Search code examples
javaopengllwjglgame-physics

Sliding a little bit after stopping


Remember playing Super Mario Bros.? When Mario moves, he runs, and then when the player stops moving, he stops, but he doesn't come to a complete stop. He slides a little bit. I need to accomplish that. Over the past 2 days, I've been completely rewriting the movement my Mario clone/platformer game, because the model I had before was inefficient and just crappy overall. Right now, I'm not sure how to continue that small bit of velocity so that the stop isn't so sudden. How would I accomplish this? All the code for movement below is in the Player class and the code for controls is in the Main class. So what can I do to detect when the player stops, and use that to add just a little bit of velocity?

Main:

import com.hasherr.platformer.entity.Player;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;

public class Main {

    private void display() {
        try {
            Display.setDisplayMode(new DisplayMode(1000, 550));
            Display.setTitle("Unnamed Platformer Game");
            Display.create();
        } catch (LWJGLException e) {
            e.printStackTrace();
            System.exit(0);
        }

        // OpenGL

        while (!Display.isCloseRequested()) {
            Display.update();
            Display.sync(60); // sync to 60 fps
            initGL();

            player.update();
            handleKeyboardInput();
        }

        Display.destroy();
    }

    private boolean keyboardInUse() {
        boolean keyboardInUse;
        if (!(Keyboard.isKeyDown(Keyboard.KEY_A)
                || Keyboard.isKeyDown(Keyboard.KEY_D) || Keyboard
                    .isKeyDown(Keyboard.KEY_SPACE))) {
            keyboardInUse = false;
        } else {
            keyboardInUse = true;
        }

        return keyboardInUse;
    }

    private void handleKeyboardInput() {
        if (!keyboardInUse()) {
            player.goingLeft = false;
            player.goingRight = false;
            player.resetVelocity();
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_D)) {   
            player.goingLeft = false;
            player.goingRight = true;
            player.moveRight();
        } else if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
            player.goingLeft = true;
            player.goingRight = false;
            player.moveLeft();
        } else if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
            player.jump();
        }

    }

    private void initGL() {
        // initial OpenGL items for 2D rendering
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_TEXTURE_2D);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glOrtho(0, 1000, 0, 550, 1, -1);

        // start rendering player image
        player.grabTexture().bind();
        glBegin(GL_QUADS);

        glTexCoord2f(0, 0);
        glVertex2f(player.xPos, player.yPos);

        glTexCoord2f(1, 0);
        glVertex2f(player.xPos + 150, player.yPos);

        glTexCoord2f(1, 1);
        glVertex2f(player.xPos + 150, player.yPos + 150);

        glTexCoord2f(0, 1);
        glVertex2f(player.xPos, player.yPos + 150);
        glEnd(); // stop rendering this image
    }

    Player player = new Player();

    public static void main(String[] args) {
        Main main = new Main();
        main.display();
    }
}

Player:

import java.io.IOException;

import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;
import org.newdawn.slick.util.ResourceLoader;

public class Player {

    public Texture playerTexture;

    // Positions & speed
    public float xPos = 20.0f; // This is initial
    public float yPos = 0.0f; // Same as above.

    public float xVel, yVel;

    public static int gravityForce = 6;
    public static int jumpVelocity = 100;
    private float moveSpeed = 8.0f;
    private static int MAX_MOVE_SPEED = 25;

    public boolean isSupported = true; // Once again, initial value.
    public boolean goingRight, goingLeft, canJump;

    // movement methods & constants

    public void update() {
        applyGravity();
        checkForSupport();
    }

    public Texture grabTexture() {
        try {
            playerTexture = TextureLoader.getTexture("PNG",
                    ResourceLoader.getResourceAsStream("res/test_char.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return playerTexture;
    }

    private void checkForSupport() {
        if (yPos == 0) {
            isSupported = true;
        } else if (yPos > 0 /* and is not on a platform */) {
            isSupported = false;
        }
    }

    private void applyGravity() {
        if (!isSupported) {
            yPos -= gravityForce;
        } 
    }

    public void printPos(String moveMethod) {
        System.out.println(" X: " + xPos + " Y: " + yPos + " Going Right: "
                + goingRight + " Going Left: " + goingLeft);
    }

    // movement methods

    public void resetVelocity() {
        moveSpeed = 15;
    }

    private void accelerateX() {
        moveSpeed += (float) (moveSpeed * 0.0096);
        if (moveSpeed >= MAX_MOVE_SPEED) {
            moveSpeed = MAX_MOVE_SPEED;
        }
        System.out.println(moveSpeed);
    }

    private void accelerateY() {

    }

    public void moveRight() {
        printPos("Moving Right!");
        accelerateX();
        xPos += moveSpeed;
    }

    public void moveLeft() {
        printPos("Moving Left!");
        accelerateX();
        xPos -= moveSpeed;
    }

    public void jump() {
        printPos("Jumping!");
        accelerateY();
        yPos += jumpVelocity;
    }

}

Solution

  • Every update decrement Mario's velocity, and move mario's position by his velocity.

    You should be allowing his velocity to persist across multiple updates. Currently you're only moving mario when a left or right arrow key is depressed. This is a fine approach, and works for some games, but now that you're wanting more physical behavior a new approach is necessary. Time to conserve some energy!

    Try this instead:

    float acceleration = 15;
    private void accelerateX(float speed) {
        moveSpeed += (float) (speed * 0.0096);
        if (moveSpeed >= MAX_MOVE_SPEED) {
            moveSpeed = MAX_MOVE_SPEED;
        }else if(moveSpeed<=-MAX_MOVE_SPEED){ moveSpeed= - MAX_MOVE_SPEED;}
    }
    public void moveRight() {
        printPos("Moving Right!");
        accelerateX(acceleration);
    }
    
    public void moveLeft() {
        printPos("Moving Left!");
        accelerateX(-acceleration);
    }
    //call this every update.
    public void update(){
        float minMoveSpeed = 1;
        //snap to zero movement if movespeed is too slow.
        //this is so the player actually stops moving eventually. otherwise
        //the dampening affect would constantly shift the player to and fro.
        if(this.moveSpeed<minMoveSpeed && this.moveSpeed>-minMoveSpeed){
            this.moveSpeed = 0;
        }
        else{
            float dampening = 1f;
            //Counter velocity. this will bring mario to a halt over a number of updates
            //play with the value of dampening to get right effect.
            double sign = -(int)Math.signum(moveSpeed);
            float dampeningValue = dampening*sign;
            this.moveSpeed += dampeningValue;
        }
        xPos+= this.moveSpeed;
    }
    

    This code allows his velocity to persist across updates and slowing brings Mario to a stop if velocity isn't being incremented/decremented by moveRight/moveLeft.

    Also you should be scaling your velocity by time so the game runs similarly on different hardware. This is less important at this point in development; just keep it in mind for the future.