UFSM: Finite State Machine

@FaSDt1517

https://i.imgur.com/WhSPYMB.png

There’s four things to keep in mind doing things this way:

  • Transitions are UFunctions, you never call them manually.
  • Your transition functions must be named as follows: “From + StateName + To + NewStateName”.
  • You never call any SetState() functions, the custom component calls them when your transition function succeeds.
  • You usually do all the state work inside of your custom FSM component class, instead of in owner character class. [HR][/HR]
    Here’s an example Character Class + Custom FSM Component:

FSM_NativeCharacter_AutoFlowAPI.h:



#pragma once

#include "UFSM.h"
#include "GameFramework/Character.h"
#include "FSM_NativeCharacter_AutoFlowAPI.generated.h"


class UFSM_AutoFlowAPI;


UCLASS()
class UNREAL_FSM_API AFSM_NativeCharacter_AutoFlowAPI :  ACharacter
{
    GENERATED_BODY()

:
    // Sets default values for this character's properties
    AFSM_NativeCharacter_AutoFlowAPI();

    UPROPERTY()
    UFSM_AutoFlowAPI* StateMachine;

    UPROPERTY()
    float Energy = 0.0f;

protected:

    UFUNCTION() void OnBeginState(uint8 StateID, uint8 PreviousStateID, FName StateName, FSM_Transition Transition);
    UFUNCTION() void OnUpdateState(uint8 StateID, FName StateName, float StateTime);
    UFUNCTION() void OnExitState(uint8 StateID, FName StateName, FSM_Transition Transition);

};


FSM_NativeCharacter_AutoFlowAPI.cpp:



#include "FSM_NativeCharacter_AutoFlowAPI.h"
#include "FSM_AutoFlowAPI.h"


AFSM_NativeCharacter_AutoFlowAPI::AFSM_NativeCharacter_AutoFlowAPI()
{
    PrimaryActorTick.bCanEverTick = true;

    StateMachine = CreateDefaultSubobject<UFSM_AutoFlowAPI>(TEXT("StateMachine"));
    if (StateMachine->IsValidLowLevelFast()) {

        StateMachine->OnBegin.AddDynamic(this,&AFSM_NativeCharacter_AutoFlowAPI::OnBeginState);
        StateMachine->OnUpdate.AddDynamic(this,&AFSM_NativeCharacter_AutoFlowAPI::OnUpdateState);
        StateMachine->OnExit.AddDynamic(this,&AFSM_NativeCharacter_AutoFlowAPI::OnExitState);

    }
}


void AFSM_NativeCharacter_AutoFlowAPI::OnBeginState(uint8 StateID, uint8 PreviousStateID, FName StateName, FSM_Transition Transition)
{
    LOG_FSM(true,FString::Printf(TEXT("On Begin State::  %s"),*StateName.ToString()));
}

void AFSM_NativeCharacter_AutoFlowAPI::OnUpdateState(uint8 StateID, FName StateName, float StateTime)
{
    LOG_FSM(true,FString::Printf(TEXT("On Update State::  %s"),*StateName.ToString()));
}

void AFSM_NativeCharacter_AutoFlowAPI::OnExitState(uint8 StateID, FName StateName, FSM_Transition Transition)
{
    LOG_FSM(true,FString::Printf(TEXT("On Exit State::  %s"),*StateName.ToString()));
}


FSM_AutoFlowAPI.h:



#pragma once

#include "UFSM.h"
#include "FSM_AutoFlowAPI.generated.h"


class AFSM_NativeCharacter_AutoFlowAPI;


UCLASS()
class UNREAL_FSM_API UFSM_AutoFlowAPI :  UStateMachineComponent
{
    GENERATED_BODY()

:
    UFSM_AutoFlowAPI();


    UFUNCTION() void OnBeginIdle(const FSM_BeginEvent StateInfo);
    UFUNCTION() void OnUpdateIdle(const FSM_UpdateEvent StateInfo);
    UFUNCTION() void OnExitIdle(const FSM_ExitEvent StateInfo);

    UFUNCTION() void OnBeginRun(const FSM_BeginEvent StateInfo);
    UFUNCTION() void OnUpdateRun(const FSM_UpdateEvent StateInfo);
    UFUNCTION() void OnExitRun(const FSM_ExitEvent StateInfo);


    UFUNCTION() void FromIdleToRun(FSM_Transition &Condition);
    UFUNCTION() void FromRunToIdle(FSM_Transition &Condition);

};


FSM_AutoFlowAPI.cpp:



#include "FSM_AutoFlowAPI.h"
#include "UFSM_FunctionLibrary.h"
#include "Math/UnrealMathUtility.h"

#include "FSM_NativeCharacter_AutoFlowAPI.h"


UFSM_AutoFlowAPI::UFSM_AutoFlowAPI()
{
    PrimaryComponentTick.bStartWithTickEnabled = true;
    PrimaryComponentTick.bCanEverTick = true;
    bWantsInitializeComponent = true;
    bAutoActivate = true;

    BlueprintAutoFlowFSM = true;
    BlueprintAutoFlowTransitions = true;

    AddState(0,FName("Idle"));
    AddState(1,FName("Run"));
    AddState(2,FName("Jump"));

    StartupState = FName("Idle");
}



void UFSM_AutoFlowAPI::OnBeginIdle(const FSM_BeginEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Begin Idle!"));
}

void UFSM_AutoFlowAPI::OnUpdateIdle(const FSM_UpdateEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Update Idle!"));
}

void UFSM_AutoFlowAPI::OnExitIdle(const FSM_ExitEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Exit Idle!"));
}


void UFSM_AutoFlowAPI::OnBeginRun(const FSM_BeginEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Begin Run!"));
}

void UFSM_AutoFlowAPI::OnUpdateRun(const FSM_UpdateEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Update Run!"));
}

void UFSM_AutoFlowAPI::OnExitRun(const FSM_ExitEvent StateInfo)
{
    LOG_FSM(true,TEXT("On Exit Run!"));
}


void UFSM_AutoFlowAPI::FromIdleToRun(FSM_Transition &Condition)
{
    if (AFSM_NativeCharacter_AutoFlowAPI* Owner = Cast<AFSM_NativeCharacter_AutoFlowAPI>(GetOwner())) {
        bool isIdle = FMath::IsNearlyEqual(Owner->GetVelocity().Size(),0.f,0.1f);
        bool isGrounded = Owner->CanJump();

        Condition = UFSMHelper::FSM_EvaluateTwoBools(isGrounded,isIdle);
    }
}

void UFSM_AutoFlowAPI::FromRunToIdle(FSM_Transition &Condition)
{
    bool timeout = (StateTime >= 5.f);

    if (AFSM_NativeCharacter_AutoFlowAPI* Owner = Cast<AFSM_NativeCharacter_AutoFlowAPI>(GetOwner())) {
        bool exausted = FMath::IsNearlyEqual(Owner->Energy,0.f,0.1f);
        bool stop = (timeout && exausted);

        Condition = UFSMHelper::FSM_EvaluateBool(stop);
    }
}


[HR][/HR]
Pay extra attention to state function signatures, They cannot deviate from that.
Transition params should be always be (FSM_Transition &Condition) and state params should always be (const FSM_xxxEvent StateInfo).

If you recreate example above and drop the *FSM_NativeCharacter_AutoFlowAPI *character inside a level, when playing it is going to enter “Run” state every 5 seconds then go back to “Idle” and start “Run” again… :

https://i.imgur.com/n0k3MGu.png