Saturday, January 24, 2015

Anansi: Collisions


Now it's time to hit something with hour bullets and missiles. There are 2 approaches for a collision detection:

  • hit zone
  • per pixel

The per pixel collision requires performance, so we choose the hit zone. It's plain and simple: We consider each sprite a rectangle and check if its bounds overlap with the bounds of another.

Checking the bounds of a rectangle is a very fast hit detection mechanism.It has a disadvantage though. We don't have a rectangle, we have an image with transparent areas. A bullet shouldn't hit the transparent area, it should hit the object. We can overcome this by not using the entire sprite as hit zone, but some areas inside of it. Examples: The missile hits only with the missile head. A Tormentor sprite is only hit at the ship's hull, but not at the legs of the ship. These aren't hard constraints that change the way the gameplay feels. In fact this is used throughout many Shoot'em'ups. Especially in bullet hell shooters where the player's ship often has only a single pixel hit zone.

Let's get coding. What we need is just another loop and a mechanism to check the overlapping of rectangles. For now it's sufficient if we use the entire sprite's rectangular area has hit zone. We'll introduce custom hit zones later.

We put this method into the SpriteBase class:
public boolean collidesWith( SpriteBase otherSprite) {
 
 return ( otherSprite.x + otherSprite.w >= x && otherSprite.y + otherSprite.h >= y && otherSprite.x <= x + w && otherSprite.y <= y + h);
 
}

Now we can easily find out if a bullet hits an enemy ship. We simply check each bullet with each enemy and perform an action if they collide. We extend the SpriteBase class with some more meaningful methods:
/**
 * Reduce health by the amount of damage that the given sprite can inflict
 * @param sprite
 */
public void getDamagedBy( SpriteBase sprite) {
 health -= sprite.getDamage();
}

/**
 * Set health to 0
 */
public void kill() {
 setHealth( 0);
}

/**
 * Set flag that the sprite can be removed from the UI.
 */
public void remove() {
 setRemovable(true);
}

And check for the collision of bullets:
private void checkBulletCollisions() {
 
 for( PlayerBullet bullet: playerBulletList) {
  for( Enemy enemy: enemyList) {
   if( bullet.collidesWith(enemy)) {
    
    // inflict damage, reduce enemy's health
    enemy.getDamagedBy(bullet);
    
    // destroy bullet, set health to 0
    bullet.kill();
    
    // remove the bullet from screen by flagging it as removable, 
    bullet.remove();
    
   }
  }
 }
}

The check for the missiles is very similar:
private void checkMissileCollisions() {
 
 for( Missile missile: playerMissileList) {
  for( Enemy enemy: enemyList) {
   if( missile.collidesWith(enemy)) {
    
    // inflict damage, reduce enemy's health
    enemy.getDamagedBy(missile);
    
    // destroy bullet, set health to 0
    missile.kill();
    
    // remove the bullet from screen by flagging it as removable, 
    missile.remove();
    
   }
  }
 }
}

Then we use the collision check methods in the game loop:
// check collisions
// ---------------------------
checkBulletCollisions();
checkMissileCollisions();

That's it. Quite easy to do. Here's a screenshot which shows that the bullets hit something and get removed. I increased the bullet counter a bit to show the effect better.


No comments:

Post a Comment