【UE4 设计模式】状态模式 State Pattern
概述
描述
-
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
-
其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
-
有限状态机(FSMs)
- 拥有状态机所有可能状态的集合
- 状态机同时只能在一个状态
- 一连串的输入或事件被发送给状态机
- 每个状态都有一系列的转移,每个转移与输入和另一状态相关
- 动画状态机系统、行为树系统来理解
-
并发状态机
- 有些状态需要并行执行,例如动画状态机,经常分为上半身动画与下半身动画融合,如装备动作、射击动作、换弹动作与行走动作并行
-
层次状态机
- 状态中嵌套子状态,可以使用继承实现或者状态栈来实现
-
下推自动机
- 用栈来存储一系列状态。有限状态机有一个指向状态的指针,下推自动机有一栈指针
- 新状态压入栈中,“当前的”状态总是在栈顶
- 弹出最上面的状态,这个状态会被销毁,它下面的状态成为新状态。如UI界面管理
套路
- 环境类 Context,用于改变状态
- 抽象状态类 State
- 具体状态类 ConcreteState
使用场景
- 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
- 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。
- 示例
- OA办公系统中多种状态:尚未办理;正在办理;正在批示;正在审核
- TCP 连接状态
- 动画系统、AI行为树
- UI界面管理
优缺点
- 优点
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
- 缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
UE4 实践
-
在UE4中,实现切换UI状态
-
创建状态抽象类,以接口的形式
UINTERFACE(MinimalAPI) class UStateInterface : public UInterface { GENERATED_BODY() }; /** * */ class DESIGNPATTERNS_API IStateInterface { GENERATED_BODY() // Add interface functions to this class. This is the class that will be inherited to implement this interface. public: virtual void EnterState() = 0; virtual void ExitState() = 0; };
-
创建状态具体类UBaseStateWidget ,作为UI的父类,切换时使用
UCLASS() class DESIGNPATTERNS_API UBaseStateWidget : public UUserWidget, public IStateInterface { GENERATED_BODY() public: virtual void EnterState() override; virtual void ExitState() override; UFUNCTION(BlueprintNativeEvent,BlueprintCallable,Category="State Pattern") void OnEnterState(); UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "State Pattern") void OnExitState(); };
#include "BaseStateWidget.h" void UBaseStateWidget::EnterState() { OnEnterState(); } void UBaseStateWidget::ExitState() { OnExitState(); } void UBaseStateWidget::OnEnterState_Implementation() { AddToViewport(); } void UBaseStateWidget::OnExitState_Implementation() { RemoveFromParent(); }
-
创建状态管理类
UCLASS() class DESIGNPATTERNS_API AUIStateManager : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AUIStateManager(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // 改变状态 UFUNCTION(BlueprintCallable, Category = "State Pattern") void EnterState(TSubclassOf<UBaseStateWidget> StateWidgetClass); // 退出所有状态 UFUNCTION(BlueprintCallable, Category = "State Pattern") void ExitAllState(); // 当前状态实例 UPROPERTY(BlueprintReadWrite, Category = "State Pattern") UBaseStateWidget* CurrentStateWidget; private: // 存储状态实例 TMap<TSubclassOf<UBaseStateWidget>, UBaseStateWidget*> WidgetInstances; };
void AUIStateManager::EnterState(TSubclassOf<UBaseStateWidget> StateWidgetClass) { if (CurrentStateWidget != nullptr) { CurrentStateWidget->ExitState(); } if (WidgetInstances.Contains(StateWidgetClass)) { CurrentStateWidget = WidgetInstances.FindRef(StateWidgetClass); } else { APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0); CurrentStateWidget = CreateWidget<UBaseStateWidget>(PC, StateWidgetClass); WidgetInstances.Add(StateWidgetClass,CurrentStateWidget); } CurrentStateWidget->EnterState(); } void AUIStateManager::ExitAllState() { for (auto& Elem : WidgetInstances) { (Elem.Value)->ExitState(); } }
-
创建状态类UBaseStateWidget 的蓝图派生类
- WBP_Start —— 开始界面
- WBP_LoadingScreen —— 加载界面
- WBP_Save —— 存档界面
- WBP_Option —— 选项界面
-
创建 UIStateManager 蓝图派生类
-
效果
-
扩展,可进一步用栈状态机实现界面管理
参考
作者:砥才人
出处:https://www.cnblogs.com/shiroe
本系列文章为笔者整理原创,只发表在博客园上,欢迎分享本文链接,如需转载,请注明出处!