State Classes

Managing complexity and re-use

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 an FSM node is assigned an implemenation class, double clicking the node will open the blueprint editor of the class.

You can also implement a state class in C++ by defining a sub-class of class UGCFSMState.

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.

Double-click on an FSM State node to show this dialog
State classes can override these FSM events

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.

Although we recommend always declaring the Context variable, it’s not an error if a state class does not have it.

The Context variable is just a variable with the type of the context object (in this case Example Base)
You access the Context variable just like a regular variable

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.

If you don’t see an exposed variable in the blueprint editor of if you see a variable that shouldn’t be there, right-click on the node and select Refresh Node.

To expose a state parameter, just create a variable and set these two flags
An exposed parameter can be set in the State Node

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.

The presence of the Context variable in a state object is required to properly initialize the Context variable of nested state objects.

Internal events

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.