State: why not just make the states (non-static!) members of the Heroine class?
Then Coop is no problem because each Heroine has her own states and you still don't have the allocation overhead and memory fragmentation.
If you wanna reset some state (e.g. chargeTime_ = 0;), you can either do that in FooState::handleInput() after heroine.state_ = heroine.barState_; or in the state's exit() method.
A Problem with the Concurrent State Machines not explicitly mentioned: Even if you somehow have two state machines for state and equipment, you will still want to set different sprites with/without gun for each state.. which probably creates kinda ugly code.
I'd use the following to make it less ugly which also has some other benefits:
One can combine Enums with State Objects - just put the states in an array, so the index matches the corresponding enum value (actually, this "pattern" is not only useful for states).
Now you don't mess around with pointers anymore, but just with an state enum value.
class Heroine
{
friend class HeroineState;
enum State
{
STATE_STANDING = 0,
STATE_JUMPING,
STATE_DUCKING,
STATE_DIVING,
// ...
_STATE_NUMSTATES
}
public:
virtual void handleInput(Input input)
{
states[stateIndex]->(*this, input);
}
virtual void update()
{
states[stateIndex]->(*this);
}
// Other methods...
private:
State stateIndex = STATE_STANDING;
HeroineState* states[_STATE_NUMSTATES];
};
StandingState::handleInput(Heroine& heroine, Input input)
{
if (input == PRESS_DOWN)
{
// Other code...
heroine.stateIndex = STATE_DUCKING;
// TODO: use a HeroineState::setState(Heroine&, State) method so only HeroineState
// needs to be a friend of Heroine to change the private Heroine::stateIndex
}
// Stay in this state by doing nothing
}
Now if we wanna introduce a second set of states (e.g. for equipment), it could get its own enum Equipment, an equipmentIndex and equipments[_EQUIPMENT_NUMSTATES].
For the sprites however, (assuming we really need one for each combination of states) we can have a multidimensional array for each combination of states:
Sprite stateSprites[_STATE_NUMSTATES][_EQUIPMENT_NUMSTATES];
and the sprite to use is just stateSprites[stateIndex][equipmentIndex].
Or maybe it's really just one big image that has _STATE_NUMSTATES colums and _EQUIPMENT_NUMSTATES rows of sprites and we get the right sprite by calculating an offset from those two indexes.
This keeps the states at n+m complexity (+ a few hacks for "no gun while diving" and similar), while allowing n*m different animations - without too much pain.
Another advantage is, that serialization is easier:
The current state is just a number (enum), no pointer => trivial to serialize/restore
We still have to serialize all the states (at least if they have their own state like chargeTime_) - but iterating the state arrays and calling states[i]->serialize(savegame); is less painful and error-prone than calling serialize on n+m state fields of the class.
I found this "pattern" (array with enum index) helpful in other cases as well.. for example if you have several class fields of the same type (or at leasts one with a common supertype) and in several methods you do basically the same thing with them (call the same method on each one, like update()), but in some you specifically want to handle only one of them. Then iterating over them is nicer then calling each of them separately and accessing them with myArray[NAME] is about as readable as myName.
BTW: The case of state_ just being a function pointer reminds me of the think function pointers of entities in the Quake engines :-)
PS: Great book so far, this is the first time I could stand reading about design pattern for longer than 5 minutes :-)
I would like to add that putting static instances of the derived classes as members of the base class is impossible, because there's no way to have defined them before the base class' definition is finished.
State: why not just make the states (non-static!) members of the Heroine class?
Then Coop is no problem because each Heroine has her own states and you still don't have the allocation overhead and memory fragmentation.
If you wanna reset some state (e.g.
chargeTime_ = 0;
), you can either do that inFooState::handleInput()
afterheroine.state_ = heroine.barState_;
or in the state'sexit()
method.A Problem with the Concurrent State Machines not explicitly mentioned: Even if you somehow have two state machines for state and equipment, you will still want to set different sprites with/without gun for each state.. which probably creates kinda ugly code.
I'd use the following to make it less ugly which also has some other benefits:
One can combine Enums with State Objects - just put the states in an array, so the index matches the corresponding enum value (actually, this "pattern" is not only useful for states).
Now you don't mess around with pointers anymore, but just with an state enum value.
Now if we wanna introduce a second set of states (e.g. for equipment), it could get its own
enum Equipment
, anequipmentIndex
andequipments[_EQUIPMENT_NUMSTATES]
.For the sprites however, (assuming we really need one for each combination of states) we can have a multidimensional array for each combination of states:
Sprite stateSprites[_STATE_NUMSTATES][_EQUIPMENT_NUMSTATES];
and the sprite to use is juststateSprites[stateIndex][equipmentIndex]
.Or maybe it's really just one big image that has _STATE_NUMSTATES colums and _EQUIPMENT_NUMSTATES rows of sprites and we get the right sprite by calculating an offset from those two indexes.
This keeps the states at n+m complexity (+ a few hacks for "no gun while diving" and similar), while allowing n*m different animations - without too much pain.
Another advantage is, that serialization is easier:
chargeTime_
) - but iterating the state arrays and callingstates[i]->serialize(savegame);
is less painful and error-prone than callingserialize
on n+m state fields of the class.I found this "pattern" (array with enum index) helpful in other cases as well.. for example if you have several class fields of the same type (or at leasts one with a common supertype) and in several methods you do basically the same thing with them (call the same method on each one, like
update()
), but in some you specifically want to handle only one of them. Then iterating over them is nicer then calling each of them separately and accessing them withmyArray[NAME]
is about as readable asmyName
.BTW: The case of state_ just being a function pointer reminds me of the
think
function pointers of entities in the Quake engines :-)PS: Great book so far, this is the first time I could stand reading about design pattern for longer than 5 minutes :-)