[UE]GameFeature浅析
GameFeature浅析
一套支持动态增删游戏玩法的框架,往 CoreGame 里 Add/Remove 其它游戏资源/逻辑;
以 Lyra(UE5.3) 为例,GameFeature 提供了很好的解耦资源/逻辑的方案,可以看出这里的 ShooterCore 被实现为了一个相对比较独立的 Plugin。
初始化
首先在游戏内,GameFeaturesSubsystem 管理着所有的 GameFeature,看到对应的初始化 UGameFeaturesSubsystem::Initialize,可以分为几步:
加载
Policy:根据GameFeaturesSubsystemSettings->GameFeaturesManagerClassName加载对应的GameSpecificPolicies注册资源回调:
UAssetManager::CallOrRegister_OnAssetManagerCreated注册
ConsoleCommand方便Debug
在资源加载完的回调后,会走到 UGameFeaturesSubsystem::OnAssetManagerCreated,进行 GameSpecificPolicies->InitGameFeatureManager,根据 Policies 执行初始化逻辑;
这里的 GameFeaturesProjectPolicies 决定了一些 GameFeaturePlugin 的加载规则,比如需要在什么地方(Server/Client) 加载什么 GameFeature,同时可以在这里的 Init 扩展需要的额外功能,比如注册一些 Observers、Subsystems。
1 | virtual void InitGameFeatureManager() override; |
在 UDefaultGameFeaturesProjectPolicies::InitGameFeatureManager 中会执行 UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugins(AdditionalFilter),根据 Filter 来加载 GameFeaturePlugin,可以发现,会进行状态机 GameFeaturePluginStateMachine 的初始状态设置并初始化。
状态机的底层的数据都存在 GameFeatureData 上,可以从这里面开始分析:
(需要注意的是,GameFeatureData 本身只记录了 Actions 和 PrimaryAssetTypesToScan,这里的 FeatureState 对应的是 GameFeaturePlugin 的对应状态机的信息,由 FGameFeaturesEditorModule::StartModule -> FGameFeatureDataDetailsCustomization::CustomizeDetails 注册进 Details 面板)
状态机
GameFeature 由一个 UGameFeaturesSubsystem : UEngineSubsystem 管理其生命周期:
classDiagram
class UGameFeaturesSubsystem {
GameFeaturePluginStateMachines : TMap[GameFeature, StateMachine]
ListGameFeaturePlugins()
LoadGameFeaturePlugin()
DeactivateGameFeaturePlugin()
UnloadGameFeaturePlugin()
ReleaseGameFeaturePlugin()
UninstallGameFeaturePlugin()
TerminateGameFeaturePlugin()
}
UGameFeaturesSubsystem..>UGameFeaturePluginStateMachine
class UGameFeaturePluginStateMachine {
AllStates : TUniquePtr[FGameFeaturePluginState]
CurrentStateInfo : FGameFeaturePluginStateInfo
SetDestination()
}
UGameFeaturePluginStateMachine..>FGameFeaturePluginState
class FGameFeaturePluginState {
TickHandle : FTSTicker-FDelegateHandle
BeginState()
UpdateState()
TryCancelState()
EndState()
}
UGameFeaturePluginStateMachine..>FGameFeaturePluginStateInfo
class FGameFeaturePluginStateInfo {
State : EGameFeaturePluginState
}
最基本的有 Load、Deactivate、Unload、Release、Uninstall、Terminate 几个功能,可以由外部调用。
核心内容围绕着每个 GameFeaturePlugin 对应的 StateMachine 展开;GameFeatureSubSystem 上记录着 TMap<FString, TObjectPtr<UGameFeaturePluginStateMachine>> GameFeaturePluginStateMachines ,也就是 PluginIdentifier->StateMachine 的多个映射。
最后都会走到 UGameFeaturesSubsystem::ChangeGameFeatureDestination 修改状态机的状态:
1 | void ChangeGameFeatureDestination(UGameFeaturePluginStateMachine* Machine, const FGameFeaturePluginStateRange& StateRange, FGameFeaturePluginChangeStateComplete CompleteDelegate); |
GameFeaturePluginStateMachine
主要维护对应 GameFeaturePlugin 的状态。
最重要的设置状态入口是 UGameFeaturePluginStateMachine::SetDestination。
1 | // UE5.0: 传入的目标状态为一个 GameFeaturePluginState |
可以发现 StateMachine 维护了一个 FGameFeaturePluginStateMachineProperties& StateProperties,记录了状态机运行中可以切换到的状态的一些属性,其中的 DestinationState 表示在状态变化中期望到达的状态(UE5.3 改为了状态区间,为了适配 Terminal 以及状态转移中不丢失对应的 CompletionHandle)
1 | /** 当前状态是否不在 Destination 内,不在的话说明需要尝试到一个新的 Destination,标记为 IsRunning */ |
FeaturePluginState
内部维护了很多状态:
可以被作为DestinationState的状态有:UnknownStatus、Uninstalled、StatusKnown、Installed、Registered、Load、Active、Terminal、其它ErrorStates。
状态间的关系如图所示:
flowchart LR Uninitialized-->*UnknownStatus *UnknownStatus-->CheckingStatus *UnknownStatus-->*Terminal CheckingStatus-->*StatusKnown *StatusKnown-->*Installed *StatusKnown-->Downloading *StatusKnown-->Uninstalling *StatusKnown-->*Terminal Downloading-->*Installed Uninstalling-->*Uninstalled *Uninstalled-->*Terminal *Uninstalled-->CheckingStatus Releasing-->*StatusKnown *Installed-->Mounting *Installed-->Releasing Mounting-->WaitingForDependencies Unmounting-->*Installed WaitingForDependencies-->Registering Registering-->*Registered *Registered-->Unregistering *Registered-->Loading Unregistering-->Unmounting Loading-->*Loaded *Loaded-->Activating *Loaded-->Unloading Unloading-->*Registered Activating-->*Active *Active-->Deactivating Deactivating-->*Loaded
状态修改完会通过UGameFeaturesSubsystem::Get().OnGameFeature... 进行通知,GameFeatureSubSystem 会调用到 Actions 的回调,同时提供了一个 CallbackObservers 通知已注册的 Observers。
1 | enum class EObserverCallback |
Actions
对于一个 GameFeature ,在 Register、Unregister、Load、Activate、Deactive 等行为时(也可以自己添加)会调用到 GameFeatureData 上配置的 Actions 的对应回调:
可以自定义各种各样的 Actions:
GameFeatureAction 中有一个关键 UGameFeatureAction_AddComponents ,其主要是将 Component 注册进对应的 Actor,应用到了另一个模块 ModularGameplay。
AddComponent 通过 ModularGameplay 的 UGameFrameworkComponentManager::AddComponentRequest 实现,简单介绍一下 ModularGameplay :
ModularGameplay
一套往 Actor 上注册 Component/ExtensionHandler 的解决方案。
这套框架核心由一个 UGameFrameworkComponentManager : USubSystem 在全局管理各个 ActorClass 的 Component/ExtensionHandler,维护 Component 的生命周期,并且提供接口供外部调用(Add/Remove/SendEvent 等)
ActorReceiver
注册 Receiver
首先将需要使用这套框架维护 Component 的 ActorClass 作为 Receiver ,通过 UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver 注册进 Manager;
同时,在注册的时候,会判断 ActorClass 是否已经提前注册的 ComponentClass 或者 EventDelegate ,如果有 ComponentClass 则会在此时创建对应实例 CreateComponentOnInstance,有 EventDelagte 则会通知一下 NAME_ReceiverAdded。
(一般在 AActor::PreInitializeComponents 调用)
移除 Receiver
通过 UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver 移除;
调用对应 Component 的销毁,通知一下 NAME_ReceiverRemoved。
(一般在 AActor::EndPlay 调用)
Component
classDiagram
class UGameFrameworkComponentManager
UGameFrameworkComponentManager : ReceiverClassToComponentClassMap
UGameFrameworkComponentManager : AddComponentRequest()
UGameFrameworkComponentManager : RemoveComponentRequest()
维护了一个 ActorClass->ComponentClass ,也就是 TMap<FComponentRequestReceiverClassPath, TSet<TObjectPtr<UClass>>> ReceiverClassToComponentClassMap,(其中 FComponentRequestReceiverClassPath 是一个结构体,用字符串数组记录 Class->Root 这条链路,作为 Class 的标识,不直接用 UClass 是因为以 Component 的视角进行操作,不期望在这里直接依赖 Receiver 对应模块的指针)
注册 Component
通过 UGameFrameworkComponentManager::AddComponentRequest 实现;
这里的 CompoentRequest 也就是一个 FComponentRequestHandle(OwningManager, ReceiverClass, ComponentClass);
内部维护了一个 RequestTrackingMap 用于 CompoentRequest 的计数,如果 ==1 则说明需要尝试创建实例;
(有可能不同的模块都依赖对应的 ComponentClass ,所以需要计数一下)
注册的时候判断对应的 Actor 是否已经 Initialize ,如果是,则 CreateComponentOnInstance。
CreateComponentOnInstance:创建 ComponentInstance ,维护 TMap<UClass*, TSet<FObjectKey>> ComponentClassToComponentInstanceMap 用于记录这个 ComponentClass 有哪些对应的 Instance
移除 Component
通过 UGameFrameworkComponentManager::RemoveComponentRequest 实现;
修改计数,==0 则执行销毁。
销毁的时候通过 ComponentClassToComponentInstanceMap 找到所有的 Component,判断对应的 Actor 是否是期望的,如果是,则销毁;
ExtensionHandler
和 Component 类似,也就是注册 Event 的部分;
classDiagram
class UGameFrameworkComponentManager
UGameFrameworkComponentManager : ReceiverClassToEventMap
UGameFrameworkComponentManager : AddExtensionHandler()
UGameFrameworkComponentManager : RemoveExtensionHandler()
UGameFrameworkComponentManager : SendExtensionEvent()
资源加载
在 FGameFeaturePluginState_Registering 状态时,会根据配置的 GameFeatureToAdd->GetPrimaryAssetTypesToScan(),执行对应的 UGameFeaturesSubsystem::AddGameFeatureToAssetManager,进行资源是否存在等判定之后,尝试加载资源到 AssetManager。
类似的,在 FGameFeaturePluginState_Unregistering 状态时,通过 UGameFeaturesSubsystem::RemoveGameFeatureFromAssetManager 进行资源卸载。
参考
Lyra (UE5.0-5.3) GameFeature 部分源码




