PushButton Tutorial Series – Enemies

Google+ Pinterest LinkedIn Tumblr +

PLAY THE DEMO

DOWNLOAD THE SOURCE CODE

RETURN TO THE TUTORIAL INDEX

For the purposes of this demo, an enemy is just a cut down version of the player entity. It will contain the same rendering, animation and spatial components that let it an animated sprite be drawn to the screen and participate in collision detection. The enemy will not move yet, but in truth most enemies in a platform game aren’t going to be much more complicated what we have here anyway: generally they will move around with a few very limited behaviours like walking along a platform or flying through the air. What we want to do is get a static enemy into the game and have the player react to it.

Creating the enemy entity is done in the EntityFactory class just like the scene, player and platforms. All the code to create the Box2D spatial component, renderer component and animation components are taken almost line for line from the creation of the player entity.

static public function createEnemy(name:String, scene:String, position:Point):IEntity
{
 var entity:IEntity = allocateEntity();                                 
 entity.initialize(name);                                             
 
 var spatial:Box2DSpatialComponent = new Box2DSpatialComponent();
    
 var collisionShape:CircleCollisionShape = new CircleCollisionShape();
 collisionShape.radius = 0.5;
 
 spatial.collisionShapes = new Array();
 spatial.collisionShapes.push(collisionShape);
 
 spatial.collisionType = new ObjectType("Enemy", "Renderable");
 spatial.collidesWithTypes = new ObjectType("Player");
 spatial.position = position;
 spatial.canSleep = false;
 spatial.canRotate = false;
 spatial.canMove = false;
 spatial.size = new Point(26, 20);
 
 spatial.manager = NameManager.instance.lookupComponentByName(
  scene, SCENE_BOX2DMANAGER_COMPONENT) as Box2DManagerComponent;    
 
 entity.addComponent(spatial, "Spatial");              
    
 var Render:SpriteRenderComponent = new SpriteRenderComponent();                                                                            
 Render.positionReference = new PropertyReference("@Spatial.position");  
 entity.addComponent( Render, "Render" );
 
 var Animation:AnimationController = new AnimationController();
 Animation.spriteSheetReference = new PropertyReference("@Render.spriteSheet"); 
 Animation.currentFrameReference = new PropertyReference("@Render.spriteIndex");
 Animation.defaultAnimation = "Idle";
 
 var IdleSpriteSheet:SpriteSheetComponent = new SpriteSheetComponent();
 IdleSpriteSheet.imageFilename = "../media/enemy.png";
 var divider1:CellCountDivider = new CellCountDivider();
 divider1.xCount = 2;
 IdleSpriteSheet.divider = divider1;

The only thing worth noting is that the enemy sprite sheet has only 2 frames of animation. By default each frame of animation can be displayed on the screen for 50 milliseconds, making the default animation rate 20 frames per second. To allow a slower frame rate we need to assign a value to the AnimationControllerInfo maxFrameDelay variable. Since we want to display only 4 frames per second, this value needs to be 1000/4 = 250 milliseconds.

 var IdleAnimation:AnimationControllerInfo = new AnimationControllerInfo();
 IdleAnimation.frameRate = 4;
 IdleAnimation.maxFrameDelay = 250;
 IdleAnimation.loop = true;
 IdleAnimation.spriteSheet = IdleSpriteSheet;
 Animation.animations["Idle"] = IdleAnimation;
 
 entity.addComponent(Animation, "Animation");  
 
 return entity;
}

When the player collides with an enemy we want it to bounce off. Just like we did with the platforms, the KeyboardController component will watch for a collision with an enemy in the OnCollision function. When a collision is detected the recoilVector variable is set to the normal of the collision.

private function OnCollision(event:CollisionEvent):void
{
 if (ObjectTypeManager.instance.doesTypeOverlap(event.collidee.collisionType, "Platform"))
 {
  if (event.normal.y > 0.7)
   onGround++;
 }
 else if (ObjectTypeManager.instance.doesTypeOverlap(event.collidee.collisionType, "Enemy"))
 {
  recoilVector = event.normal;
 }
}

In the onTick function we check to see if the recoilVector variable has been set to something. If it has we play a sound effect to show that the player has been hurt, and the velocity of the player is set to a small amount along the negative of the recoilVector, which will bounce the player away from the enemy.

public override function onTick(tickRate:Number):void
{
 // ...
 
 if (recoilVector != null)
 {
  if (hurtSound != null) hurtSound.play();
  velocity.x -= recoilVector.x * RecoilSpeed;
  velocity.y -= recoilVector.y * RecoilSpeed;
  recoilVector = null;
  recoilDelay = RecoilDelay;
 }

The recoilDelay variable is then counted down to zero. The purpose of this is to stop the player from being controlled with the keyboard for a short duration of time. If we didn’t do this the player could bounce off an enemy with the recoilVector, but then move straight back into the enemy thanks to the velocity applied in response to keyboard input, defeating the purpose.

 if (recoilDelay > 0)
 {
  recoilDelay -= tickRate;
  recoilDelay = recoilDelay<0?0:recoilDelay;
 }
 else
 {   
  // respond to keyboard input 
 }
   
 // ...
}

Share.

About Author

Leave A Reply