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 time a state node with an implementation class is entered, an new object of that class is instantiated. The state classes can override three events to perform their task:
- The OnEnter overridable event will be invoked on the new state object, as soon as the state is entered
- While the state is active, the OnTick overridable event is invoked every frame on the state object. The OnTick event has a deltaTime parameter, that will contain the difference in seconds since the previous tick, as it’s typical with tick events
- When the state node exits, the OnExit overridable event is invoked. The OnExit event has an event parameter that will contain the name of the event that has caused the transition. If the transition occurred due to a expired timeout, the event parameter will be set to a special value, that can be retrieved with the GetTimeOutEventName function. For a sub-FSM, if the state is exited because a parent FSM has executed a transition, the event parameter will be set “None”
Immediately after the OnExit function is called, the state object is marked as destroyed and will not be accessed again. It will be released by the engine at the next garbage collection cycle.
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.