Search code examples
javaphysicscollision-detection

Quick and dirty platformer with Physics has errors with moving platforms


Alright, so I'm trying to make a quick and dirty platformer engine, and I am having some problems with my collision detection and moving platforms. for one thing the "player" seems to bounce on top of moving platforms ever so slightly, and when he hits the right side errors happen as well. I'll upload a jnlp demo so you can try to find more errors and see whats happening, but here is the source:

import java.awt.Rectangle;
import java.util.Vector;

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;


public class Platformer extends BasicGame{

 boolean keys[];
 int ALL_KEYS = 0xFF;
 Player player;
 Vector<Vector<Thing> > things;
 int level = 0;

 public Platformer() {
  super("You've met with a terrible fate, haven't you?");
 }

 public void init(GameContainer gc) throws SlickException {
  keys = new boolean[ALL_KEYS];
  for(int i = 0; i < ALL_KEYS; i++){
   keys[i] = false;
  }

  player = new Player();
  things = new Vector<Vector<Thing> >();
  Vector<Thing> temp = new Vector<Thing>();
  temp.add(new Thing(0, 440, 640, 40, 1));
  temp.add(new Thing(200, 300, 240, 50, 1));
  temp.add(new Thing(500, 200, 240, 50, 1));

  things.add(temp); 
 }

 public void update(GameContainer gc, int delta) throws SlickException{
  if(keys[Input.KEY_UP]){
   player.velo = player.maxJump;
   keys[Input.KEY_UP] = false;
  }

  if(keys[Input.KEY_DOWN]){
   keys[Input.KEY_DOWN] = false;
  }

  if(keys[Input.KEY_LEFT]){
   player.delta -= player.speed;

   if(player.delta < -player.maxSpeed)
    player.delta = -player.maxSpeed;
  }

  else if(keys[Input.KEY_RIGHT]){
   player.delta += player.speed;

   if(player.delta > player.maxSpeed)
    player.delta = player.maxSpeed;
  }

  else{
   if(player.delta < -0.5){
    player.delta += player.speed;
   }

   else if(player.delta > 0.5){
    player.delta -= player.speed;
   }

   else if(player.delta > -0.5 && player.delta < 0.5){
    player.delta = 0; 
   }
  }

  if(player.delta < 0)
   player.moveLeft(things.get(level));

  else if(player.delta > 0)
   player.moveRight(things.get(level));

  if(player.velo < 0)
   player.moveUp(things.get(level));

  else
   player.moveDown(things.get(level));


  things.get(level).get(1).moveRight(player, things.get(level));

 }

 public void render(GameContainer gc, Graphics g) throws SlickException{
  g.setColor(new Color(0,55,55));
  g.fillRect(0, 0, 640, 480);

  g.setColor(new Color(255,0,0));
  g.fillRect(player.x, player.y, player.width, player.height);

  for(int i = 0; i < things.get(level).size(); i++){
   if(things.get(level).get(i).type == 1)
    g.setColor(new Color(0,100,100));

   g.fillRect(things.get(level).get(i).x, things.get(level).get(i).y,things.get(level).get(i).width, things.get(level).get(i).height);
  }
 }

 public void keyPressed(int key, char c) {
  keys[key] = true;
 }

 public void keyReleased(int key, char c) {
  keys[key] = false;
 }

 public static void main(String[] args) throws SlickException{
   AppGameContainer app =
   new AppGameContainer( new Platformer() );

   app.setShowFPS(false);
   app.setAlwaysRender(true);
   app.setTargetFrameRate(60);
   app.setDisplayMode(640, 480, false);
   app.start();
 }


 class Player{
  float x = 50;
  float y = 50;

  float delta = 0; // x momentum
  float velo = 0;
  int height = 50;
  int width = 30;
  float speed = 0.2f;
  int maxSpeed = 6;
  int maxFallSpeed = 5;
  int maxJump = -8;

  public void moveLeft(Vector<Thing> things){
   x += delta;

   if(x < 0)
    x = 0;

   for(int i = 0; i < things.size(); i++){
    if(new Rectangle((int) x, (int) y, width, height).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     x += (things.get(i).x + things.get(i).width) - x;
     delta = 0;
    }
   }
  }

  public void moveRight(Vector<Thing> things){
   x += delta;

   if(x + width > 640)
    x = (640 - width);

   for(int i = 0; i < things.size(); i++){
    if(new Rectangle((int) x, (int) y, width, height).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     x -= (x + width) - things.get(i).x;
     delta = 0;
    }
   }
  }

  public void moveLeftWithThing(Vector<Thing> things, float thingSpeed){
   x -= thingSpeed;

   if(x < 0)
    x = 0;

   for(int i = 0; i < things.size(); i++){
    if(new Rectangle((int) x, (int) y, width, height).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     x += (things.get(i).x + things.get(i).width) - x;
     delta = 0;
    }
   }
  }

  public void moveRightWithThing(Vector<Thing> things, float thingSpeed){
   x += thingSpeed;

   if(x + width > 640)
    x = (640 - width);

   for(int i = 0; i < things.size(); i++){
    if(new Rectangle((int) x, (int) y, width, height).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     x -= (x + width) - things.get(i).x;
     delta = 0;
    }
   }
  }

  public void moveUp(Vector<Thing> things){
   y += velo;

   velo += speed;

   if(velo > maxFallSpeed)
    velo = maxFallSpeed;

   for(int i = 0; i < things.size(); i++){
    if(new Rectangle((int) x, (int) y, width, height/2).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     y += (things.get(i).y + things.get(i).height) - y;
     velo = 0;
    }
   }
  }

  public void moveDown(Vector<Thing> things){
   y += velo;

   velo += speed;

   if(velo > maxFallSpeed)
    velo = maxFallSpeed;


   boolean b = false;
   for(int i = 0; i < things.size(); i++){
    if(!b && new Rectangle((int) x, (int) y + (height/2), width, height).intersects(new Rectangle((int) things.get(i).x, (int) things.get(i).y, things.get(i).width, things.get(i).height))){
     y -= (y + height) - things.get(i).y;
     velo = 0;
    }
   }
  }

 }

 class Thing{
  float x = 50;
  float y = 50;
  int height = 50;
  int width = 30;
  int type = -1;
  float speed = 0.5f;

  public Thing(float x, float y, int width, int height, int type){
   this.x = x;
   this.y = y;
   this.width = width;
   this.height = height;
   this.type = type;
  }

  public void moveUp(Player player){
   y -= 0.5f;

   if(new Rectangle((int) x,(int) y, width, height).intersects(new Rectangle((int) player.x, (int) player.y, player.width, player.height))){
    player.y -= (player.y + player.height) - y;
    player.velo = 0;
   }
  }

  public void moveRight(Player player, Vector<Thing> things){
   x += speed;

   if(new Rectangle((int) x,(int) y - 1, width, height).intersects(new Rectangle((int) player.x, (int) player.y, player.width, player.height))){
    player.moveRightWithThing(things, speed);
   }
  }
 }

}

And here is the demo: http://prime.programming-designs.com/java/platformer_demo/platdemo.jnlp


Solution

  • The reason why you are having trouble finding the error is that the code is ghastly.

    • Vast amounts of duplication of code: moveLeft, moveRight, moveUp and moveDown are all very similar.
    • Logic for acceleration due to gravity appears in two places.
    • Collision logic repeated all over the place.
    • Desperately poor choice of names. velo for velocity downwards, but delta for velocity rightwards (mislabelled as "momentum"): why not vy and vx? Also maxSpeed for maximum horizontal velocity but maxFallSpeed for maximum vertical velocity.
    • Acceleration/deceleration called speed.
    • Wasteful allocation, e.g. calling new Rectangle every time you want to test whether something has hit something else.
    • Positions are floating-point, but collision is based on integers.

    Duplication of code has the effect of introducing bugs, because when functionality is duplicated there's the possibility of getting it wrong in one of the places, and also of hiding bugs, because the volume of code makes it harder to spot them.

    Anyway, the bouncing on platforms may be something to do with the fact that in Player:moveDown you are using a rectangle that's offset by half the player's height, whereas in Thing:moveRight you are colliding with a rectangle that's not offset. (But that's just a guess.)