We described in the previous sections how to all the FSM logic is contained in the event graph of the object that runs the FSM. However, if we need to attach a complex behavior to states, using the state Tick functions will clutter the event graph, which quickly become unamanageable. State classes allow to factor the state logic out of the FSM graph, in order to keep complexity under control. Moreover, once a behavior is packaged in a state class, it can be re-used several times with ease!
A state class is any class that derives from class GC FSM State. The easiest way to create a blueprint for a state class is to double-click any FSM State node that has no implementation class assigned to it. Simple as that! A dialog box will open up so that you can specify the name and location of the new blueprint.
Once you have created a state class, you can use it in any FSM State node, by simply selecting the class as the value of the Implementation Class pin. Each a state node with an implementation class is entered, an new object of that class is instantiated, then the OnEnter overridable event will be invoked on the new state object. While the state is active, the OnTick overridable event is invoked every frame on the state object. When the state node exits, for example because a transition event has been received, the OnExit overridable event is invoked on the state object, then the object is marked as destroyed to be released at the next garbage collection.
Accessing the context object
Typically, a state object will need to access the original object that is running the FSM. We call such object the context object. GC FSM provides a very convenient way to gain access to it. Just add a variable named Context with a type compatible with the actual type of the context object and it will be automatically set before calling OnEnter on the state object. If you created a state class blueprint by double-clicking the FSM State node as suggested above, a Context variable with the right type will be automatically created for you.
The type of the Context variable is usually the exact same class of the context object, however even a base class of the context object will do. Using a base class allows the same state class to be re-used with context objects of different types.
The presence and compatibility of the Context variable is checked at compile time, when possible. If there is no variable or if the variable has an incompatible type, you can fix the issue by right-clicking on the state node and selecting the “Add Context Variable” or “Fix Context Variable” commands.
Exposing state object parameters
In order to improve re-usability of complex state classes, you can specify class variables to be exposed directly in the FSM State node. Just set the “Instance Editable” and “Expose on Spawn” flags on the variable to expose and re-compile the blueprint. The variable will then be shown as a pin right in the State node.
The exposed variables will be set to the values provided to the FSM state node when the state is entered, after instantiating the state object, but before calling OnEnter.
Nesting state machines
Since you can have FSMs in every object, you can have them in states classes too! FSMs defined in a state class works exactly the same as regular top-level FSMs, with the following exceptions:
- the Context object for the states in a nested FSM is the original object that created the top-level FSM. Currently there is no way for a nested state to access its parent state object directly.
- when a state running nested FSMs is exited, all the active states of its nested FSMs receive the OnExit notification before being destroyed.
In addition to the event processing executed at the FSM level, a state object has the possibility to handle events internally. While the state is active, when the FSM receives an event that does not lead to a transition and was not deferred, the FSM checks if the state object implements an Internal Event with a matching name: if it does, the event is invoked.
To create an internal event handler, select “Add Internal Event…” from the GC FSM category. The name of the node is editable and will be matched with the event name to handle.
The internal event handler is passed the age of the event, which is the time in seconds since the event was received by the FSM. The age is typically either 0 or a small value less than the frame time, but it may be a larger value if the event was initially received while the FSM was in a different state that deferred the event.