[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 部分源码