This is part 4 of an 8 part series of writing a finished flash game using Flex and ActionScript 3. Here I will discuss precisely what was mentioned in step 4 for our Current Goals in completing this project, and in great detail discuss how I got there. Upon completing this tutorial you will have learned how to make an abstract enemy class, all enemies (that are not bosses) that will extend from that class, a bullet class for the player to use to shoot the enemies and the collision of the bullet to the player. We will discuss why it is important to have an abstract enemy class ( a base class for all enemies) on top of having the base class for all entities, but it might not make sense until we discuss the artificial intelligence for the game in the next tutorial.
This is actually going to be our shortest tutorial, and I'm sure after the last tutorial this is actually something you want to hear. In fact, it is because of our abstract code that we want to reuse, and the magical programming feature of polymorphism that let's us save a lot of time when we have to make many different game entities that just happens to share similarities with other game entities, which in turn let's us organize these similarities and make a base class that all entities extend, so we don't have rewrite code. So without further a due let's start coding.
The Abstract Enemy Class
Below is the base class for all entities. The initEnemies will pass a specific graphic to the abstract class AbstractEntity that we made in our last tutorial, and create an entity of that specific kind. This class actually hasn't changed at all from the AbstractEntity class except we have our own initEnemies function we have our enemies that extend AbstractEntity to utilize gracefully.
package
{
import flash.geom.Point;
public class AbstractEnemy extends AbstractEntity
{
//constructor
public function AbstractEnemy()
{
super();
}
//initialize the enemies
public function initEnemies(graphics:GraphicsResource):void
{
super.init(graphics, new Point(Math.random()*Constants.width, Math.random()*Constants.height));
}
//shutdown the enemy entity
override public function shutdown():void
{
super.shutdown();
}
//update the enemies.
override public function update(seconds:Number):void
{
super.update(seconds);
}
}
}
initEnemies() - Each enemy will call this as the initiate, and will pass to the a unique graphic, which then in turn this base class will make them go to a random point on the screen within the width and height of the application.
shutdown() update() - These are the same as the abstract class that it extends, and it is left blank for now, but when we get real dirty with each enemy moving in a specific formation, and creating what is a Galaga we'll write that specific code in this update function, and of course add to this class as we need to, but for now to just get enemies on the screen at a random spot and shoot them this is all that is needed.
So this abstract class may not look much, and at first glance you probably don't see the reason to have an abstract class that extends the abstract class of AbstractEntity but I promise you will in the next tutorial, so for now let's just understand that it will be needed, and change form that of AbstractEntity, but for now it's all we need.
Now I didn't know what specific names each of the enemies were, so I called them respectively by either what insect I could clearly define them as, or by the color of the bug.
So we now we can look at our enemies.
Our Enemies
I played an arcade version of this game already, and from what I gathered there were 4 main non-boss enemies, which were bees, a smaller bug, and then a green and blue bug, and well those are the names that I gave them, and I started with the bees first, so let's look at the Bee's class, and then just list the rest, since they are all exactly the same for this lesson.
Killer Bees
package
{
public class Bees extends AbstractEnemy
{
public function Bees()
{
super();
}
//initiate the Bee's
public function initBee():void
{
super.initEnemies(ResourceManager.BeeGraphics);
}
//update the bee's. later this is where the AI will go.
override public function update(seconds:Number):void
{
super.update(seconds);
}
//shutodwn the bee
override public function shutdown():void
{
super.shutdown();
}
}
}
Small Bugs
package
{
public class SmallBug extends AbstractEnemy
{
public function SmallBug()
{
super();
}
//initiate the Bee's
public function initSmallBug():void
{
super.initEnemies(ResourceManager.BugGraphics);
}
//update the bee's. later this is where the AI will go.
override public function update(seconds:Number):void
{
super.update(seconds);
}
//shutodwn the bee
override public function shutdown():void
{
super.shutdown();
}
}
}
Blue Bugs
package
{
public class BlueBug extends AbstractEnemy
{
public function BlueBug()
{
super();
}
//initiate the Bee's
public function initBlueBug():void
{
super.initEnemies(ResourceManager.BlueBugGraphics);
}
//update the bee's. later this is where the AI will go.
override public function update(seconds:Number):void
{
super.update(seconds);
}
//shutodwn the bee
override public function shutdown():void
{
super.shutdown();
}
}
}
and finally
The Green Bugs
package
{
public class GreenBug extends AbstractEnemy
{
public function GreenBug()
{
super();
}
//initiate the Bee's
public function initGreenBug():void
{
super.initEnemies(ResourceManager.GreenBugGraphics);
}
//update the green bug. later this is where the AI will go. Leave this blank for collision detection.
override public function update(seconds:Number):void
{
super.update(seconds);
}
//shutodwn the bee
override public function shutdown():void
{
super.shutdown();
}
}
}
Now if you have kept up with these tutorials that I have written, and you're sweating over there's a lot of classes, and how am I going to instantiate all of these then... Calm down! All of the classes for now are precisely the same and just like the player class (except no keyDown event), and their names, and specific Graphic they pass to the AbstractEnemy, which passes that graphic to the AbstractEntity are different, but besides the class name, and the specific graphic they pass they're all the same.
They call the initEnemies with their specific initNameofClass function, and pass to that a graphic, and then in turn the AbstractEnemy does the first global function that it will do only to all enemies and that is place them randomly on the screen. (This will be changed when we work on the AI in the next tutorial), and then that's it.
So without the names, and the specific graphics resource we give that specific instance it's no different than the player class.
now for the bullet class.
The Bullet Class
Since the bullet class is not an enemy, but just another entity like the player then we can still just use the AbstractEntity class, and extend it to save time and lines of code. Doing so we get the following class below;
package
{
import flash.geom.Point;
public class Bullet extends AbstractEntity
{
public function Bullet()
{
super();
}
//initialize the bullet
public function initBullet(position:Point):void
{
super.init(ResourceManager.BulletGraphics, position, 0);
}
//shutodwn the bullet
override public function shutdown():void
{
super.shutdown();
}
//update the bullet if we collide with an entity delete this bullet, and that specific entity
override public function update(seconds:Number):void
{
position.y -= 10;
if(position.y < 0) shutdown();
if(super.Collided(this, EntityManager.Instance.greenBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.greenBugs);
}
else if(super.Collided(this, EntityManager.Instance.blueBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.blueBugs);
}
else if(super.Collided(this, EntityManager.Instance.smallBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.smallBugs);
}
else if(super.Collided(this, EntityManager.Instance.bees))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.bees);
}
super.update(seconds);
}
}
}
The following code above is precisely the same as the player class, and I'm hoping you guys and girls can see a pattern now, and see why having abstract classes are not only important but are a good thing! Saves me time!
Now the only code we're really going to have to discuss here is the update function that we override from the AbstractEntity class.
We check for a collision and if we have a collision then we must remove the 2 that has collided, which will always be the bullet entity, and the entity that checks the collision on.
We use the removeEntity() function that we made in the EntityManager class, and now I'm sure you can realize why we named it the EntityManager class... ha ha... let's continue....
//update the bullet if we collide with an entity delete this bullet, and that specific entity
override public function update(seconds:Number):void
{
position.y -= 10;
if(position.y < 0) shutdown();
if(super.Collided(this, EntityManager.Instance.greenBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.greenBugs);
}
else if(super.Collided(this, EntityManager.Instance.blueBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.blueBugs);
}
else if(super.Collided(this, EntityManager.Instance.smallBugs))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.smallBugs);
}
else if(super.Collided(this, EntityManager.Instance.bees))
{
EntityManager.Instance.removeEntity(EntityManager.Instance.bullet);
EntityManager.Instance.removeEntity(EntityManager.Instance.bees);
}
super.update(seconds);
}
Woo! What is this...
super.Collided(this, Entity.Instance.blueBugs)
It's simply a class we added in the AbstractEntity class, and we simply pass it this specific entity the bullet entity with all 4 entities of the enemies. Now why do we do it here in the bullet class? Well, I have this huge crunch over saving time, money, and lines of code and realizing that we only need to check for collision with bullet and all enemies, and the player and all enemies instead of having a global class to check for a possible collision of an entity that doesn't exist we do it here only when it does exist. Does that make sense?
OK, when the bullet is instantiated it then goes through this cycle of update each time we redraw the screen, but when it no longer exist not only are we checking on a null entity that doesn't exist for a collision if we have this in another global class, but it's not needed, and possibly we can create an error, so what do we do?
We check to see when the collision check is needed. We then put the collision check function in a class that all entities will be using Hint: the AbstractEntity class and then we only check for the collision when we the bullet exist.
Now for the quick add of this collision detection that is in the AbstractEntity class.
Updated AbstractEntity Class
The only code we add is the function that checks for the collision, which is below.
//check for a collision between 2 entities. Let the specific entities check this only when they're not null
public function Collided(entityA:AbstractEntity, entityB:AbstractEntity):Boolean
{
var leftA:int = entityA.position.x;
var leftB:int = entityB.position.x;
var rightA:int = entityA.position.x + entityA.graphics.bitmap.rect.width;
var rightB:int = entityB.position.x + entityB.graphics.bitmap.rect.width;
var topA:int = entityA.position.y;
var topB:int = entityB.position.y;
var bottomA:int = entityA.position.y + entityA.graphics.bitmap.rect.height;
var bottomB:int = entityB.position.y + entityB.graphics.bitmap.rect.height;
if( (bottomA <= topB ) || ( topA >= bottomB ) || ( rightA <= leftB ) ||( leftA >= rightB )) return false;
else
return true;
}
The code above simply gets the precise points and sizes of 2 entities that is dealing with. We put these values in variables that describe what they are pertaining to a collision with that specific entities graphic. (i.e. why it's labeled leftA, leftB, ... bottomA, and bottomB respectively).
Then instead of checking for a collision, let's just check when there's not a collision that is when we know a collision can't possibly occur. if any of these return then we return false, for we are not colliding, else we can safely assume that a collision has been made, and we must clean up accordingly.
Now that we have all enemies, and bullets. Let's create this instances.
Instantiating Enemies
First we declare the variables
//we create a variable like we did with the player. public var player:Player; public var bees:Bees; public var smallBugs:SmallBug; public var greenBugs:GreenBug; public var blueBugs:BlueBug; public var bullet:Bullet;
and then we create an instance of each class.
//we create the instance of all enemies where we created the instance of the player player = new Player(); bees = new Bees(); smallBugs = new SmallBug(); greenBugs = new GreenBug(); blueBugs = new BlueBug(); bullet = new Bullet();
and then we initialize those entities that are needed soon as the game start. Note: we do not initialize the bullet. No need for it.
//we then initialize all enemies when we initialize the player player.initPlayer(); bees.initBee(); smallBugs.initSmallBug(); greenBugs.initGreenBug(); blueBugs.initBlueBug();
NOTICE: We don't initialize the bullet in this class. That's because it's not needed, for it's only initialized when we need it, which is when the player hit's the spacebar key
now let's add the final piece for this tutorial that will bring this project to life. We must now edit the player class just a little, and create an instance of the bullet when we hit the spacebar key.
Edit Player Class
//give the player some movement
public function keyHandler(event:KeyboardEvent):void
{
if(event.keyCode == 37) position.x -= 20;
if(event.keyCode == 39) position.x += 20;
if(event.keyCode == 32)
{
EntityManager.Instance.bullet = new Bullet();
EntityManager.Instance.bullet.initBullet(new Point(position.x, position.y - graphics.bitmap.rect.height));
}
}
When we hit the keycode of 32, which is the spacebar key we create an instance of bullet, and then we initialize it by passing it a position which is equivalent to our players specific position at that instance.
OK, we're done!
See what you have created now!

Conclusion
We have created the base class of all enemies, all enemies that are not bosses, put them on a random point on the screen, created the bullet class, and once the player hits the spacebar key we initialize that bullet and as it's existing and updating we check for a collision between that bullet and all enemies. If we collide with that bullet and any of the enemies we then remove those 2 specific entities, which collided. Well that is it for now I'm afraid, but no worries I can assure you that the next tutorial will be at least 2x as long as the previous tutorial, for we'll be working on special algorithms, and level ups, so be sure to tune in for the next tutorial in this series, but until then...
Happy Coding!
Source Code
Source Code





MultiQuote





|