I'm working on a state machine implementation for game objects in a video game. I'd like this state machine to be simple to use; in order to invoke a change of state a 'Raise' method should be called on the state machine. My current design involves using a generic type as the event identifier:
stateMachine.Raise<Event>();
with the definition:
public void Raise<EventType>() where EventType : Event
The state machine then finds states that handle this event type and updates the state according to the transitions they provide. I considered originally using a string to recognize event types, e.g.:
stateMachine.Raise("Event");
or and Enum:
stateMachine.Raise(GameObject.Event);
But I feel that both these methods pose issues. Strings are two flexible and can't be error checked at compile time to ensure that they're valid. Enums can't be extended, limit the event's available to each class that extends GameObject, make the event calls longer unnecessarily and cause issues with type checking inside the state machine. However the method of using a class as an identify means that I declare the events inside the GameObject like this:
protected class SomeEvent : Event {}
protected class OtherEvent : Event {}
...
protected class AnotherEvent : Event {}
Despite being elegant, I feel this is somehow going to become a code smell or terrible practice at some point, I get the impression I'm abusing the purpose of classes. Eventually I will have hundreds of empty classes littering my code base which I can see being a potential issue. What really is the best alternative for this?
Since you're writing for a video game - and unless this is a feature of the game - your events and states should be defined in your specs, so the list of possible events should not be moving a lot once defined; so go for classes that derive from a common ancestor.
Why not enums or strings? Because enums and strings don't carry data. You usually want to have some data going with your events, because it will let you group your events by families. You don't need a GrumpinessIncreaseEvent
and a JoyIncreaseEvent
, rather a MoodChangeEvent
with some information in it (what type of moods are affected, by how much, etc)
What's more you can have some events that are loosely typed for potential known unknowns you may have later (an event with an object property that is then cast to some type at runtime, for example)
re: OP's comment: I indeed think that you should use instances instead of type. You wouldn't pass PunchInFace
but new DamageEvent() {type = Damage.PUNCH, source= PuncherGuy.Fist, Target=PunchedGuy.Face}
because this kind of event is friendlier to plenty of components in your game (physics engine, score engine, etc...)
Once again it depends on how the game is done (which makes me think that the question should in fact be closed because it is opinon-based) but when you raise a PunchInFaceEvent
by type, what can you say? Who punched who? Where? When? etc...