Sunday, January 29, 2012

Enemies

I haven't posted about game mechanics in a bit, so I guess I'll talk about how I implemented enemies! This will also contain a modification I made to my asset system, as well as another one that I would like to make in the future. So here we go!

An instance of the Enemy class is created for almost everything in the game that has the potential to kill the player. The exceptions to this are doors and light gates (implemented with another class called Static) and the spikes that line the edges of some levels.

Now in order to create a single Enemy class that can represent any type of enemy, I had to use a lot of switch statements. For the non programmers out there, a switch statement lets you define several different cases and execute a different block of code depending on which case fits. For my purposes, I could check which type of enemy a particular instance of the Enemy class is and make sure it behaves correctly.


//Conditional x collision depending on enemy type
    if(type==SLIME){
     if(assets.getBg().checkCollisionX(collision, yPos) || assets.getBg().checkGap(collision, yPos+spriteHeight)){
      collided=true;
     }
    }else if(type==STINGER){
     if(assets.getBg().checkCollisionX(collision, yPos) || assets.getBg().checkCollisionX(collision, yPos+spriteHeight-assets.getBg().tileSize) || (xPos<=xBoundL && xSpeed<0) || (xPos>=xBoundR&&xSpeed>0)){
      collided=true;
     }
I again use a switch statement to define different hitboxes depending on the type of enemy
public Rect getLoc(){
  if(type==STINGER){
   return new Rect(xPos+40,yPos+40,xPos+spriteWidth-20,yPos+spriteHeight-25);
  }else if(type==LIGHT_TRAP){
   return new Rect(xPos,yPos+15,xPos+spriteWidth,yPos+spriteHeight);
  }else{
   return new Rect(xPos,yPos,xPos+spriteWidth,yPos+spriteHeight);
  }
 }


This setup makes it very simple for me to add new enemies in. I simply need to add cases defining it's hitbox and movement pattern and I'm set!

Another consideration I  needed to make was how I manage the bitmaps containing spritesheets for these enemies. My original implementation had all of the enemy sprite sheets aliased correctly such that each sheet was loaded once per type of enemy. However, whenever an enemy switched directions or was flipped from on to off, the change was handled within the enemy class. This meant that eventually, every enemy instance would have it's own sprite sheet. Terribly inefficient, and unavoidable as long as sprite sheet changes are handled by the enemy itself.

To fix this, I modified my Assets structure (yet again) to contain a bitmap manager for the Enemy class. A data structure was built that stores two sprite sheets per enemy type, labeled by the enemy's type name. The reason I stored two per enemy is that if we have some slimes facing left and some facing right, they can't be read off of the same sheet! Whenever an Enemy is created, it checks the data structure to see if it's bitmaps are loaded in and loads them if needed. When a switch is flipped, the two bitmaps for that enemy are overwritten with the new versions. This way, I can have 2 slimes or 200 slimes in a level with virtually the same memory footprint!

In the future, I'd like to move ALL bitmap allocations into the Assets class. Right now the Player sets up its own bitmaps, the Background sets up a bitmap, etc. A much more elegant and less bug-prone setup would be for the Assets instance created for a level to have a single data structure with all of the bitmaps used for the level. It can then pass every single asset a reference to it's bitmaps, and requires much less work when I want to clean out all of the bitmaps before loading a new level.

No comments:

Post a Comment