views:

204

answers:

3

I'm making a Java shoot em up game for Android phones. I've got 20 odd enemies in the game that each have a few unique behaviors but certain behaviors are reused by most of them. I need to model bullets, explosions, asteroids etc. and other things that all act a bit like enemies too. My current design favors composition over inheritance and represents game objects a bit like this:

// Generic game object
class Entity
{
  // Current position
  Vector2d position;

  // Regular frame updates behaviour
  Behaviour updateBehaviour;
  // Collision behaviour
  Behaviour collideBehaviour;

  // What the entity looks like
  Image image;
  // How to display the entity
  Renderer renderer;

  // If the entity is dead and should be deleted
  int dead;
}

abstract class Renderer { abstract void draw(Canvas c); }

abstract class Behaviour { abstract void update(Entity e); }

To just draw whatever is stored as the entity image, you can attach a simple renderer e.g.

class SimpleRenderer extends Renderer
{
  void draw(Canvas c)
  {
    // just draw the image
  }
}

To make the entity fly about randomly each frame, just attach a behavior like this:

class RandomlyMoveBehaviour extends Behaviour
{
  void update(Entity e)
  {
    // Add random direction vector to e.position
  }
}

Or add more complex behaviour like waiting until the player is close before homing in:

class SleepAndHomeBehaviour extends Behaviour
{
  Entity target;
  boolean homing;

  void init(Entity t) { target = t; }

  void update(Entity e)
  {
    if (/* distance between t and e < 50 pixels */)
    {
      homing = true;
      // move towards t...
    }
    else
    {
      homing = false;
    }
  }
}

I'm really happy with this design so far. It's nice and flexible in that you can e.g. modularize the latter class so you could supply the "sleep" behavior and the "awake" behavior so you could say something like new WaitUntilCloseBehaviour(player, 50/pixels/, new MoveRandomlyBehaviour(), new HomingBehaviour()). This makes it really easy to make new enemies.

The only part that's bothering me is how the behaviors and the renderers communicate. At the moment, Entity contains an Image object that a Behaviour could modify if it chose to do so. For example, one behavior could change the object between a sleep and awake image and the renderer would just draw the image. I'm not sure how this is going to scale though e.g.:

  • What about a turret-like enemy that faces a certain direction? I guess I could add a rotation field to Entity that Behavior and Renderer can both modify/read.

    • What about a tank where the body of the tank and the gun of the tank have separate directions? Now the renderer needs access to two rotations from somewhere and the two images to use. You don't really want to bloat the Entity class with this if there is only one tank.

    • What about an enemy that glows as his gun recharges? You'd really want to store the recharge time in the Behaviour object, but then the Renderer class cannot see it.

I'm having trouble thinking of ways to model the above so the renderers and the behaviors can be kept somewhat separate. The best approach I can think of is to have the behavior objects contain the extra state and the renderer object then the behavior objects call the renderers draw method and pass on the extra state (e.g. rotation) if they want to.

You could then e.g. have a tank-like Behaviour object that wants a tank-like Renderer where the latter asks for the two images and two rotations to draw with. If you wanted your tank to just be a plain image, you would just write a subclass Renderer that ignored the rotations.

Can anyone think of any alternatives? I really want simplicity. As it's a game, efficiency may be a concern as well if e.g. drawing a single 5x5 enemy image, when I have 50 enemies flying around at 60fps, involves many layers of function calls.

+1  A: 

I think my answer to a recent similar question is applicable:

http://stackoverflow.com/questions/2231527/game-design-in-an-oo-manner/2238470#2238470

Evan Rogers
A: 

The composition design is a valid one, as it allow to mix-and-match the behaviour(s) and render.

In the game we're toying with, we've added a "databag" that contains basic informations (in your case the position and the dead/alive status), and variables datas that are set/unset by the behaviour and collision subsytem. The renderer can then use these data (or not if not needed). This work well, and allows for neat effect, such as setting a "target" for a given graphical effect.

A few problems :

  • if the Renderer ask for data that the behaviour did not set. In our case, the event is logged, and default values (defined in renderer) is used.
  • It's a little bit harder to check for the needed informations beforehand (ie what data should be in the databag for the Renderer A ? what data are set by the Behaviour B ?). We try to keep the doc up-to-date, but we're thinking of recording the set/get by the classes, and generate a doc page...

Currently we're using a HashMap for the databag, but this is on a PC, not an IPhone. I don't know if perfomance will be enough, in which case another struct could be better.

Also in our case, we've decided for a set of specialized renderer. For example, if the entity possess a non void shield data, the ShieldRenderer display the representation... In your case, the tank could possess two renderer linked to two (initialization-defined) datas :

Renderer renderer1 = new RotatedImage("Tank.png", "TankRotation");
Renderer enderer2 = new RotatedImage("Turret.png", "TurretRotation");

with "TankRotation" and "TurretRotation" set by the behaviour. and the renderer simply rotating the image before displaying it at the position.

  image.rotate (entity.databag.getData(variable));

Hope this help

Regards
Guillaume

PATRY
Thanks. I haven't profiled it, but if I want a lot of objects flying around (e.g. bullets), I would probably want to avoid the use of hashmaps for updating every behaviour on android. One advantage I've noticed to keeping variable storage local to behaviour objects is it makes the system much more robust and easier to test as you don't have to worry about combined behaviours messing with the same variables.
BobbyJim
agreed. As i said, we're on PC, and there's not too much of objects. Also, it's for fun :)
PATRY
A: 

The design you're going with looks good to me. This chapter on components may help you.

munificent
Thanks. I've implemented the approach above now and it's brilliant! One thing I never see people suggesting is sequencing behaviours (e.g. a behaviour that calls two behaviours in sequence) and conditional behaviours (e.g. one behaviour conditionally calls another behaviour). It feels like I've got a little scripting language now and it's very light weight. For example, by using a combination of objects I can say things like "if (you are 300 pixels away from the player) (home in on the player) otherwise (shoot at player)". I can code up new e.g. enemies in the game in a matter of minutes now.
BobbyJim
That's very cool. You basically do have a scripting language: that's a textbook [interpreter pattern](http://en.wikipedia.org/wiki/Interpreter_pattern).
munificent