Lyra - UGameFrameworkComponentManager
UGameFrameworkComponentManager
游戏框架组件管理器 是 模块化Gameplay插件(Modular Gameplay plugin) 中的一个 游戏实例子系统(Game Instance Subsystem) 。它可以与 游戏功能插件(Game Feature Plugins)一起使用。 该子系统中实现的函数可以由 游戏功能操作(Game Feature Actions) 用于支持可扩展性。 游戏功能操作由一般Gameplay代码用于协调不同Gameplay对象之间的通信。管理器实现两个基本系统:扩展处理程序(Extension Handlers)和 初始化状态(Initialization States)。
扩展处理程序也就是比如监听所有的Controller,如果有Controller初始化,并且有状态流转都会出发这个扩展处理事件
初始化功能一般都要和 IGameFrameworkInitStateInterface 打配合
IGameFrameworkInitStateInterface
IGameFrameworkInitStateInterface 的作用,就是将一个组件原本混乱、单体的初始化过程(主要挤在BeginPlay里),拆分成一个清晰、有序、分阶段的“生命周期”。
它允许不同组件之间优雅地声明和等待依赖关系,从而构建出可预测、可扩展、无惧异步加载的复杂系统。
在传统的开发模式(全是 BeginPlay)中,剧组就像是一群没有排练过的人,导演一喊“开机”,演员、灯光、摄影、录音全部同时开始干活,结果往往是:演员还没换好衣服摄影就开始拍了,或者灯光还没架好录音就开始录了。在程序里,这就是所谓的“竞态条件” —— 组件 A 还没初始化完数据,组件 B 就尝试去读取它,导致游戏崩溃或逻辑错误。
IGameFrameworkInitStateInterface的核心接口
-
virtual FName GetFeatureName() const override { return NAME_ActorFeatureName; }返回这个组件在 GameFrameworkComponentManager 系统中的唯一标识名称(身份证号)
其他组件如果想查询或依赖这个组件的初始化状态,就必须使用这个函数返回的 FName
-
virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const override;这是一个条件检查函数。当 Manager 尝试将组件的状态从 CurrentState 推进到 DesiredState 时,会调用这个函数来询问:“现在可以进行这个状态转换吗?”
-
virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) override;这是一个状态改变回调函数。在 CanChangeInitState 返回 true 并且 Manager 成功地将组件的状态更新后,这个函数会被立即调用。
-
virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) override;这是一个事件监听器。前提是你必须在 BeginPlay 中调用 BindOnActorInitStateChanged() 来注册这个监听。
注册后,当同一个Actor上其他被Manager管理的组件的初始化状态发生变化时,这个函数就会被调用。
-
virtual void CheckDefaultInitialization() override;在这个函数内部,你会调用 UGameFrameworkComponentManager 提供的一些工具函数(如 ContinueInitStateChain),来触发一次从当前状态到最终状态的连续转换尝试
正常工作流程
首先:我们需要在Actor中去注册到 UGameFrameworkComponentManager
|
|
假设我们有4个 FGAmeplayTag: LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable, LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady
我们还有两个Component:
class LYRAGAME_API ULyraHeroComponent : public UPawnComponent, public IGameFrameworkInitStateInterfaceclass LYRAGAME_API ULyraPawnExtensionComponent : public UPawnComponent, public IGameFrameworkInitStateInterface
所以流程为:
-
先把这四个注册进去,比如在GameInstance注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14void ULyraGameInstance::Init() { Super::Init(); UGameFrameworkComponentManager* ComponentManager = GetSubsystem<UGameFrameworkComponentManager>(this); if (ensure(ComponentManager)) { ComponentManager->RegisterInitState(LyraGameplayTags::InitState_Spawned, false, FGameplayTag()); ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataAvailable, false, LyraGameplayTags::InitState_Spawned); ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataInitialized, false, LyraGameplayTags::InitState_DataAvailable); ComponentManager->RegisterInitState(LyraGameplayTags::InitState_GameplayReady, false, LyraGameplayTags::InitState_DataInitialized); } } -
随后在两个Component初始化,并记得销毁状态(两个Component都一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26void ULyraPawnExtensionComponent::OnRegister() { Super::OnRegister(); // Register with the init state system early, this will only work if this is a game world RegisterInitStateFeature(); } void ULyraPawnExtensionComponent::BeginPlay() { Super::BeginPlay(); // Listen for changes to all features BindOnActorInitStateChanged(NAME_None, FGameplayTag(), false); // Notifies state manager that we have spawned, then try rest of default initialization ensure(TryToChangeInitState(LyraGameplayTags::InitState_Spawned)); CheckDefaultInitialization(); } void ULyraPawnExtensionComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { UnregisterInitStateFeature(); Super::EndPlay(EndPlayReason); } -
只要BeginPlay触发了,
CheckDefaultInitialization()这个就会执行,当然你在其他地方也可以执行,只要满足你的需要1 2 3 4 5 6 7 8 9 10void ULyraPawnExtensionComponent::CheckDefaultInitialization() { // Before checking our progress, try progressing any other features we might depend on CheckDefaultInitializationForImplementers(); static const TArray<FGameplayTag> StateChain = { LyraGameplayTags::InitState_Spawned, LyraGameplayTags::InitState_DataAvailable, LyraGameplayTags::InitState_DataInitialized, LyraGameplayTags::InitState_GameplayReady }; // This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready ContinueInitStateChain(StateChain); } -
随后就会执行 CanChangeInitState,每当当前状态通过,返回 true 后,就会调用一次 HandleChangeInitState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29bool ULyraPawnExtensionComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const { check(Manager); APawn* Pawn = GetPawn<APawn>(); if (!CurrentState.IsValid() && DesiredState == LyraGameplayTags::InitState_Spawned) { return true; } if (CurrentState == LyraGameplayTags::InitState_Spawned && DesiredState == LyraGameplayTags::InitState_DataAvailable) { if (!PawnData) { return false; } return true; } else if (CurrentState == LyraGameplayTags::InitState_DataAvailable && DesiredState == LyraGameplayTags::InitState_DataInitialized) { return Manager->HaveAllFeaturesReachedInitState(Pawn, LyraGameplayTags::InitState_DataAvailable); } else if (CurrentState == LyraGameplayTags::InitState_DataInitialized && DesiredState == LyraGameplayTags::InitState_GameplayReady) { return true; } return false; }1 2 3 4 5 6 7void ULyraPawnExtensionComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) { if (DesiredState == LyraGameplayTags::InitState_DataInitialized) { // This is currently all handled by other components listening to this state change } } -
当同一个Actor上其他被Manager管理的组件的初始化状态发生变化时,就会调用:
1 2 3 4 5 6 7 8 9 10 11void ULyraPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params) { // If another feature is now in DataAvailable, see if we should transition to DataInitialized if (Params.FeatureName != NAME_ActorFeatureName) { if (Params.FeatureState == LyraGameplayTags::InitState_DataAvailable) { CheckDefaultInitialization(); } } }
协作方法
-
bool UGameFrameworkComponentManager::HaveAllFeaturesReachedInitState(AActor* Actor, FGameplayTag RequiredState, FName ExcludingFeature) const询问 “对于指定的这个 Actor(Pawn),所有在 Manager 中注册过的、并且与这个 Actor 相关的功能(Features),是否都已经达到或超过了给定的状态(InitState_DataAvailable)?”
-
bool UGameFrameworkComponentManager::HasFeatureReachedInitState(AActor* Actor, FName FeatureName, FGameplayTag FeatureState) const它在问 GameFrameworkComponentManager:“嘿,我的同事 PawnExtensionComponent,它是不是已经到达过 DataInitialized 状态?”
注意是:到达过 xxx 状态,也就是说如果目标已经经过这个状态也会返回 true