Making a Space Invaders clone with PushButton – Explosions

Google+ Pinterest LinkedIn Tumblr +

PLAY THE DEMO

DOWNLOAD THE SOURCE CODE

RETURN TO THE TUTORIAL INDEX

Destroying an enemy in response to a collision with a players bullet may sound simple enough, but there is a bit of work that has to be done in the background to manage the health of the enemy, decrease that health in response to a collision with a bullet, and finally to display an explosion when the enemy has died.

The explosion, just like any other animated sprite, is defined in the XML files. Like the bullets it exists as a template, but is not added to any groups, so it is not loaded when the level is loaded. Because the explosion will not participate in collision detection or be moved around the screen there is little need to simulate it with the Box2D physics engine. So the spatial component will be a SimpleSpatialComponent, unlike the Box2DSpatialComponent used for the enemies, player and bullets. Apart from that the same rendering and animation components are added to the explosion entity.

Code

In addition a new component called DestroyAfterCountdownComponent is added. The purpose of this component is to destroy an entity after a certain period of time. In this case we want to destroy the explosion entity once the animation has completed.

Code

Pushbutton comes with a handy component called HealthComponent which can be used to maintain the health of an entity, cause damage to an entity by decreasing the health, and destroy the entity once all of its health is gone. This component is added to the enemy template, and we set the initial health of the enemy to 1 (it defaults to 100).

Code

When the health managed by the HealthComponent is reduced to zero the entity is destroyed by default, but it also dispatches an event to let other components know that the entity has died. The DeathHandlerComponent is used to watch for this event and add the a new instance of the explosion entity.

Code

The DamageOnContactConponent, which was introduced in the last tutorial, has been modified to find the health component of the entity that is has collided with and call its damage function, which will reduce the health of the other entity and eventually destroy it. The CollisionEvent which is passed to the collision event listener function has references to two Box2DSpatialComponent called collidee and collider. You can’t assume one or the other belongs to the same entity as the DamageOnContactConponent, so both have to be checked to find the Box2DSpatialComponent that belongs to the other entity involved in the collision.

public class DamageOnContactConponent extends EntityComponent
{
 public var damage:int = 0;
 
 public function DamageOnContactConponent()
 {
  super();
 }
 
 protected override function onAdd():void
 {
  super.onAdd();
  
  owner.eventDispatcher.addEventListener(CollisionEvent.COLLISION_EVENT, OnCollision);
 }
 
 protected override function onRemove():void
 {
  super.onRemove();
  
  owner.eventDispatcher.removeEventListener(CollisionEvent.COLLISION_EVENT, OnCollision);
 }
 
 private function OnCollision(event:CollisionEvent):void
 {
  var mySpatial:Box2DSpatialComponent = owner.lookupComponentByType(Box2DSpatialComponent) as Box2DSpatialComponent;
  var other:Box2DSpatialComponent = event.collidee === mySpatial?event.collider:event.collidee;   
  var otherHealth:HealthComponent = other.owner.lookupComponentByType(HealthComponent) as HealthComponent;
  if (otherHealth != null) otherHealth.damage(damage); 
    
  owner.destroy();   
 }
 
}

The new DeathHandlerComponent component sets the onDied function to be called in the event of a HealthComponent.DIED event. Inside the onDied a new instance of the Explosion template is created and positioned. Notice that we try and pull out a reference to both a Box2DSpatialComponent and SimpleSpatialComponent component in order to set the initial position of the explosion. Of course we know that the explosion entity contains a SimpleSpatialComponent (because that is what we game it in the XML file), but I left this code in to highlight one of the issues with the PushButton component design system, which is that components that perform the same function are quite often unrelated. In this case both the Box2DSpatialComponent and SimpleSpatialComponent provide a way to position an entity in space, the actual position variable is not specified as part of a common interface or base class, forcing us to check for the existence of both components when trying to set the initial position.

public class DeathHandlerComponent extends EntityComponent
{
 public var PositionReference:PropertyReference = null;
 
 public function DeathHandlerComponent()
 {
  super();
 }
 
 protected override function onAdd():void
 {
  super.onAdd();
  
  owner.eventDispatcher.addEventListener(HealthComponent.DIED, onDied);
 }
 
 protected override function onRemove():void
 {
  super.onRemove();
  owner.eventDispatcher.removeEventListener(HealthComponent.DIED, onDied);
 }
 
 protected function onDied(event:Event):void
 {
  var position:Point = owner.getProperty(PositionReference);
  var entity:IEntity = TemplateManager.instance.instantiateEntity("Explosion");
  if (entity != null)
  {
   var spatial:Box2DSpatialComponent = 
    entity.lookupComponentByType(Box2DSpatialComponent) as Box2DSpatialComponent;
   var simpleSpatial:SimpleSpatialComponent = 
    entity.lookupComponentByType(SimpleSpatialComponent) as SimpleSpatialComponent;
    
   if (spatial != null)
   {
    spatial.position = new Point(position.x, position.y);
   }
   else if (simpleSpatial != null)
   {
    simpleSpatial.position = new Point(position.x, position.y);
   }
   
   var anim:AnimationController =
    entity.lookupComponentByType(AnimationController) as AnimationController; 
    
   if (anim)
    anim.setAnimation(anim.animations[anim.defaultAnimation]);
  } 
 }
 
}

Finally the DestroyIfOffScreenComponent will count down a internal counter, destroying the entity to which is belongs once that counter has reached zero.

public class DestroyAfterCountdownComponent extends TickedComponent
{
 public var TimeToLive:Number = 1;
 protected var remainingTime:Number = 1;
 
 public function DestroyAfterCountdownComponent()
 {
  super();
 }
 
 protected override function onAdd():void  
 {
  super.onAdd();
  remainingTime = TimeToLive;
 }
 
 public override function onTick(tickRate:Number):void
 {
  remainingTime -= tickRate;
  if (remainingTime <= 0)
   owner.destroy();
 }
 
}

Share.

About Author

Leave A Reply