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