Let the fighting commence!
Another interesting problem that I run into in Jamie Farser’s blog, he is building a game and run into a problem designing the object model for it. The problem can be sum up pretty easily in two pictures:
And here is what Jamie has to say about it:
Each interface defines several methods or properties (for example, Attack or AttackRanged). However this feels a bit wrong to me - I'd ideally like just a single Attack method, which would then do something to determine whether the unit was capable of ranged attack
There is another issue here, the object model as it is structured now is fixed. But in most games, units gain additional capabilities as they mature. For example, a spearman may start out as a simple hand to hand unit but be upgraded to a javelin thrower.
This calls for a state pattern, because the state that a unit may be is going to changed over time. More over, I don’t like the idea that we have things like Attack or AttackRanged, it seems to me that the game engine knows way too much about the way that units are operating. I would much rather have something like ExecuteTurn, instead.
That would move the game engine into a simple command executer, and the code inside a a unit to a simple forward to the appropriate state. It seems to be simpler. And upgrading can be done by simply switching the state that you are currently in. It is a remarkably stable design. Something very similar to that is sitting in the heart of most of what Rhino Mocks is doing.
Comments
In my opinion, the state machine pattern is one of the most elegant, simple solutions to most problems. For some reason, a lot of people seem to miss it, or not even realize it's available for them.
Good post.
The strategy pattern used like in the head first design pattern would be good, attach new attack strategy at runtime.
I could imagine an extension mechanism that is inherent to every unit. Extensions are many, can act as composite, can be merged (e.g. Power-Ups) or removed again (e.g. LifeCount). In a cycle, a Unit responds by letting all its extensions participate in the Unit's reaction.
Pmlarocque,
Strategy pattern isn't very good here, it doesn't provide for state management, and you need it here.
State & Strategy are closely related, but state allow changing itself
It would be very flexible if the unit were to have a set of weapons, and each weapon had a set of commands.
Very interesting solution, and something I'm going to look into today. I had looked into using Strategy, but I found I was ending up with the game engine becoming a God Engine.
I'd also looked into "applying" upgrades to the base unit (my basic solution was having a list of IUpgrades on each unit), but this was a vast oversimplification and didn't allow for anything other than modifying the base unit properties.
I would recommend that you look at your object model a bit differently.
Let me explain.
The difference between the archer, clubman and peasant in terms of the engine code should be negligible. They will all behave in a similar manner having actions such as attack, move, etc.
However, the difference between these units in terms of the game could be vast. The way they attack, their movement rates, etc.
Therefore, try to create a separation between the elements of your design which are engine focused versus elements that are game logic focused.
I found this distinction to be helpful every time I’ve dabbled with game programming.
Something like the following, this is unfinished BTW..
interface IAction
{
}
interface IAttack : IAction
{}
interface IMove : IAction
{}
IUpgradable
interface IAbilities
{
}
interface IUnit : IUpgradeable
{
}
class Unit : IUnit
{
}
class RangedAttack : IAttack
{
}
As others have hinted / said, I would consider wrapping things like Attack, Move, etc. in some IAbility interface, and let each Unit have some collection of these which can be added to when an Upgrade occurs (that's a feature of the game, right?).
Depending on the desired features of the engine, I would also consider using the flyweight pattern to distinguish between a unit type and a unit instance. For example:
// behavior and data that are true for all unit types (e.g. the Heavy Tank has a Max HP of 100).
interface IUnitType {
Abilities InitialAbilities;
// other data.
}
// behavior and data that differ within instances of a given unit type (e.g. one Heavy Tank might have an HP of 75, and another might have only 50).
interface IUnit {
IUnitType type; // useful for, e.g., ensuring that a Mechanic will bring up a HeavyTank's HP only up to 100, no more.
int CurrentHp;
int CurrentSpeed;
IAbilities Abilities;
// whatever other behavior units have a runtime.
}
// loading the game:
UnitTypes unitTypes = {new UnitType("Tank", ...), ...};
// creating a unit during the game:
// The unitType has all initial state data.
aTank = MyUnitFactory.CreateUnit(theTankUnitType);
So that demonstrate Enginey stuff. More Gamey extensions might include (if you need them):
class TankType ...
class Tank ...
In place of the flyweight pattern, you could instead use prototypes describing the start states of the IUnits, especially if that ends up being the only thing you describe in your IUnits (as may be the case with Upgrades, etc.).
If anyone is still interested, I posted my own thoughts on this on my blog:
"Spearmen, Javelin Throwers, and the State Pattern. Oh My!"
www.squaredroot.com/.../spearmen-javelin-throwe...
Comment preview