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;
}
}
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.