Collision Detection

As mentioned before, you need to make sure the player (and the baddies) can't walk through walls but can jump up on platforms. In short, every time you move a creature, you need to check to see whether the creature collided with any tiles; if it did, you must adjust the creature's position accordingly. Because you're using tile-based maps, collision detection is an easy technique to implement. We'll break this process down into parts: detecting a tile collision and correcting a sprite's position to avoid a collision.

Detecting a Collision

Theoretically, the sprite could move across several tiles at once and could be located in up to four different tiles at any one time. Therefore, you need to check every tile the sprite is currently in and every sprite the tile is going to be in. Here's getTileCollision(), which achieves this task (see Listing 5.11). It checks to see if the sprite crosses any solid tiles in its path from its old position to its new position. If so, it returns the location of the tile the sprite collides with. Otherwise, it returns null.

Listing 5.11 getTileCollision() Method
Point pointCache = new Point();
...
/**
 If a collision is found, returns the tile location of the
 collision. Otherwise, returns null.
*/
public Point getTileCollision(Sprite sprite,
 float newX, float newY)
{
 float fromX = Math.min(sprite.getX(), newX);
 float fromY = Math.min(sprite.getY(), newY);
 float toX = Math.max(sprite.getX(), newX);
 float toY = Math.max(sprite.getY(), newY);
 // get the tile locations
 int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
 int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
 int toTileX = TileMapRenderer.pixelsToTiles(
 toX + sprite.getWidth() - 1);
 int toTileY = TileMapRenderer.pixelsToTiles(
 toY + sprite.getHeight() - 1);
 // check each tile for a collision
 for (int x=fromTileX; x<=toTileX; x++) {
 for (int y=fromTileY; y<=toTileY; y++) {
 if (x < 0 || x >= map.getWidth() ||
 map.getTile(x, y) != null)
 {
 // collision found, return the tile
 pointCache.setLocation(x, y);
 return pointCache;
 }
 }
 }
 // no collision found
 return null;
}


Also, getTileCollision() treats movement off the left or right edge of the map as a collision, to keep creatures on the map. Note that this method isn't perfect when a sprite moves across several tiles in between frames (a case for a large amount of time between frames). You'll implement better sprite-to-environment collisions in , "Collision Detection," but this code will work for most cases.

Handling a Collision

Next, you'll correct the sprite's position after a collision is detected. Start with the example in Screenshot, with the sprite moving down and to the right.

Screenshot The sprite collides with a wall when it moves too far.

Java graphics 05fig08.gif


In this case, the sprite moves diagonally and collides with two sprites at the same time. Visually, it looks like an easy fix: Just scoot the sprite over to the left. But how can you calculate that the fix is to move left and not up, down, or to the right? To calculate this, break the movement of the sprite into two parts: moving horizontally and moving vertically. First, move the sprite horizontally, as shown in Screenshot.

Screenshot First, move the sprite horizontally. A collision is detected.

Java graphics 05fig09.gif


In this example, a collision with a tile is detected. To correct this, just move the sprite back the opposite way it came, lining up the sprite with the edge of the tile, as shown in Screenshot.

Screenshot Second, correct the sprite's horizontal position so it doesn't collide with any tiles.

Java graphics 05fig10.gif


Now the player has moved horizontally, and its position has been corrected to avoid a collision. Next, apply the same technique for the vertical movement, as shown in Screenshot.

Screenshot Finally, move the sprite vertically. In this case, there is no collision.

Java graphics 05fig11.gif


In this example, there is no collision when the sprite moves vertically, so you are done. The code for this is in the updateCreature() method in Listing 5.12: to move the sprite's horizontal position and then its vertical position, checking and correcting for collisions all the way.

Listing 5.12 updateCreature() Method
private void updateCreature(Creature creature, long elapsedTime) {
 // apply gravity
 if (!creature.isFlying()) {
 creature.setVelocityY(creature.getVelocityY() +
 GRAVITY * elapsedTime);
 }
 // change x
 float dx = creature.getVelocityX();
 float oldX = creature.getX();
 float newX = oldX + dx * elapsedTime;
 Point tile =
 getTileCollision(creature, newX, creature.getY());
 if (tile == null) {
 creature.setX(newX);
 }
 else {
 // line up with the tile boundary
 if (dx > 0) {
 creature.setX(
 TileMapRenderer.tilesToPixels(tile.x) -
 creature.getWidth());
 }
 else if (dx < 0) {
 creature.setX(
 TileMapRenderer.tilesToPixels(tile.x + 1));
 }
 creature.collideHorizontal();
 }
 // change y
 float dy = creature.getVelocityY();
 float oldY = creature.getY();
 float newY = oldY + dy * elapsedTime;
 tile = getTileCollision(creature, creature.getX(), newY);
 if (tile == null) {
 creature.setY(newY);
 }
 else {
 // line up with the tile boundary
 if (dy > 0) {
 creature.setY(
 TileMapRenderer.tilesToPixels(tile.y) -
 creature.getHeight());
 }
 else if (dy < 0) {
 creature.setY(
 TileMapRenderer.tilesToPixels(tile.y + 1));
 }
 creature.collideVertical();
 }
}


The updateCreature() method also applies gravity to creatures that aren't flying. Gravity always affects the creatures, but if a creature is standing on a tile, the effect isn't visible because the creature collides with the tile it is standing on. When a collision is detected and corrected for, the creature's collideVertical() or collideHorizontal() methods are called. Usually these methods change or halt the velocity of the creature so the collision won't happen again any time soon. That's it! Now the player and the baddies can roam around the map without walking through tiles.

Sprite Collisions

Next, you need to detect when the player collides with other sprites, such as power-ups and bad guys. In this game, you'll ignore collisions between creatures and just detect collisions with the player. This is simply a matter of seeing whether the player's sprite boundary intersects with another sprite's boundary, as shown in the isCollision() method in Listing 5.13.

Listing 5.13 isCollision() Method
public boolean isCollision(Sprite s1, Sprite s2) {
 // if the Sprites are the same, return false.
 if (s1 == s2) {
 return false;
 }
 // if one of the Sprites is a dead Creature, return false
 if (s1 instanceof Creature && !((Creature)s1).isAlive()) {
 return false;
 }
 if (s2 instanceof Creature && !((Creature)s2).isAlive()) {
 return false;
 }
 // get the pixel location of the Sprites
 int s1x = Math.round(s1.getX());
 int s1y = Math.round(s1.getY());
 int s2x = Math.round(s2.getX());
 int s2y = Math.round(s2.getY());
 // check if the two Sprites' boundaries intersect
 return (s1x < s2x + s2.getWidth() &&
 s2x < s1x + s1.getWidth() &&
 s1y < s2y + s2.getHeight() &&
 s2y < s1y + s1.getHeight());
}


Because the TileMap contains all the sprites in a list, you can just run through the list checking every sprite to see if it collided with the player, shown in Listing 5.14.

Listing 5.14 getSpriteCollision() Method
public Sprite getSpriteCollision(Sprite sprite) {
 // run through the list of Sprites
 Iterator i = map.getSprites();
 while (i.hasNext()) {
 Sprite otherSprite = (Sprite)i.next();
 if (isCollision(sprite, otherSprite)) {
 // collision found, return the Sprite
 return otherSprite;
 }
 }
 // no collision found
 return null;
}


What happens when a collision with a sprite is made is entirely up to you. If the sprite is a power-up, you can just give the player points, play a sound, or do whatever else the power-up is supposed to do. If the sprite is a bad guy, you can either kill the bad guy or kill the player. In this case, you kill the creature if the player falls or jumps on it, or, in other words, when the vertical movement of the player is increasing:

boolean canKill = (oldY < player.getY());


In all other cases, such as when the player just walks up and touches a bad guy, the player dies. In the future, you might want to add other ways to kill a bad guy, such as with an "invincible" power-up or by pushing a creature off a cliff.



   
Comments