Hi, you’ve probably come here from some of the sites that link to this article. This post is an old one, though, and I’ve written a more up-to-date post called State of the Art Game Objects that you probably want to check out that has a lot more research links and info.
I’ve mentioned Object-Oriented Game Design by Britt L. Hannah before, but I wanted to write a bit more about it.
The article is not named very well. Game Design and Software Engineering are two different things entirely. The article isn’t actually about object-oriented game design, whatever that means, so much as object-oriented software development for games. It doesn’t make the information any less valid, however.
It basically discusses a component-based design for game objects. In a recent issue of Game Developer magazine, Mick West wrote “Evolve Your Hierarchy” which gives an overview of the topic. Some references listed in the article:
- Game Object Structure: Inheritance vs Aggregation
- A Data Driven Game Object System is a presentation by Scott Bilas for GDC 2002.
- “Component Based Object Management” is in the book Game Programming Gems 5.
To summarize, there is a tendency to use object-oriented languages to create deep hiearchies. Scott Bila’s slides #7 and #8 show how inflexible and unwieldy these hierarchies can be. So if you can’t just have objects inherit from Drawable, Collidable, Shootable, or similar abstractions, what can you do?
You give an entity states in the form of objects. But rather than give a class private members to hold state like you usually would, you create a separate class for each state you would like to store. So instead of the following:
class Ship
{
int hitPoints;
string name;
}
you would do:
class Ship
{
State hitPoints;
State name;
}
What’s the difference? What happens if you need a new type of ship? Or an asteroid? Or a base? Or an alien? It is conceivable that you might have different types of entities that need to track the state of their hit points or names. It is also conceivable that those entities might not need to inherit the behaviors of a ship. So the states are placed into their own objects and assigned to Entity objects. You don’t really need to create a Ship class since a Ship is really nothing more than an entity that has the states that belong to a Ship.
Now the part that was a real eye-opener to me. It is very intuitive to create classes for things we think of as objects. In computer science class, we’re taught that classes have state and functions to manipulate that state. A class is created for a noun, and the functions in the class are the verbs.
Well, it turns out that the verbs can be encapsulated in classes. If we use the first example of a Ship above, actions would be functions defined in Ship:
class Ship
{
void setName( string);
string getName( );
void setHitPoints( int );
void adjustHitPoints( int );
int getHitPoints( );
}
Each time you add some state to a class, you need to add functionality to access such state. It can get really messy, really fast.
If you separate State into its own classes, however, then you can create Action objects to interact between entities. In the second Ship example, you can create an Action called AdjustHitPoints:
class Action
{
void doAction( ) = 0;
}
class AdjustHitPoints : public Action
{
void doAction() { entity.hasState( HIT_POINTS)->hp += amount; }
}
An Entity needs some way for the Action objects to grab state, so hasState() fills that role. Action objects have a function called doAction() that manipuates the states from an Entity.
Can you see how powerful this design is? Instead of hard-coding state into entity classes, you can construct entities at run-time. Instead of giving individual entities the methods to manipulate the state, you separate the events into their own classes. You can add a bunch of Actions to an Entity’s queue. The Entity can then pop the Actions off one-by-one and run doAction(). You don’t call adjustHitPoints(). You just activate the AdjustHitPoints Action object for the entity.
Normally if you have an abstract class called Human, you might derive Man and Woman classes from it. Let’s say you have a pointer to a Human, human, and it points to a PoliceOfficer object. You can’t say human->catchCriminal() because a Human doesn’t have the functionality of a PoliceOfficer. It is sometimes difficult and/or dangerous to dynamic_cast to the proper object type, so it seems overly difficult to get a PoliceOfficer to catch a crook since you don’t know who the PoliceOfficer is. If you change the code so that you know who the PoliceOfficer is, what was the point of needing to use a pointer to Human? Or inheritance, for that matter?
However, if you use the separate components to handle state, you can say human->activateAction( CATCH_CRIMINAL ). If it isn’t a PoliceOfficer, then it won’t have that Action. Nothing happens, just as we would expect. A PoliceOfficer, on the other hand, will have that Action object in its repertoire, and so the CatchCriminal Action will be activated. Eventually some code will run when the PoliceOfficer object updates that will look something like:
action->doActions();
Even better than the above example is that you could create a different type of Human-derived object: a Deputy. A Deputy isn’t a PoliceOfficer, but it should also have the ability to catch criminals. There’s no need to duplicate code. You just give it its own instance of the Action.
Separating state into components and encapsulating events into their own objects allows for more flexibility in your game code. I’ve already found that this design was both easy to implement and fun to use. I have been writing a text-based board game, and I was surprised with how easy it was to construct entities. I sometimes find myself writing code that resembles the deep game entity hierarchy, but whenever I do it is a source of pain. Refactoring the code so that it resembles the component-based model has always made it easier to work with.