Basic platform movement and gravity

Hello!

I’m working on a very basic platform engine. This is my first attempt so please excuse me for sloppy programming pratice and such :stuck_out_tongue:
I’ve got a working platform engine. A player that moves and a “world” loaded from a file.

My problem is that the movement and physics isn’t that good and i really don’t like how it looks.
I’d like to know what to do with the code to get a smooth movement and physics that works properly!
I’ve implemented collision detecion, which I think works okay but if someone thinks diffrently please explain what I should do to make it better.

If someone helps me out with this it would help my out alot :smiley:

So time for some code :slight_smile:

My Frame class (The main class)


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;


public class Frame extends JFrame implements Runnable,KeyListener {

	
	private static final long serialVersionUID = 1L;
	
	// SCREEN VARIABLES
	int screenWidth  =640;
	int screenHeight =480;
   
	// world objeccts and variables
	
	Map map;
    Block blocks;
	
	
	// Graphicsh 
	BufferedImage backBuffer;
	ImageEntity background;
	Graphics2D g2d;
	
	// animated sprite variable
	//AnimatedSprite player;
	
	// Hero variables
	AnimatedSprite hero;
	Hero player;
	
	
	int playerX = 320;
	int playerY = 100;
	int moveX,moveY;
	
	boolean collision = false;
	int  test = 0;
	// world physics variables
	long TimeStart,TimeEnd;
	
	
	// Fps variables
	int frameCount = 0;
	int frameRate = 0;
	long startTime = System.currentTimeMillis();
	int desierdRate = 60;
	
	// Threads
	Thread gameloop;
	
	
	public static void main(String[] args) {
		new Frame();
	}
	
    Frame () {
    	
    	// Basic GUI STUFF
    	super ("The Diary");
    	setSize (screenWidth,screenHeight);
    	setVisible (true);
    	setResizable (false);
    	setDefaultCloseOperation (3);
    	
    
    	
    	// Create a backbuffer so the screen wont flicker like hell!
    	backBuffer = new BufferedImage (screenWidth,screenHeight,BufferedImage.TYPE_INT_RGB);
        g2d = backBuffer.createGraphics();
        background = new ImageEntity (this);
        background.load("bluespace.png");


        // Hero
        player = new Hero (32,32);
        player.showBounds(true);
       // player.setGravity(5);
        
        //map
        map = new Map("maze.txt");
        
     
           
        
         
        
        // skapar och startar min tråd
        gameloop = new Thread (this);
        gameloop.start();
        addKeyListener (this);
   
    	         
        

     }
    
    public void run () {
    	Thread t = Thread.currentThread();
    	
     	try {
			map.createMap();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
    
    	
    	
    	while (t == gameloop) {
    		try {
    			Thread.sleep(1000 / desierdRate);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		gameUpdate ();
    		repaint ();
    	}
    }
    

    // Game update here is it where the magics happends
   
    void gameUpdate ()  {
    
    	// räknar ut FPS
    	log ("FPS :"+ frameRate);
    	frameCount++;
    	if (System.currentTimeMillis() > startTime + 1000) {
    		startTime = System.currentTimeMillis();
    		frameRate = frameCount;
    		frameCount = 0;
    	}
        
    	
    	// backgrouunds bilden
    	g2d.setColor(Color.GRAY);
    	g2d.fill(new Rectangle (0,0,screenWidth-1,screenHeight-1));
       
        // rita ut alla blocks
    	
    	for (Block b : map.getBlocks()) {
			b.draw(g2d);
			
		}
		
    	checkCollision () ;
    	player.draw(g2d);
    	player.move();
    	
    
        
    	// Draw debug info
    	g2d.setColor(Color.WHITE);
    	g2d.drawString("FPS: "+ frameRate, 50, 50);
    	g2d.drawString("Player X" + player.positionX, 50,70);
        g2d.drawString("Player Y"+ player.positionY, 50, 80);
        g2d.drawString ("Collison:" + player.collision,50,100);
        g2d.drawString("Falling: " +  player.falling, 50, 110);
        
       
         
    }
   
    
   
    private boolean checkCollision() {
   
    	for (int n = 0; n < map.getBlocks().size();n++) {
    	   
    		if (player.getBounds().intersects(map.getBlocks().get(n).getBounds())) {
    		

    			return player.collision = true;
    		}
    	 }
    	
        return player.collision = false;
    	
    }


    
	public void paint (Graphics g) {
    	 //paint the backBuffer
    	g.drawImage(backBuffer, 0, 0, this);
    }

	

		


	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_LEFT:
			player.setX(-5);
			
			break;
		
		case KeyEvent.VK_RIGHT:
			player.setX(5);
	
			break;
		case KeyEvent.VK_UP :
		
		player.setGravity(0);
	    player.setY(-50);
			
			break;
	
		case KeyEvent.VK_SPACE:
			player.positionX = 150;
			player.positionY = 150;
			break;
		
	
		}
		
		
	}

	
	public void keyReleased(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_LEFT:
		  player.setX(0);
		  //player.setGravity(5);
			break;
		case KeyEvent.VK_RIGHT:
		  player.setX(0);
		  //player.setGravity(5);
			break;
		case KeyEvent.VK_UP:
		   player.setY(0);
		   player.setGravity(5);
			break;
				
		
		
		}
		
	}
	public void keyTyped(KeyEvent e) {
		
		
	}
	
	public void log(String s) {
		//System.out.println (s);
	}
 	
	
	


}



My Hero class :

import java.awt.*;


public class Hero {

	int positionX,positionY,x,y;
	int width,height;
	int gravity;
    boolean drawBounds;
    boolean collision,falling ;
	Map m;
	
	
	// konstruktör
	Hero (int width,int height) {
		positionX = 150;
		positionY = 150;
		this.width = width;
		this.height = height;
		drawBounds = false;
	}
	
	// riitar ut objectet
	public void draw (Graphics2D g2d) {
		g2d.setColor(Color.BLACK);
		g2d.fillRect(positionX, positionY, width, height);
		
		if (drawBounds == true) {
			g2d.setColor(Color.RED);
			g2d.drawRect(positionX + x , positionY +y, width, height);
		}
	}
	
	// collision detections rectangle som är en aning före obejctet!
    public Rectangle getBounds () {
    	Rectangle r ;
    	r = new Rectangle (positionX +x ,positionY +y ,width,height);
    	return r;
    }
	
	
	// en setter som talar om om man vill visa collsionrectangle
	public void showBounds(boolean b) {
		drawBounds = b;
	}
	


	
	public void setX (int x) {
		this.x = x;
	}
	public void setY (int y) {
		this.y = y;
	
	}
	
	
	public void move () {
		if (collision == true) {
            setGravity (0);
            x = 0;
            y = 0;
		}
		
		if (y > 0) {
			falling = true;
		} else  {
			falling = false;
		}
		
		if (x > 0 || x< 0 && collision == false) {
			setGravity (5);
		}
		
		
		positionX +=x;
		positionY +=y;
		gravity();
		
		
		
	}

	public void setGravity (int gravity){
		this.gravity = gravity;
	}
	
	private void gravity () {
			setY (gravity);
	
	 }
		  
	      
	}
	


If needed my block class and world class


import java.awt.*;

import javax.swing.ImageIcon;
public class Block {

	int width,height,x,y;
	Image BLOCK_WALL;
	
	public Block (int x, int y) {
		this.x = x;
		this.y = y;
		width  = 20;
		height = 20;
	    BLOCK_WALL = new ImageIcon ("D:/Programmering/Projekt/Workspace/Framework/src/block_wall.png").getImage();
	}
	
	public void draw (Graphics2D g2d) {
		g2d.drawImage(BLOCK_WALL, x, y, width,height,null);
		g2d.setColor(Color.RED);
		g2d.drawRect(x, y, width, height);
	}
	public Rectangle getBounds() {
		Rectangle r;
		r = new Rectangle  (x,y,width,height); 
	    return r;
	}

}



import java.util.*;
import java.io.*;

public class Map {

	ArrayList <Block> blocks;
	String filePath;
	
	
	public Map (String path) {
		filePath= path;
		blocks = new ArrayList<Block>();
	}
	public void createMap () throws IOException {
		
		ArrayList <String> lines = new ArrayList <String>();
		
		BufferedReader r= new BufferedReader (new FileReader (filePath));
		
		while (true) {
			String line = r.readLine();
			
			if (line == null) {
				r.close();
				break;
			}
			else {
				lines.add(line);
			}
		}
		
		for (int y = 0; y < lines.size(); y++) {
			for (int x = 0; x<lines.get(y).length();x++) {
				// laddar in mapppen med x och y värden
				
				char mark = lines.get(y).charAt(x);
				
				
				
				if (mark == '#') {
					blocks.add(new Block (x*20,(y*20)+25));
				}
			}
		  }
	   } 
	
		public ArrayList <Block> getBlocks () {
			return blocks;
		}
		
		
		
	
}



I’m new to this fourm so please excuse me if I’ve posted in the wrong forum and for my unbelievable long post x)

Thank in advance!

Your movement code looks fine, but try to call move before you paint it or else there will be a lag effect.

Could you be a bit more specific on how your movement isn’t “good”?

Hi, thanks for your replay!

I’ve changed the code so the move method is before the draw method in my gameUpdate but I didn’t notice something.

Well what I ment was that the movement fells very choppy and unnatural… do you have any advice?

It seems like the choppiness is more because of the way you’re performing the movement than because of the way you’re drawing.

A 32x32 character jumping 50 pixels upwards instantly? This will make it look choppy because motion is not instant. Typically, a jump will follow some sort of parabolic arch which follows a height = velocity + time * acceleration formula. This makes it so that as time passes the jump will go from quickly going upwards, to slowing, before peaking out and starting to fall.

If you want to make it so that the jumper will go a maximum of 50 pixels upwards in say 5 time steps you just do:

50 = velocity + 5 * gravity
50 - 5 * gravity = velocity

Okay! Yeah that sounds pretty logic… do you think I should change the code for the left and right movement aswell?
I’m not completly sure how to implement the velocity for the movement so if you could point me in the direction (I know that you already given my a hint) :smiley:

  1. Make all your ints float, and then cast them to int when you need to do drawing and such. This will make things much smoother.
  2. Rethink your logic to be more like real physics. In other words, as a person in the real world your downward velocity is not just “gravity.” It could be ana accumulation of any number of forces. Each movable object in the world has 3 components:
    • Position
    • Velocity
    • Acceleration(s) applied to velocity
    In the real world, there is no way for your position to just magically change. Your position only changes when your velocity is nonzero, and its your velocity that changes your position. Similarly, there is no way for your velocity to instantly change. It can only be changed by accelerations. You can however cause all sorts of accelerations. When you jump, your legs are applying a single large acceleration upwards. Meanwhile gravity is constantly applying a downward acceleration to you, so eventually it overcomes that single up acceleration and you come back down.

In simple code terms:


public class Vector2
{
    public float x;
    public float y;
    public Vector2(float xPos, float yPos)
    {
        x = xPos;
        y = yPos;
    }
}

public class PhysicalEntity
{
    private Vector2 position;
    private Vector2 velocity;
    private boolean isOnGround;
    public static final float GROUND_FRICTION = 0.9f;
    public static final float AIR_FRICTION = 0.99f;

    public PhysicalEntity(Vector2 pos)
    {
        position = pos;
        velocity = new Vector2(0,0);
        isOnGround = false;
    }

    public void update(float delta)
    {
        //apply friction
        if (isOnGround)
        {
            velocity.x *= 1.0f - ((1.0f - GROUND_FRICTION) * delta);
            velocity.y *= 1.0f - ((1.0f - GROUND_FRICTION) * delta);
        }
        else
        {
            velocity.x *= 1.0f - ((1.0f - AIR_FRICTION) * delta);
            velocity.y *= 1.0f - ((1.0f - AIR_FRICTION) * delta);
        }

        //change position by velocity
        position.x += velocity.x * delta;
        position.y += velocity.y * delta;
    }

    public void applyAcceleration(Vector2 acceleration)
    {
        velocity.x += acceleration.x;
        velocity.y += acceleration.y;
    }
}

public class World
{
    public static final Vector2 GRAVITY = new Vector2(0.0f, -9.8f);
    private ArrayList<PhysicalEntity> entities;

    public World()
    {
        entities = new ArrayList<PhysicalEntity>();
    }

    public void update(float delta)
    {
        Vector2 frameGravity = new Vector2(GRAVITY.x * delta, GRAVITY.y * delta);
        for (int i = 0; i < entities.size(); i++)
        {
            entities.get(i).applyAcceleration(frameGravity);
            entities.get(i).update(delta);
        }
    }
}

That should get you started. Then when you press left you apply a negative X acceleration, right you apply a positive, etc. When you jump you apply a positive Y acceleration. Pretty easy. The big thing is that in most platformers you want the ground friction amount to be very high (that means a very low value in that GROUND_FRICTION coefficient) as sliding can be really annoying. But you notice if you play Mario he actually has momentum, which is what you get without too much friction.

Ah, just a quick note about that big post there!

While it is correct for all intents and purposes, due to the fact that most graphical positional systems do not follow the Cartesian Coordinate system, you have to make a decision between using the same system as your graphics do or writing some form of translation between the two. Typically, people go with changing their system instead of having the translation.

Graphical representation
[tr][td][/td][td]0[/td][td]1[/td][td]2[/td][td]3[/td][/tr][tr][td]0[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]1[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]2[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]3[/td][td][/td][td][/td][td][/td][td][/td][/tr]

Cartesian (Described above)
[tr][td][/td][td]0[/td][td]1[/td][td]2[/td][td]3[/td][/tr][tr][td]3[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]2[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]1[/td][td][/td][td][/td][td][/td][td][/td][/tr][tr][td]0[/td][td][/td][td][/td][td][/td][td][/td][/tr]

Basically, you either have to use a inversed accelerations and a Y-Position system where small values appear higher up, or you’ll need to write a translation system for your game that will convert the Cartesian to the Graphical (Typically by having something like screenPosY = screen.height - object.position, every time you want to draw something). Where as, just writing your game’s physics from the other view point feels more much intuitive (At least for people like me.)

Yes good point. I neglected to mention this. :slight_smile: If you’re using Java2D you are probably using the graphical representation, which means reversing gravity to cause a positive Y (because up is down, as UprightPath said).

Ohh, well! loads of new code to understand and learn :smiley: Thanks alot guys for trying to help me out!
If I get stuck I’ll ask away :slight_smile:

Thanks again!

Even things like Slick2D use this system (Which, since it’s meant to be sort of analogous to Java2D is understandable).

Another thing to note, about his example is the idea of the ‘Delta’, which appears in the World.update(float delta) class. If I’m correct (And I’m not sure, heh) this delta refers to the amount of time since the last update was called. In some cases, this will always be the same or close enough not to matter (Especially if you have something managing your update rate), in other cases this will vary enough that your objects might seem to move in a more “jerky” method, as you’d described earlier. This really only happens when you go from having very small deltas to suddenly having a very large one.

Correct me if I’m wrong there.

I included the delta because he mentioned he’s using variable timestep.

Delta is usually how many seconds the last update took. That way you can express everything in terms of meters or pixels per second, and if you multiply times delta that’s what you get. If you’re using fixed timestep, you don’t have a delta. I’ve also just made a delta of 1.0 mean it was the target update time (like if I want it to run at 60fps and the last frame took 1/60th of a second). Either way works as long as you’re consistent.

EDIT - I’m mistaken, someone else said they were using variable timestep. You can have a look at that info here:

^

[quote]Your position only changes when your velocity changes is not zero
[/quote]
FTFY

Hey again! I was able to implement the physics and it workes fine!
But what would progaming be whiteout some problem! :stuck_out_tongue:
As I said the movement is perfect but now my collision detecion get screwed up by using this new vector class that you wrote for me.

My bounding rectangle before was created like this:

   public Rectangle getBounds () {
       Rectangle r ;
       r = new Rectangle (positionX +x ,positionY +y ,width,height);
       return r;
    }

This worked like a charm because the bounding rectangle would move before the player which would make the collision perfect.

I’ve tried to implement this this collision detecion with the new physics code like this :


  public Rectangle getBounds () {
    	Rectangle r ;
    	r = new Rectangle ((int)position.x + (int) velocity.x,(int) position.y + (int)velocity.y, width, height);
    	return r;
    }

but I’m not able to get it to work! Do you have any suggestion on how I should do this insteed?

p.s sorry about my grammar, it’s kinda late xD

Ah, for collision detection?

You’d put a check where the comment “//change position by velocity” is in the prior code. Otherwise you’re moving the character twice (I’m assuming that you’re checking after you’ve run the update code, which means you’re adding velocity.x/y twice, and the second time without applying the delta.)

Well this is how I’ve done it this far:

in my frame class I’ve the detection of collision… (It has to be here due to the way I draw my world.)

 private boolean checkCollision () {
    	
        for (int n = 0; n < map.getBlocks().size();n++) {
            
            if (player.getBounds().intersects(map.getBlocks().get(n).getBounds())) {
            

               return player.collision = true;
            }
          }
         
          return player.collision = false;
         
      }
  Then I my hero class I have:
 public void setIsOnGround ()  {
		
		if (collision == true) {
			isOnGround = true;
			
			
		} else {
			updateGravity (0.5f); //<- stops the player from falling trhough blokcs in some way!
			isOnGround = false;
          
		   
		}	
	}

 
   

And in my gameUpdate method I have this



// updates the game
    	
    	
    	checkCollision ();
    	player.setIsOnGround();
    	player.update(0.5f);
    	
    	
    	gameRender ();



The overrided physical entity method in my hero class :


	@Override
	public void update (float delta) {
		
		//apply friction
        if (isOnGround){
            velocity.x *= 1.0f - ((1.0f - GROUND_FRICTION) * delta);
            velocity.y *= 1.0f - ((1.0f - GROUND_FRICTION) * delta);
        }
        else{
            velocity.x *= 1.0f - ((1.0f - AIR_FRICTION) * delta);
            velocity.y *= 1.0f - ((1.0f - AIR_FRICTION) * delta);
        }
        
        
        
        if (collision == true )  {
        	velocity.x = 0;
        	velocity.y = 0;
        	//GRAVITY.x = 0;
        	//GRAVITY.y = 0;
        }
        
        
        

        position.x += velocity.x * delta;
        position.y += velocity.y * delta;
        }




With this code I’ve managed to get it to stop when it hits a block from above due to the gravity… but when I hit a block I get stuck and can’t move. What should happen to the gravity and the two booleans (collision,isOnGround) when the object hit a block?

Any suggestions? :slight_smile:

Check both directions. This means that you’ll have to figure out whether the collision is occurring due to your horizontal or vertical velocity.

This can be a little difficult, since I’m not sure how your map is being implemented. However, basically you have three cases:

Collision Up: velocity.y = 0, isFalling = true
Collision Down: velocity.y = 0, isFalling = false
Collision Left/Right: velocity.x = 0, isFalling = true

You’d check for these in the direction you’re moving. So, if your velocity.y is of the same sign as your gravity, then you’re obviously falling down, and only have to check up. If your velocity is the opposite sign of gravity, then you check up.

This takes a bit of math, really. And a bit more information. But, if you can write something that figures out which is closer, the X or the Y collision you might~

Hmm, the way that my world is implemented is on the first post.
You mean that I should check this every time I move?
I’ll will have to think about how I should implement this collision testing but if you could help me I would be very glad :smiley:
TIA!

Actually, looking now, what I said was somewhat wrong. Collision Left/Right should not affect the isFalling flag. Only a downward collision should if you’re using simple collision physics.

I’ll have to think about the collision system, because the only system I’ve really done using it is using another method than the rectangle1.intersets(rectangle2). However, if you get a collision, you can probably figure out which edge the collision would have occurred on.

Say, you have…

Player and Block. Player has a velocity vector. Player and Block have a distance vector (distance.x = player.x - block.x, distance.y = player.y - block.y). From that distance vector, we can figure out which corners are important in figuring out the collision. Top left, top right, bottom left, bottom right. From there, you just follow the the player’s velocity vector, plotted from the important corner, to see whether it bumps into the vertically or horizontally.

Sorry, I can’t provide a better explanation of it, I can see it in my head, but this math is beyond me this morning.

FTFY
[/quote]
Thank you. :slight_smile:

For collision detection I typically just do a raycast in the direction of my velocity and if I hit anything then I clamp the position change to only be as far as the distance to the item the raycast hits. If it doesn’t hit anything, move the position the full velocity distance.

Yeah, this is where it goes from merely writing the physics engine to actually deciding how you want your physics engine to work.

For Raycasting, I’m not completely sure how well that will work. I’m only passingly knowledgeable about the subject, but it seems to me that using such a method can result in missed collisions if the objects in question are greatly different in size (Such as player being much larger than the object). Like, allowing a player to pass through a bar because it’s not in the raycast path.

If you can figure out where the closest collision occurs, you can figure out which of the velocities need to be changed and stuff like that.

I just looked back at your code, which I must admit I didn’t do initially, it seems as though it’d be difficult to figure out collision direction, because it will always find the first object that you’d collide with from the TOP-LEFT side of the screen. Instead of finding the closest object in the direction of your velocity. This means that if you’re moving fast enough that you might land on several objects (velocity.X > 20, or velocity.y > 25), you’ll always get the one that’s further from you if you’re moving left, up or up-and-left, which will make figuring out where the closest collision is happening difficult.