views:

511

answers:

5

I'm designing a simple game, which uses Java 2D and Newtonian physics. Currently my main "game loop" looks something like:

do {
  for (GameEntity entity : entities) {
    entity.update(gameContext);
  }

  for (Drawable drawable : drawables) {
    drawable.draw(graphics2d);
  }
} while (gameRunning);

When an entity is instructed to update itself it will adjust its velocity and position based on the current forces applied to it. However, I need entities to exhibit other behaviour; e.g. if a "bad guy" is shot by a player the entity should be destroyed and removed from the game world.

My question: What is the best way to achieve this in an object oriented manner? All examples I've seen so far incorporate the game loop into a God class called something like Game, which performs the steps: detect collisions, check-if-bad-guy-killed, check-if-player-killed, repaint, etc and encapsulates all the game state (lives remaining, etc). In other words, it's very procedural and all the logic is in the Game class. Can anyone recommend a better approach?

Here are the options I've thought of so far:

  • Pass a GameContext to each entity from which the entity can remove itself if required or update the game state (e.g. to "not running" if the player is killed).
  • Register each GameEntity as a listener to the central Game class and take an event oriented approach; e.g. a collision would result in a CollisionEvent being fired to the two participants in the collision.
+5  A: 

In one particular engine that I worked on, we decoupled the logic from the graphic representation and then had objects that would send messages for what they wanted to do. We did this so that we could have games exist on a local machine or networked and they were indistinguishable from each other from a code standpoint. (Command pattern)

We also had the actual physics modeling done in a separate object that could be changed on the fly. This let us easily mess with gravity, etc.

We made heavy use of the event driven code (listener pattern), and lots and lots of timers.

For example, we had a base class for an object that was intersectable that could listened to the collision event. We subclassed it into a health box. On collision, if it was hit by a player entity, it sent a Command to the collider that it should gain health, sent a message to broadcast a sound to all that could hear it, deactivated collisions, activated the animation to remove the graphics from the scene graph, and set a timer to reinstantiate itself later. It sounds complicated, but it really wasn't.

If I recall (and it's been 12 years), we had the abstract notion of scenes, so a game was a sequence of scenes. When a scene was finished, an event was fired that would typically send a command take down the current scene and start another one.

plinth
Thanks - Some good advice here.
Adamski
It would probably be a good idea to use the mediator pattern here to facilitate event invocation/registry/handling and data, otherwise you'd have to couple all the sub-systems and game entities to each other.
dvide
+2  A: 

I disagree that because you have a main Game class all the logic has to happen in that class.

Over-simplification here mimicking your example just to make my point:

mainloop:
  moveEntities()
  resolveCollisions()   [objects may "disappear"/explode here]
  drawEntities()        [drawing before or after cleanEntitites() ain't an issue, a dead entity won't draw itself]
  cleanDeadEntities()

Now you have a Bubble class:

Bubble implements Drawable {

handle( Needle needle ) {
    if ( needle collide with us ) {
        exploded = true;
    }
}

draw (...) {
   if (!exploded) {
      draw();
     }
  }
}

So, sure, there's a main loop that takes care of passing the messages around between the entities but the logic related to the collision between a Bubble and a Needle is definitely not in the main Game class.

I'm pretty sure that even in your case all the logic related to the movement is not happening in the main class.

So I disagree with your statement, that you written in bold, that "all the logic happens in the main class".

This is simply not correct.

As for good design: if you can easily provide another "view" of your game (like, say, a mini-map) and if you can easily code a "frame-for-frame perfect replayer", then your design is probably not that bad (that is: by recording only the inputs and the time at which they happened, you should be able to recreate the game exactly as it was played. That's how Age of Empires, Warcraft 3, etc. make their replay: it's only the user inputs and the time at which they happened that is recorded [that's also why the replay files are typically so tiny]).

Webinator
Thanks. I wasn't advocating eliminating the game loop altogether, just keeping it as simple as possible so what you've said makes sense.
Adamski
+1  A: 

This game was an experiment in keeping the model and view separate. It uses the observer pattern to notify the view(s) of changes in the game's state, but events would perhaps offer a richer context. Originally, the model was driven by keyboard input, but the separation made it easy to add timer-driven animation.

Addendum: You need to keep the game's model separate, but you can re-factor that model into as many classes as required.

trashgod
+2  A: 

I write my own engines (raw & dirty), but a pre-built engine that has a decent OO model, is Ogre. I'd recommend taking a look at it (it's object model / API). The node assignment is a tad funky, but it makes perfect sense the more you look at it. It's also extremely well documented with a ton of working game examples.

I've learned a couple tricks from it myself.

Pestilence
Great stuff - Will check it out.
Adamski
+8  A: 

I have worked closely with two commercial game engines and they follow a similar pattern:

  • Objects represent components or aspects of a game entity (like physical, renderable, whatever), rather than the whole entity. For each type of component there is a giant list of components, one for each entity instance that has the component.

  • The 'game entity' type itself is just a unique ID. Each giant list of components has a map to look up the component (if any exists) that corresponds to an entity ID.

  • If a component requires an update it is called by a service or system object. Each service is updated directly from the game loop. Alternatively, you could call services from a scheduler object which determines update order from a dependency graph.

Here are the advantages of this approach:

  • You can freely combine functionality without writing new classes for every combination or using complex inheritance trees.

  • There is almost no functionality that you can assume about all game entities that you could put in a game entity base class (what does a light have in common with a race car or a sky-box?)

  • The ID-to-component look-ups might seem expensive, but services are doing most of the intensive work by iterating through all the components of a particular type. In these cases, it works better to store all the data you need in a single tidy list.

Evan Rogers
@Evan: Thanks for your answer. I meant to ask you: How would you handle the removal of an entity in this instance? For example, suppose you have an aspect Collidable, and your CollisionManager detects a collision, which should cause the entity to be removed. Presumably the CollisionManager only references the Collidable aspect of the entity, and so what approach do you then take to remove **all** aspects of the entity (Drawable, Collidable, etc) from the various lists?
Adamski
The missing piece I didn't mention is messages or events. Each system can subscribe to any message type or post messages. The physics system might post a "Collide" message which a game play system might subscribe to, and may post a "delete entity" message in response. Another system responsible for creating and deleting entities might subscribe to the the delete entity message type. This is a lot more work than direct function calls, but it is all in the name of decoupling.
Evan Rogers