Detecting tile collisions in a 2D platformer

Started by
4 comments, last by Starfarer42 9 years, 6 months ago

My question is there a better way of detaching collisions. I never done a 2D platformer and I already learned a lot of lessons after solving the coordinate comparisons problem with the main character colliding with the block.

I was thinking storing the blocks tiles into a list for a given "game map" class. But then it seems the main character need to go through constantly a list of blocks let say for "24 blocks" which means 24 elements in an array list even though the main character is no where the blocks but is in the game map that contains those blocks.

I love to hear everyone suggestions. I know the below code can be improved. block 1 code is the same code as block 0 code. The difference is the replace block0 name with block1 name and you will see the code is exactly the same.

Code is in Java.

 
// block 0 code
if(getX() + idleRightAnim.getWidth() > block.getX() - camera.getX()
&& getX() < block.getX() + block.getWidth() - camera.getX()
&& getY() + idleRightAnim.getHeight() == block.getY() - camera.getY()
&& state != ActionState.RUNNING && state != ActionState.ATTACKING)
{
 
state = ActionState.IDLE;
onSurface = true;
 
 
}
 
if((getX() + idleRightAnim.getWidth() < block.getX() - camera.getX()
|| getX()  > block.getX() + block.getWidth() - camera.getX())
&& getY() + idleRightAnim.getHeight() == block.getY() - camera.getY())
{
onSurface = false;
 
}
 
// block 1 code
if(getX() + idleRightAnim.getWidth() > block1.getX() - camera.getX()
&& getX() < block1.getX() + block1.getWidth() - camera.getX()
&& getY() + idleRightAnim.getHeight() == block1.getY() - camera.getY()
&& state != ActionState.RUNNING && state != ActionState.ATTACKING)
{
 
state = ActionState.IDLE;
onSurface = true;
 
 
}
 
 
if((getX() + idleRightAnim.getWidth() < block1.getX() - camera.getX()
|| getX()  > block1.getX() + block1.getWidth() - camera.getX())
&& getY() + idleRightAnim.getHeight() == block1.getY() - camera.getY())
{
onSurface = false;
 
}
 

ScreenShot2014-09-27at105957PM_zps5372b3

Advertisement

You're tile map should be stored in such a way it's easy to get what tiles are located at which coordinates, like a Vector of Vectors (such that you can check [TileX][TileY], or make a call to your Map class to retrive the tiles within your world X, Y.

You would then only compare the tiles that live within your characters bounding box coordinates (X, Y - X + Char.Width, Y + Char.Height). No need to check every tile that exists in your map.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

My reccomendation is to just check each object for collision in a double for loop, and check for collision pairs.

However, if this method can be rather slow, since your doing o(n)^2 checks.

You may wish to optomize later, and add a broadphase collision test, such as a sweep and prune, or a quad-tree.

Whatever floats your boat.

View my game dev blog here!

For a 2D platformer, I think a good idea would be to have two-dimensional array for tiles.

Then you can do:


ArrayList<Rectangle> collision = new ArrayList<Rectangle>();

int xBegin = playerPos.x / Tile.TILE_WIDTH;
int xEnd   = (playerPos.x + Player.PLAYER_WIDTH) / Tile.TILE_WIDTH;

int yBegin = playerPos.y / Tile.TILE_HEIGHT;
int yEnd   = (playerPos.y + Player.PLAYER_HEIGHT) / Tile.TILE_HEIGHT;

for (int x = xBegin; x <= xEnd; x++) {
 for (int y = yBegin; y<= yEnd; y++) {
  if (tiles[x][y].isSolid()) {
   collision.add(new Rectangle(x * Tile.TILE_WIDTH, y * Tile.TILE_HEIGHT, Tile.TILE_WIDTH, Tile.TILE_HEIGHT);
  }

 }
}

I think the code is self-explanatory.

Then you can iterate through collision and handle collision for each tile (rectangle) separately.

MatejaS

Like most people already explained, use an two dimensional array with either an object type or byte.


public static final byte SOLID = 0;
public static final byte HOLLOW = 1;

byte[][] worldTiles = new byte[stageHeight][stageWidth];

And when to check for collision, the fastest way is to treat all entities as rectangles(this is often enough). You then check if the border of the rectangle is colliding with solid tile.


	//Checks if the specified GameObject collides with the given tile
	public boolean collidesWithTile(byte tileType, GameObject go)
	{
		int 	x  = (int) go.x,
			y  = (int) go.y,
			x2 = (int) (go.x + go.width),
		    	y2 = (int) (go.y + go.height);
		
		for(int lx = x; lx < x2; lx++)
		{
			if(worldTiles[y][lx] == tileType || worldTiles[y2][lx] == tileType)
				return true;
		}
		for(int ly = y; ly < y2; ly++)
		{
			if(worldTiles[ly][x] == tileType || worldTiles[ly][l2] == tileType)
				return true;
		}
		return false;
	}


go.x++;
boolean canGoRight = collidesWithTile(HOLLOW, go);
go.x--;

The great thing about byte array is that they take so little space in the ram. An map with 25 million tiles wont take more than 100mb ram.

You could also use a bunch of references instead of byte, which should take less space than byte.

This being Java, there technically are no multi-dimensional arrays -- just one-dimensional arrays of references to other arrays. So instead of a 2-dimensional array I would recommend using a 1-dimensional array and mapping the x/y coordinates to the array indexes. Keeping the tiles in one contiguous chunk of memory will have better cache locality which should give you better performance.


// Define an array large enough to hold everything in one chunk of RAM
byte[] worldTiles = new byte[HEIGHT * WIDTH];

// Map the x, y coordinates to an array index using the following formula:
byte tile = worldTiles[y * HEIGHT + x];

It's basically the same formula a C/C++ compiler would build for you automatically, but Java makes us write it out explicitly. It probably wouldn't hurt to define a class to handle that calculation for you. Perhaps something like this:


public class World {

    private byte[] tiles;
    private int width;
    private int height;

    public World(int width, int height) {
        this.width = width;
        this.height = height;
        this.tiles = new byte[width * height];
    }

    public byte getTile(int x, int y) {
        return tiles[y * height + x];
    }

    public void setTile(int x, int y, byte t) {
        tiles[y * height + x] = t;
    }
}

This topic is closed to new replies.

Advertisement