FSM 设计模式学习

FSM 设计模式学习

FSM

Struct FSM

定义了状态机的三个阶段:EnterTickExit

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: 定义一个结构体,名称由 stateStruct 组合而成。例如,如果你传入 Idle,结构体名为 IdleStruct
  • FSM: state ## Struct 继承自 FSM,表示这是一个有限状态机的状态。
  • 构造函数 (state ## Struct()): 设置Enter、Tick和Exit的 lambda 函数:
    • Enter:进入状态时调用。
    • Tick:状态激活期间每帧调用一次,delta 表示时间差。
    • Exit:退出状态时调用。
    • 将状态注册到 UResourceManagerFSMMap 中,使用 EState::state 作为键值(例如 EState::Idle)。

2. 创建实例

state ## Struct state ## Struct;
  • 创建 state ## Struct 的实例,实例名与结构体名相同。如果 stateIdle,则实例名为 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(): 定义一个方法,用于转换到指定的状态。如果 stateIdle,方法名为 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 创建的状态机中的EnterTickExit都分别进行了初始化

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() {
}
posted @ 2024-08-21 12:03  Dream_moon  阅读(79)  评论(0)    收藏  举报