FSM 设计模式学习
FSM
Struct FSM
定义了状态机的三个阶段:Enter
、Tick
、Exit
struct FSM {
public:
FSM() {
}
TUniqueFunction<void()> Enter;
TUniqueFunction<void(float)> Tick;
TUniqueFunction<void()> Exit;
};
enum EState
枚举了不同的状态
UENUM(BlueprintType)
enum EState {
Idle UMETA(DisplayName = "Idle"),
Placement UMETA(DisplayName = "Placement"),
Press UMETA(DisplayName = "Press"),
Drag UMETA(DisplayName = "Drag"),
MoveAxis UMETA(DisplayName = "MoveAxis"),
};
FSMMap
TMap<EState, FSM*> FSMMap;
RegisterFSM
这个宏用于在 Unreal Engine 中定义和注册有限状态机的状态。它自动创建一个状态的结构体,设置进入、更新和退出状态的处理函数,并提供状态转换的方法。使用这个宏可以减少重复代码,确保状态定义和注册的一致性。
1. 定义结构体
struct state ## Struct : FSM {
public:
state ## Struct() {
Enter = [=]() {
getController()->Enter ##state## State();
};
Tick = [=](float delta) {
getController()->Tick ##state## State(delta);
};
Exit = [=]() {
getController()->Exit ##state## State();
};
UResourceManager::Get()->FSMMap.Add(EState::state, this);
}
};
state ## Struct
: 定义一个结构体,名称由state
和Struct
组合而成。例如,如果你传入Idle
,结构体名为IdleStruct
。FSM
:state ## Struct
继承自FSM
,表示这是一个有限状态机的状态。- 构造函数 (
state ## Struct()
): 设置Enter、Tick和Exit的 lambda 函数:Enter
:进入状态时调用。Tick
:状态激活期间每帧调用一次,delta
表示时间差。Exit
:退出状态时调用。- 将状态注册到
UResourceManager
的FSMMap
中,使用EState::state
作为键值(例如EState::Idle
)。
2. 创建实例
state ## Struct state ## Struct;
- 创建
state ## Struct
的实例,实例名与结构体名相同。如果state
是Idle
,则实例名为IdleStruct
。
3. 定义状态转换函数
void Enter ##state## State();
UFUNCTION()
void Tick ##state## State(float* delta) {
Tick ##state## State(*delta);
}
void Tick ##state## State(float delta);
void Exit ##state## State();
Enter ##state## State()
: 声明一个方法,用于处理状态进入的逻辑。Tick ##state## State(float\* delta)
: 声明一个方法,用于处理状态的每帧更新,参数是delta
的指针。UFUNCTION
宏使得这个方法可以通过 Unreal 的反射系统调用。Tick ##state## State(float delta)
: 声明一个方法,用于处理状态的每帧更新,参数是delta
的值。Exit ##state## State()
: 声明一个方法,用于处理状态退出的逻辑。
4. 定义状态转换方法
void ChangeStateTo ## state() {
ChangeStateTo(EState::state);
}
ChangeStateTo ## state()
: 定义一个方法,用于转换到指定的状态。如果state
是Idle
,方法名为ChangeStateToIdle()
,并调用ChangeStateTo(EState::Idle)
。
完整代码
#define RegisterFSM(state) \
struct state ## Struct : FSM{ \
public: \
state ## Struct(){ \
Enter = [=](){ \
getController()->Enter ##state## State(); \
}; \
Tick = [=](float delta){ \
getController()->Tick ##state## State(delta); \
}; \
Exit = [=](){ \
getController()->Exit ##state## State(); \
}; \
UResourceManager::Get()->FSMMap.Add(EState::state, this); \
} \
}; \
state ## Struct state ## Struct; \
void Enter ##state## State(); \
UFUNCTION() \
void Tick ##state## State(float* delta) { \
Tick ##state## State(*delta); \
} \
void Tick ##state## State(float delta); \
void Exit ##state## State();\
void ChangeStateTo ## state(){\
ChangeStateTo(EState::state); \
}
ChangeStateTo
EState State = EState::Idle;
void AMyPlayerController::ChangeStateTo(EState state) {
if (State == state)
return;
if (UResourceManager::Get()->FSMMap.Contains(State) && UResourceManager::Get()->FSMMap[State] != nullptr)
UResourceManager::Get()->FSMMap[State]->Exit();
State = state;
if (UResourceManager::Get()->FSMMap.Contains(State) && UResourceManager::Get()->FSMMap[State] != nullptr)
UResourceManager::Get()->FSMMap[State]->Enter();
}
Tick
控制器的 Tick
函数中,如果当前状态存在,则状态依照进入 Tick
函数
void AMyPlayerController::Tick(float delta) {
if (UResourceManager::Get()->FSMMap.Contains(State) && UResourceManager::Get()->FSMMap[State] != nullptr)
UResourceManager::Get()->FSMMap[State]->Tick(delta);
}
实际运用
注册各个状态下的状态机
不同状态机还附带了需要传递的信息
RegisterFSM(Idle);
RegisterFSM2(Placement, AActor*, HitActor, UStaticMeshComponent*, HitComponent);
RegisterFSM3(Drag, AActor*, HitActor, UStaticMeshComponent*, HitComponent, FTransform, StartTransform);
RegisterFSM2(MoveAxis, FTouchParam, TouchParam, FTransform, StartTransform);
RegisterFSM4(Press, AActor*, HitActor, UMeshComponent*, HitComponent, FTouchParam, TouchParam, FPressParam, PressParam);
以下是转化不同状态的实际运用场景,可以发现,除了一开始定义的 ChangeStateTo()
函数,通过 RegisterFSM
创建的状态机中的Enter
、Tick
、Exit
都分别进行了初始化
void AMyPlayerController::OnLeftMousePressed() {
if (!IsPressActor()) {
ChangeStateToPress(nullptr, nullptr);
}
}
bool AMyPlayerController::IsPressActor() {
if (IsPressAxisActor()) {
ChangeStateTo(EState::MoveAxis);
return true;
}
}
if (hit.GetActor()->IsA<AMeshActor>()) {
UMeshComponent* meshComponent = Cast<AMeshActor>(hit.GetActor())->MeshComponent;
ChangeStateToPress(hit.GetActor(), meshComponent);
}
if (UResourceManager::Get()->GetTag(hit.GetActor(), "soft").Equals("true")) {
ChangeStateToPress(hit.GetActor(), Cast<UStaticMeshComponent>(hit.GetComponent()));
return true;
}
void AMyPlayerController::OnLeftMouseReleased() {
UWidgetManager::Get()->SetMouseOverUI(false);
ChangeStateToIdle();
}
AActor* AMyPlayerController::PlacementActor(const FString& id)
{
AActor* actor = UResourceManager::Get()->SpawnActor(id, FVector(0, 0, -5000), FRotator::ZeroRotator);
if (actor != nullptr) {
ChangeStateToPlacement(actor, nullptr);
}
return actor;
}
void AMyPlayerController::EnterIdleState() {
}
void AMyPlayerController::TickIdleState(float delta) {
}
void AMyPlayerController::ExitIdleState() {
}
标签:Tick,State,##,void,FSM,学习,state,delta,设计模式
From: https://www.cnblogs.com/Dreammoon/p/18371329