首页 > 其他分享 >FSM 设计模式学习

FSM 设计模式学习

时间:2024-08-21 12:04:31浏览次数:4  
标签:Tick State ## void FSM 学习 state delta 设计模式

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() {
}

标签:Tick,State,##,void,FSM,学习,state,delta,设计模式
From: https://www.cnblogs.com/Dreammoon/p/18371329

相关文章

  • Android Qcom USB Driver学习(五)
    前面的几篇都有涉及,所以本文学习一下pmicusbcharger都相关的vote机制OVP:OverVoltageProtection过压保护USB_IN:Inputcurrentlimit一般仅支持USB_IN即VBUS在输入(有些能支持DC_IN),APSD:autonomouspowersourcedetection运行于BC1.2SDP/CDP的检测完成......
  • 零基础学习人工智能—Python—Pytorch学习(六)
    前言本文主要讲神经网络的上半部分。另外,我发现我前面文章写的有歧义的地方还是挺多,虽然,已经改了一部分,但,可能还有没发现的,大家看的时候尽量多理解着看吧。本着目的是学会使用神经网络的开发,至于数学的部分,就能过就过吧。神经网络先学个例子先结合以前的知识理解一个例子,理......
  • 机器学习--序列到序列模型总结
    序列到序列(Seq2Seq)模型的发展历程中,随着技术的进步和研究的深入,出现了多种不同的架构。这些架构在编码器-解码器结构的基础上逐步演化,融合了多种改进策略和创新方法。以下是总结出的主要Seq2Seq模型架构:1.基础的RNNSeq2Seq模型编码器和解码器:最早的Seq2Seq模型使用简单的......
  • 随机森林学习笔记概述
    随机森林(RandomForest)是一种集成学习方法,它通过构建多个决策树并将它们的预测结果进行投票或平均来提高预测性能。随机森林在许多实际应用中表现出了很好的性能,尤其是在分类和回归问题上。以下是关于随机森林的一些学习笔记概述:1.基本概念  集成学习:通过组合多个弱学习......
  • 机器学习框架推理流程简述(以一项部署在windows上的MNN框架大模型部署过程为例子)
    一、写在前面公司正好有这个需求,故我这边简单接受进行模型的部署和demo程序的编写,顺便学习了解整个大模型的部署全流程。这篇博客会简单提到大模型部署的全流程,侧重点在推理这里。并且这篇博客也是结合之前的MNN部署流程来编写的,最好连下来一起看。MNN框架在WIN10上的部署MNN框......
  • 深入Java虚拟机JVM类加载学习笔记
    1.类加载过程----------以及风中叶老师在他的视频中给了我们一段程序,号称是世界上所有的Java程序员都会犯的错误加载---验证---准备---解析---初始化---使用---卸载诡异代码如下:packagetest01;classSingleton{ publicstaticSingletonsingleton=newSingleton();......
  • 学习Java第八周
    子类只能从被扩展的父类获得成员变量、方法和内部类(包括内部接口、枚举),不能获得构造器和初始化块。2.Java类只能有一个直接父类,实际上,Java类可以有无限多个间接父类。3.如果定义一个Java类时并未显式指定这个类的直接父类,则这个类默认扩展java.lang.Object类4.super限定public......
  • 开始步入Spring框架的学习
    Spring框架Spring概述Spring是一个广泛使用的Java企业级应用开源开发框架,它旨在简化企业级应用的开发。以下是Spring的详细概述:一、创建背景与目的创建背景:Spring框架的创建是为了解决企业级编程开发中的复杂性,实现敏捷开发。它最初由RodJohnson在2002年提出并随后创建......
  • 机械学习—零基础学习日志(如何理解概率论3)
    随机变量的函数分布一维随机变量分布,可以看到下图,X为不同情况的概率。而x如果是大于等于X,那么当x在40以内时,没有概率,为0。当x变大,在40-80之间,那么x大于X的概率为,0.7,所以随着x增大,概率会越来越高。同时概率是如下图所示,为离散型,间断性增加的。对于不同类型的,比如离散型,连续......
  • 程序员日常编码与提升式学习怎么平衡
    为什么要提升式学习工作过的人都知道,只要时间久了,工作的项目就会变得很熟悉,后面的工作就是体力活了,无非是照着之前的代码写一些逻辑,工作给人带来的提升就会越来越少,此时如果不学一些新东西的话就会如同温水组青蛙,时间久了,就会让自己的年龄和能力不匹配。有些提升式为了更......