Threesome

27 Apr 2014

Three minor additions, that nevertheless are pretty fundamental to the game. None of them particularly spectacular by itself, so let’s just get to them ;-)

Planet bounding box

When creating a planet, a bounding box is now automatically calculated based on the width of the planet surface. The planet surface is extended with a straight surface edge segment as wide as the planet surface, on both sides. The bottom and top coordinates of the bounding box are calculated by adding/subtracting a constant height to/from the top/bottom surface coordinates.

The planet bounding box is used for two things at the moment. First of all it allows a saner infinite-wraparound implementation. Wraparound now occurs for any entity that crosses the halfway point of the extension edge at either end of the surface. When this happens, the entity is basically warped to the halfway point of the opposing extension edge by changing its position attribute, preserving its velocity, rotation, etc. Second, the top of the bounding box is used to detect and possibly kill and remove entities that ‘leave the planet atmosphere’, so to say. At some point in the future, this check will apply to the player ship, which should end the level by leaving the planet atmosphere while carrying the orb, but right now, it only has an effect on a new class of entities. Which brings us to the next new feature.

Projectiles

Projectiles are basically entities without behaviors, in other words, K14Entity instances without a step function. Projectiles originate at a certain entity (for example a turret) with a direction, velocity, and by default are not affected by gravity.

I added a base class K14Projectile that derives from K14Entity, and has an initializer that takes a source entity, a direction and a speed, along with the usual K14Entity initialization parameters (id, size, classname). The initializer will calculate the projectile spawn point, which will be right above the the source entity, and configure the underlying Box2D body for continuous collision detection. The initializer looks like this:

-(id) initWithPlanet: (K14Planet *) planet
                  id: (int) id
              entity: (K14Entity *) entity
           direction: (CGVector) direction
            velocity: (float) velocity
                size: (CGSize) size
           className: (NSString *) className
{
  self = [super initWithPlanet:planet id:id className:className size:size];
  
  if (self != nil)
  {
    // Make this a Box2D 'bullet body' to enable continuous collision detection. Also
    // make it a 'dynamic body' to make it movable in the first place, and by default
    // configure the projectile to ignore gravity;
    self.body->SetType(b2_dynamicBody);
    self.body->SetBullet(true);
    self.body->SetGravityScale(0.0f);

    // Set position, just above the bounding rectangle of the originating entity
    CGVector up_vector = CGVectorMake(cos(entity.angle + M_PI_2), sin(entity.angle + M_PI_2));
    float bullet_radius = sqrt(0.25f * size.width * size.width + 0.25f * size.height * size.height);

    self.position = CGPointMake(entity.position.x + (0.5f * entity.size.height + bullet_radius) * up_vector.dx,
                                entity.position.y + (0.5f * entity.size.height + bullet_radius) * up_vector.dy);
        
    // Set direction and velocity
    float n = sqrtf(direction.dx * direction.dx + direction.dy * direction.dy);

    self.velocity = CGVectorMake((direction.dx / n) * velocity, (direction.dy / n) * velocity);
  }

  return self
}

From the K14Projectile class, I derived a super-simple concrete projectile class called K14Bullet, which will be used for bullets fired by turrets. This class basically just sets up the K14Projectile class with an appropriate size, and adds a single circle-shaped Box2D fixture to the entity body. To represent circular fixtures I added a K14CircleShape class configured with a radius, which sits along the already exiting K14BoxShape class used for everything else up to now.

Projectiles are typically spawned by entities, such as turrets. To allow an entity to do so, I added an additional factory method createProjectileOriginatingFromEntity to K14Planet, which takes the entity, direction, velocity and class name as arguments. It instantiates the class type associated with the passed class name, and adds it to the game world.

To test the whole thing, I added a simple time counter to the K14Turret step method, which will fire a bullet every few seconds, in the direction the turret is facing. The K14Turret step method looks like this at the moment:

-(void) step: (NSTimeInterval) dt
{
  self.lastBulletFired += dt;
  
  if (self.lastBulletFired > 2.0f)
  { 
    CGVector direction = CGVectorMake(cos(self.angle + M_PI_2), sin(self.angle + M_PI_2));

    [self.planet createProjectileOriginatingFromEntity:self direction:direction velocity:8.5f className:@"K14Bullet"];
    
    self.lastBulletFired = 0.0f;
  }
  
  [super step:dt];
}

Obviously turret behavior will have to be a little more complex than this, the turret should only fire when it ‘sees’ the player, and it should fire in the player direction instead of always firing straight up. The current turret game logic is just a placeholder to test projectiles.

Collision detection

The last but definitely not least important feature I added was collision detection for entities. The actual collision logic is all inside Box2D. Some other people have already done a great job explaining Box2D collisions, so I won’t go into a lot of detail about the implementation here. I basically configured a Box2D contact listener, and stored a reference to the parent K14Entity with every Box2D body using a b2Body.userData pointer. Whenever a Box2D contact listener callback fires, I check the userData pointer of both bodies involved, which could be NULL for (at most) one of the bodies, in which case the collision is between an entity and the planet surface. If both bodies have a valid K14Entity associated with them, the collision is between 2 entities.

Based on the type of the collision (entity-surface) or (entity-entity) a call one of two new selectors: didCollideWithSurface and didCollideWithEntity:entity, which have an empty default implementation in K14Entity. Right now, the only entity type that reacts to collisions by overriding these selectors, is K14Bullet, which sets a new K14Entity flag called state with a value K14_DEAD on itself whenever it hits something. The K14Planet step method will inspect the state field of all active entities and remove the ‘dead’ ones in the next step call. Eventually, dead entities should trigger various game mechanics such as reducing the number of lives when the player ship is hit, checking game termination conditions, etc. but right now, collision detection and the entity state flag are only used to remove projectiles when they hit something.

Video time!

I made another video to show the new features in action. This simple plays the action replay I’ve been using so far, but adds the ‘shoot’ behavior to the turret. I tweaked the firing frequency until one of the bullets would hit the player ship, just to show that projectiles are nicely flagged as ‘dead’ and removed from the game world when they hit the player.

I also added some logging statements to the collision detection handlers of the K14Entity class to illustrate the callbacks triggered when the player ship bounces around the planet surface. This is what the console shows after a single replay:

2014-04-27 16:00:03.186 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:06.451 2k14: The Game[704:60b] PROJECTILE '4' LEFT ATMOSPHERE
2014-04-27 16:00:08.519 2k14: The Game[704:60b] PROJECTILE '5' LEFT ATMOSPHERE
2014-04-27 16:00:10.585 2k14: The Game[704:60b] PROJECTILE '6' LEFT ATMOSPHERE
2014-04-27 16:00:12.284 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:12.285 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:12.652 2k14: The Game[704:60b] PROJECTILE '7' LEFT ATMOSPHERE
2014-04-27 16:00:14.318 2k14: The Game[704:60b] ENTITY 'K14Turret' HIT ENTITY: 'K14Ship'
2014-04-27 16:00:14.319 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT ENTITY: 'K14Turret'
2014-04-27 16:00:14.719 2k14: The Game[704:60b] PROJECTILE '8' LEFT ATMOSPHERE
2014-04-27 16:00:15.084 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT ENTITY: 'K14Bullet'
2014-04-27 16:00:15.085 2k14: The Game[704:60b] PROJECTILE '9' DIED
2014-04-27 16:00:15.785 2k14: The Game[704:60b] ENTITY 'K14Reactor' HIT ENTITY: 'K14Ship'
2014-04-27 16:00:15.785 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT ENTITY: 'K14Reactor'
2014-04-27 16:00:16.218 2k14: The Game[704:60b] ENTITY 'K14Reactor' HIT ENTITY: 'K14Ship'
2014-04-27 16:00:16.218 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT ENTITY: 'K14Reactor'
2014-04-27 16:00:16.619 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:16.951 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:17.052 2k14: The Game[704:60b] ENTITY 'K14Ship' HIT THE SURFACE
2014-04-27 16:00:18.851 2k14: The Game[704:60b] PROJECTILE '10' LEFT ATMOSPHERE

Next steps

Even though progress is slow because of the limited amount of time spent on coding, with just the few basic mechanics that are implemented, the game is already starting to come together nicely. The next thing I want to implement is probably the most fundamental, and (compared to everything else) most elaborate gameplay mechanic: lifting and carrying the orb by activating the tractor beam when it is in range.

Development scoreboard

It took me about 4 hours to implement the three features I wrote about in this post, most of which was spent on the projectile features. The total development time is now about 27 hours. SLOC count is now 630 lines.