FSM
Name | Finite State Machine Plugin |
Category | Programming |
Author | Bruno Xavier Leite |
Version | 1.0 |
UE4 Build | 4.12+ |
Contents
Overview
Quick user guide on how to use the UFSM Plugin for Unreal Engine 4.
This guide assumes you have familiarity with Unreal Engine tools and subsystems; including Blueprints, AnimGraphs, BehaviorTrees, etc.
UFSM is a simple, yet powerful, Finite State Machine system which allows you to manipulate Actor States through Animation Graph’s State Machines, Behavior Tree Tasks, Blueprint Graphs, or C++ Native Code.
Finite State Machines allows for more logic encapsulation and cleaner code architecture in general, drawing systems design and debugging easier on the long run.
You will be able to get the plugin from Epic Games, in Launcher or Marketplace webpage, and install directly to your Unreal Engine directory. No other installation steps are required.
Requirements
Theoretically, this system could work with any Unreal Engine 4 version, but is developed and maintained only for 4.12 version and beyond.
Building the engine from source is not required, you can install on both GitHub or Launcher engine versions.
Technical Details
Within the UFSM Plugin you have some new feature tools to work with:
- Blueprintable Finite State Machine System.
- Blueprint Actor Component Based.
- Event-Based Tasks/Actions can be Broadcasted.
- Supports Enumerators as State ID.
- Supports Runtime State-Machine Creation/Deletion.
- Supports Runtime State-Machine Event Bindings.
- State Machines can Tick and Track Update Time.
- FSM Properties can be Replicated.
- Get/Set States from C++, Blueprints, Animation Graphs, Behavior Trees.
FSM Component
Once you have the UFSM Plugin installed and UEditor restarted, you now have access to a new Custom Actor Component: the 'Finite State Machine' Component.
This component can be attached to any Actor in your Map, but for some utilities an AI Controller or a Skeletal Character are required. In case it's just a Pawn or plain Actor, you can manipulate its States through Blueprint Graphs or C++ Code, but not Behavior Trees or Animation Blueprints.
For all Actors you wish to implement a Finite State Machine, each of them must have at least one of this Component.
FSM: Blueprint Graph
When you add a FSM Component to your Actor/Pawn/Character Blueprint, a new set of Events are generated for it. You use those Events to query, change, or react to State Machine's changes in your Blueprint when the game is running.
On Begin State
This Blueprintable Event fires in your Actor every time a FSM State enters active mode. From it you are able to query which State ID has just entered active mode and its Name. From there is quite simple to compare its ID with an Enumerator or fire another Event based on which active State Name is set.
NOTE : If you have "Blueprint Auto-Flow FSM" option active (enabled by default on latest plugin releases) then this Node is no longer required, all of your State functions will be called automatically by their owner State when such State becomes active in your FSM Component.
On Update State
This Blueprintable Event fires in your Actor every time the actual active FSM State ticks, every frame. From it you can query which State ID is ticking, its name and also for how long have it be updating in seconds. Keeping track of the Update Time is quite useful in Finite State Machines, you can architecture other Functions or Events to fire once an State has been running after X seconds.
NOTE : If you have "Blueprint Auto-Flow FSM" option active (enabled by default on latest plugin releases) then this Node is no longer required, all of your State functions will be called automatically by their owner State when such State becomes active in your FSM Component.
On Exit State
This Blueprintable Event fires in your Actor every time the actual active FSM State is exiting active mode. From it you can query which State ID just transitioned to inactive mode and its Name.
NOTE : If you have "Blueprint Auto-Flow FSM" option active (enabled by default on latest plugin releases) then this Node is no longer required, all of your State functions will be called automatically by their owner State when such State becomes active in your FSM Component.
FSM: Animation Graph
If you so wish, you can make a FSM Component reflect a State Machine inside an Animation Blueprint. This however applies only to Skeletal Actors because Animation Blueprint is required.
Animation Graph’s State Machine Editor is great to setup a FSM and keep a complete visual reference of how your FSM behaves. To do so, you create an Animation Blueprint as usual for your Skeletal Asset, but instead of choosing AnimInstance as Parent Class, pick ‘StateMachineABP’:
Within an Animation Blueprint you can add multiple State Machines, so you must tell the Animation Blueprint which is the Name of the FSM Component you want it to link then tell it the Name of which of its own State Machines you want to 'export' to the FSM Component. Once you setup target Name fields, and 'Override FSM' option is enabled, the Animation Blueprint will on Game startup tell the FSM to refresh all of its States reflecting the States you've created inside the Animation Graph's State Machine. Note that 'Target ASM' refers to Animation State Machine and 'Target FSM' refers to the FSM Component you have attached to your Actor.
The Graph setup is that simple, simply make sure you have input both FSM and ASM Names correctly then the linkage is done.
FSM: Behavior Trees
You can also manipulate FSM States within an AI Controller’s Behavior Tree Board. Using the custom Task nodes: ‘Add State’, ‘Set State’, ‘Check State’, and ‘Remove State’, you can easily make your AI dictate FSM Component’s States based on AI decisions. This is also an awesome way to easily make your AI react to changes within an Animation Blueprint’s State or react to changes within a Character Blueprint’s Events.
It's possible to organize your Tree to manipulate the whole FSM Component or restrict it to react to or change only specific FSM States, you have a lot of freedom on how you Add, Set, Check for, or Remove States. You can even use multiple Behavior Trees to dictate different FSM State routes.
FSM: (C++) Native API
Of course if you wish so, you can build your FSM Component and its States from C++ Code and still later be able to interact with it through Behavior Trees, Animation Blueprints, Blueprints, etc. An additional power you have from Native Code is you can bind Functions to FSM States, directly. That means from C++ you don't have to query for State ID to call a Function like in Blueprints, you can bind Functions to a State object directly!
Here's a straightforward example of the creation of a Finite State Machine Component within an Actor's Constructor:
auto StateMachine = CreateDefaultSubobject<UStateMachineComponent>(TEXT("StateMachine"));
if (StateMachine->IsValidLowLevel()) {
StateMachine->AddState(0,FName("Idle"));
StateMachine->AddState(1,FName("Stand"));
StateMachine->AddState(2,FName("Run"));
StateMachine->AddState(3,FName("Push"));
};
And binding Functions to a State and each of its Events; these functions will be executed every time the State is active:
StateMachine->GetState(0)->OnBeginState.AddDynamic(this,&ACharacter::OnBeginIdle);
StateMachine->GetState(0)->OnUpdateState.AddDynamic(this,&ACharacter::OnUpdateIdle);
StateMachine->GetState(0)->OnExitState.AddDynamic(this,&ACharacter::OnExitIdle);
As you can see, you can bind a new Function to States during runtime, or unbind Functions from it while the game is running as well. You can also easily manipulate States by State Name, State IDs or even directly compare against Enumerator types directly by code, with very simple lines, such as:
switch (StateMachine->GetStateID()) {
case 0: /////////////////// Idle:
if (Axis >= 0.8f) {StateMachine->SetState(FName("Push"));}
default:
StateMachine->SetState((uint8)EStates::Idle);
break;
}
Example Character Controller class, setup to use a FSM Component (UFSM 1.6.0+ update), and how to subscribe to State change events automatically fired by the UStateMachineComponent object:
A Simple Character .H:
#pragma once
#include "UFSM.h"
#include "GameFramework/Character.h"
#include "FSM_NativeCharacter.generated.h"
UCLASS()
class MYGAMEAPI AFSM_NativeCharacter : public ACharacter, public IFSMInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AFSM_NativeCharacter();
UPROPERTY()
UStateMachineComponent* StateMachine;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void BindBeginIdle();
UFUNCTION()
void BindUpdateIdle();
UFUNCTION()
void BindExitIdle();
public:
/// FSM: Initialization Interface.
UFUNCTION(Category = "FSM", BlueprintNativeEvent, BlueprintCallable, meta = (DisplayName = "FSM: On Initialized", Keywords = "FSM Initialization Interface"))
void OnInitializedFSM(const UStateMachineComponent* Context);
//
/// FSM: On Any 'On Begin' Event.
UFUNCTION(Category = "FSM", BlueprintNativeEvent, BlueprintCallable, meta = (DisplayName = "FSM: On Any Begin", Keywords = "FSM Begin Event"))
void OnAnyBeginState(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID, const uint8 WithPrevious);
//
/// FSM: On Any 'On Update' Event.
UFUNCTION(Category = "FSM", BlueprintNativeEvent, BlueprintCallable, meta = (DisplayName = "FSM: On Any Update", Keywords = "FSM Update Event"))
void OnAnyUpdateState(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID, const float WithStateTime);
//
/// FSM: On Any 'On Exit' Event.
UFUNCTION(Category = "FSM", BlueprintNativeEvent, BlueprintCallable, meta = (DisplayName = "FSM: On Any Exit", Keywords = "FSM Exit Event"))
void OnAnyExitState(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID);
};
A Simple Character .CPP:
#include "FSM_NativeCharacter.h"
#include "Unreal_FSM.h"
AFSM_NativeCharacter::AFSM_NativeCharacter()
{
PrimaryActorTick.bCanEverTick = true;
StateMachine = CreateDefaultSubobject<UStateMachineComponent>(TEXT("StateMachine"));
if (StateMachine->IsValidLowLevelFast()) {
StateMachine->AddState(0,FName("Idle"));
StateMachine->AddState(1,FName("Stand"));
StateMachine->AddState(2,FName("Run"));
StateMachine->AddState(3,FName("Push"));
StateMachine->SetActive(true,true);
StateMachine->bAutoActivate = true;
StateMachine->Debug = true;
}
}
void AFSM_NativeCharacter::BeginPlay()
{
Super::BeginPlay();
FSM_Transition Trans;
StateMachine->SetStateID(0,Trans);
}
void AFSM_NativeCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FSM_Transition Trans;
if (StateMachine->GetTime() > 4 && (StateMachine->GetCurrentStateID() < StateMachine->GetLastStateID())) {
StateMachine->SetStateID(StateMachine->GetCurrentStateID()+1,Trans);
} else if (StateMachine->GetTime() > 4) {
StateMachine->SetStateID(0,Trans);
}
}
void AFSM_NativeCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void AFSM_NativeCharacter::OnInitializedFSM_Implementation(const UStateMachineComponent* Context)
{
if (Context != StateMachine) {return;}
//
StateMachine->GetState(0)->OnBeginState.AddDynamic(this,&AFSM_NativeCharacter::BindBeginIdle);
StateMachine->GetState(0)->OnUpdateState.AddDynamic(this,&AFSM_NativeCharacter::BindUpdateIdle);
StateMachine->GetState(0)->OnExitState.AddDynamic(this,&AFSM_NativeCharacter::BindExitIdle);
}
void AFSM_NativeCharacter::OnAnyBeginState_Implementation(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID, const uint8 WithPrevious) {/*...*/}
void AFSM_NativeCharacter::OnAnyUpdateState_Implementation(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID, const float WithStateTime) {/*...*/}
void AFSM_NativeCharacter::OnAnyExitState_Implementation(const UStateMachineComponent* Context, const FName WithStateName, const uint8 WithStateID) {/*...*/}
void AFSM_NativeCharacter::BindBeginIdle() {
UE_LOG(LogTemp,Warning,TEXT("{FSM}:: %s"),TEXT("BINDED Begin!"));
}
void AFSM_NativeCharacter::BindUpdateIdle() {
UE_LOG(LogTemp,Warning,TEXT("{FSM}:: %s"),TEXT("BINDED Update!"));
}
void AFSM_NativeCharacter::BindExitIdle() {
UE_LOG(LogTemp,Warning,TEXT("{FSM}:: %s"),TEXT("BINDED Exit!"));
}
Note that you have to make the plugin visible to your Game's module in order to make its Components and header files usable in your C++ code. For that you must add a reference of UFSM module to your Build.cs file:
PrivateDependencyModuleNames.AddRange(new string[] { "UFSM" });
Conclusion
With a little bit of imagination, this little tool provides unlimited possibilities! Thanks to the awesome environment provided by Epic Games and their Unreal Engine 4. If you’re used to work with State Machines, such as Unity PlayMaker, you’ll quickly adapt to the workflow proposed by this Plugin; You can really encapsulate all code logic into Finite State rules. If you never tried FSMs before to build game logic, it will set you free of infinite spaghetti piles of code on the long run, give it a try!