关于 ue-game-features
面向 UE 5.8 开发者的 Game Features 与 Modular Gameplay 参考技能,介绍功能插件、状态管理、动作和组件注入。内容按开发任务组织,并以引擎头文件验证 API。
主要功能
- 01Game Feature 插件布局与描述符
- 02插件状态及子系统控制
- 03自定义 Game Feature Action
- 04向 Actor 注入组件
- 05初始化状态系统与项目策略
适用场景
- 01创建可独立加载的游戏功能插件
- 02运行时加载、激活或卸载功能
- 03编写功能激活与停用逻辑
- 04协调模块化组件的初始化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-game-features安装后在 Claude Code 中输入/ue-game-features确认已加载。
附带文件references/experience-system.mdreferences/game-feature-patterns.md
详细介绍
UE 游戏功能与模块化游戏玩法
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;较旧的形式列在“已弃用 — 请勿使用”下。
游戏功能(在 5.8 中为 Beta)将游戏玩法打包为独立插件,位于 Plugins/GameFeatures/ 下。UGameFeaturesSubsystem(一个 UEngineSubsystem)通过状态机驱动每个插件;插件内的 UGameFeatureData 主数据资产包含一个实例化的 UGameFeatureAction 对象列表,这些对象会在注册、加载、激活和停用时运行。模块化游戏玩法(在 5.8 中为 Beta)提供 UGameFrameworkComponentManager(一个 UGameInstanceSubsystem),用于向选择加入的 Actor 注入组件;此外还提供 UGameFrameworkComponent 基类和初始化状态系统,用于协调独立加载的功能之间的初始化顺序。Build.cs 模块:GameFeatures 和 ModularGameplay。GameFeatures 模块已公开依赖 ModularGameplay 和 DataRegistry,因此只使用 Actions 的代码仅需依赖 GameFeatures。
上下文
如果 .agents/ue-project-context.md 存在,请阅读该文件(模块名称、约定、已启用的插件、GAS/网络设置)。如果文件不存在,也请继续。
根据请求和代码库确定对应领域。只有在两种合理解读会导致不同代码时才提问。
| 请求涉及…… | 前往 |
|---|---|
创建功能插件、.uplugin 键、文件夹布局、UGameFeatureData | 插件布局与描述文件 |
| 在运行时激活或卸载功能、插件 URL、插件状态 | 插件状态与子系统 |
| 编写在功能开启或关闭时运行的 Action | 编写 UGameFeatureAction |
| 选择现成的 Action,而不是自行编写 | 引擎提供的 Actions |
| 从功能向 Actor 添加组件、扩展事件 | 组件注入 |
| 协调分别加载的组件之间的初始化顺序 | 初始化状态系统 |
| 为 Pawn/Controller/PlayerState/GameState 组件选择基类 | 模块化组件基类 |
| 筛选要加载的插件、客户端/服务端数据、状态观察器 | 项目策略与观察器 |
| Lyra 风格的体验组合(示例代码,不是引擎 API) | references/experience-system.md |
| 完整模板:自定义 Action、接收器设置、策略子类 | references/game-feature-patterns.md |
插件布局和描述符
Game Feature 插件是一个普通插件,位于 <Project>/Plugins/GameFeatures/ 或 <AdditionalPluginDirectory>/GameFeatures/ 下。该位置决定它是否为 Game Feature 插件——UGameFeaturesSubsystemSettings::IsValidGameFeaturePlugin 会根据描述符路径判断它是否位于这些文件夹中。.uplugin schema 中没有插件级别的 "Type" 键。
Plugins/GameFeatures/
├── MyFeature/
│ ├── MyFeature.uplugin
│ ├── Content/MyFeature.uasset (UGameFeatureData, named after the plugin)
│ └── Source/MyFeatureRuntime/
└── MyFeatureRules/
├── MyFeatureRules.uplugin
└── Content/MyFeatureRules.uasset
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "My Feature",
"Description": "Adds my gameplay feature",
"Category": "Game Features",
"CreatedBy": "",
"CanContainContent": true,
"ExplicitlyLoaded": true,
"BuiltInInitialFeatureState": "Active",
"Modules": [
{ "Name": "MyFeatureRuntime", "Type": "Runtime", "LoadingPhase": "Default" }
],
"Plugins": [
{ "Name": "GameFeatures", "Enabled": true },
{ "Name": "ModularGameplay", "Enabled": true }
]
}
| 键 | 含义 |
|---|---|
ExplicitlyLoaded | 插件管理器不会在启动时挂载该插件;由该功能的状态机负责挂载和卸载。功能插件始终设为 true。 |
CanContainContent | 插件包含 Content/ 目录(创建 UGameFeatureData 资源所必需)。 |
BuiltInInitialFeatureState | 内置插件在启动时推进到的状态:"Installed"、"Registered"、"Loaded" 或 "Active"。未知值会记录错误并回退到 Active。 |
EnabledByDefault | 无需在 .uproject 中添加条目即可启用插件。 |
Modules[].Type | ModuleHostType 名称:Runtime、RuntimeNoCommandlet、Editor、Developer、Program、…… |
Modules[].LoadingPhase | ModuleLoadingPhase 名称:Default、PreDefault、PostConfigInit、PostEngineInit、…… |
仅包含内容的功能插件会完全省略 Modules。添加模块意味着添加一个 Build.cs;请参阅 ue-module-build-system。
UGameFeatureData
UGameFeatureData : UPrimaryDataAsset 是子系统为插件加载的资源。它的两个可编辑属性受保护;请通过访问器读取:
const TArray<UGameFeatureAction*>& FeatureActions = FeatureData->GetActions();
const TArray<FPrimaryAssetTypeInfo>& ScanTypes = FeatureData->GetPrimaryAssetTypesToScan();
Actions 是一个 Instanced 数组,其中包含直接在资源中创建的 UGameFeatureAction 子类。PrimaryAssetTypesToScan 告知 Asset Manager 在功能注册时要扫描插件中的哪些主资源类型;请参阅 ue-data-assets-tables。
只有在需要更改 GetPrimaryAssetTypesToScan 或在编辑器中更改 GetDisallowedActions 时,才继承 UGameFeatureData。
插件状态和子系统
目标状态按顺序完整排列;转换状态和错误状态位于目标状态之间。EGameFeaturePluginState 是由 GAME_FEATURE_PLUGIN_STATE_LIST 宏生成的普通枚举。
| 目标状态 | 含义 |
|---|---|
Terminal | 移除状态机之前的最终状态。 |
UnknownStatus | 仅知晓 URL。 |
StatusKnown | 已确认可用(位于磁盘或某个包中)。 |
Uninstalled | 插件的本地数据已移除。 |
Installed | 文件位于本地存储中,但尚未注册。 |
Registered | 已发现并注册资源;操作已接收到 OnGameFeatureRegistering。 |
Loaded | 插件代码和内容已载入内存,但尚未影响游戏。 |
Active | 操作已应用;该功能会影响游戏。 |
日志和 GetPluginState 结果中会出现的转换状态包括 CheckingStatus、Downloading、Mounting、Registering、Loading、ActivatingDependencies、Activating、Deactivating、Unloading、Unregistering、Unmounting、Releasing、Uninstalling,以及对应的 Error* 状态。
另外还有两个枚举:EGameFeatureTargetState(Installed、Registered、Loaded、Active)是传递给 ChangeGameFeatureTargetState 的目标状态;EBuiltInAutoState(Invalid、Installed、Registered、Loaded、Active)是解析 BuiltInInitialFeatureState 时使用的枚举。
通过代码驱动插件
#include "GameFeaturesSubsystem.h"
void UMyFeatureManager::EnableMyFeature()
{
UGameFeaturesSubsystem& GFS = UGameFeaturesSubsystem::Get();
FString PluginURL;
if (!GFS.GetPluginURLByName(TEXT("MyFeature"), PluginURL))
{
UE_LOG(LogMyGame, Error, TEXT("MyFeature is not a discovered game feature plugin"));
return;
}
GFS.LoadAndActivateGameFeaturePlugin(PluginURL,
FGameFeaturePluginLoadComplete::CreateUObject(this, &UMyFeatureManager::HandleFeatureLoaded));
}
void UMyFeatureManager::HandleFeatureLoaded(const UE::GameFeatures::FResult& Result)
{
if (Result.HasError())
{
UE_LOG(LogMyGame, Error, TEXT("MyFeature failed: %s"), *Result.GetError());
}
}
GetPluginURLByName(FStringView PluginName, FString& OutPluginURL) 是为内置插件构建 URL 的安全方式。静态构建器 UGameFeaturesSubsystem::GetPluginURL_FileProtocol(PluginDescriptorPath) 和 GetPluginURL_InstallBundleProtocol(PluginName, BundleName) 会生成 file: 和 installbundle: URL(EGameFeaturePluginProtocol::File、::InstallBundle)。
| 调用 | 效果 |
|---|---|
LoadGameFeaturePlugin(URL, CompleteDelegate) | 推进到 Loaded。 |
LoadAndActivateGameFeaturePlugin(URL, CompleteDelegate) | 推进到 Active。 |
RegisterGameFeaturePlugin(URL, CompleteDelegate) | 推进到 Registered。 |
ChangeGameFeatureTargetState(URL, EGameFeatureTargetState::Registered, CompleteDelegate) | 移动到任意目标状态。 |
DeactivateGameFeaturePlugin(URL) | 回退到 Loaded。 |
UnloadGameFeaturePlugin(URL, /*bKeepRegistered=*/ false) | 回退到 Registered 或 Installed。 |
TerminateGameFeaturePlugin(URL) | 拆除状态机。 |
CancelGameFeatureStateChange(URL) | 中止正在进行的状态转换。 |
以上每个完成委托都是 FGameFeaturePluginChangeStateComplete 的别名,即 DECLARE_DELEGATE_OneParam(..., const UE::GameFeatures::FResult&)。接受 TConstArrayView<FString> PluginURLs 的重载通过 FMultipleGameFeaturePluginsLoaded 报告结果,它是一个 TDelegate<void(const TMap<FString, UE::GameFeatures::FResult>&)>。
查询:GetPluginState(URL)、IsGameFeaturePluginActive(URL, bCheckForActivating)、IsGameFeaturePluginActiveByName(PluginName, bCheckForActivating)、IsGameFeaturePluginRegistered(URL, bCheckForRegistering)、IsGameFeaturePluginLoaded(URL)、IsGameFeaturePluginInErrorState(URL)、GetActivePluginNames()。
用于测试的控制台命令(GameFeaturesSubsystem.cpp:583-661,接受插件名称或 URL):ListGameFeaturePlugins [-activeonly] [-csv]、LoadGameFeaturePlugin、DeactivateGameFeaturePlugin、UnloadGameFeaturePlugin、ReleaseGameFeaturePlugin、CancelGameFeaturePlugin、TerminateGameFeaturePlugin。LoadGameFeaturePlugin 控制台命令会调用 LoadAndActivateGameFeaturePlugin,并且带有 ECVF_Cheat 标记;而同名 C++ 函数只会推进到 Loaded。
编写 UGameFeatureAction
UGameFeatureAction 是一个从 UObject 派生的 UCLASS(MinimalAPI, DefaultToInstanced, EditInlineNew, Abstract)。DefaultToInstanced 和 EditInlineNew 使实例能够以内联方式创建在 Actions 数组中。可重写的虚函数,原文如下:
virtual void OnGameFeatureRegistering() {}
virtual void OnGameFeatureUnregistering() {}
virtual void OnGameFeatureLoading() {}
virtual void OnGameFeatureUnloading() {}
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context);
virtual void OnGameFeatureActivating() {}
virtual void OnGameFeatureActivated() {}
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) {}
请重写带上下文参数的 OnGameFeatureActivating 版本;该版本的基类实现会调用无参数版本,因此同时重写两者会导致代码运行两次。IsDataValid 是 UObject 的重写函数,不是 UGameFeatureAction 的成员,并且仅在编辑器中可用。
// MyGameFeatureAction_GrantTag.h
#pragma once
#include "GameFeatureAction.h"
#include "GameplayTagContainer.h"
#include "MyGameFeatureAction_GrantTag.generated.h"
struct FGameFeatureActivatingContext;
struct FGameFeatureDeactivatingContext;
struct FWorldContext;
UCLASS(meta = (DisplayName = "My Grant Tag"))
class MYGAME_API UMyGameFeatureAction_GrantTag : public UGameFeatureAction
{
GENERATED_BODY()
public:
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context) override;
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override;
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
UPROPERTY(EditAnywhere, Category = "Tags")
FGameplayTag TagToGrant;
private:
void ApplyToWorld(const FWorldContext& WorldContext);
};
一个 Action 实例会由该功能适用的所有 World 共享,因此切勿在普通成员变量中保存每个 World 的状态。使用上下文筛选 World,并通过 FObjectKey 为状态建立索引:
void UMyGameFeatureAction_GrantTag::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
{
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
{
if (Context.ShouldApplyToWorldContext(WorldContext))
{
ApplyToWorld(WorldContext);
}
}
}
FGameFeatureActivatingContext 和 FGameFeatureDeactivatingContext 都派生自 FGameFeatureStateChangeContext,后者提供 ShouldApplyToWorldContext(const FWorldContext&)、ShouldApplyUsingOtherContext(const FGameFeatureStateChangeContext&) 和 SetRequiredWorldContextHandle(FName)。
需要异步工作的停用流程必须保持状态机开启。FGameFeatureDeactivatingContext::PauseDeactivationUntilComplete(FString InPauserTag) 会返回一个 FSimpleDelegate,你必须在游戏线程上的所有执行路径中调用它:
void UMyGameFeatureAction_GrantTag::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
{
FSimpleDelegate Resume = Context.PauseDeactivationUntilComplete(TEXT("MyGrantTag_Cleanup"));
StartAsyncCleanup(Resume); // must call Resume.ExecuteIfBound() when finished
}
包含 World 跟踪和 RAII 句柄模式的完整模板见 references/game-feature-patterns.md。
引擎提供的操作
| 操作 | 用途 |
|---|---|
UGameFeatureAction_AddComponents | 向 UGameFrameworkComponentManager 添加 Actor↔组件生成请求。 |
UGameFeatureAction_AddCheats | 为每个玩家添加作弊管理器扩展。 |
UGameFeatureAction_AddActorFactory | 在插件注册时注册 Actor 工厂。 |
UGameFeatureAction_AddChunkOverride | 将插件资源烹饪到指定的块 ID 中。 |
UGameFeatureAction_AddWPContent | 将 World Partition 内容作为内容包添加。 |
UGameFeatureAction_AddWorldPartitionContent | 为插件添加 World Partition 内容。 |
UGameFeatureAction_DataRegistry | 加载并初始化一组 Data Registry。 |
UGameFeatureAction_DataRegistrySource | 向现有的 Data Registry 添加源资源。 |
UGameFeatureAction_AudioActionBase | 影响音频引擎的操作的 Abstract 基类。 |
UIrisFilterGameFeatureAction | 创建 Iris 网络筛选器。 |
UGameFeatureAction_AddComponents 是大多数功能都需要的操作。它持有 TArray<FGameFeatureComponentEntry> ComponentList,每个 FGameFeatureComponentEntry 都包含 TSoftClassPtr<AActor> ActorClass(meta=(AllowAbstract="True"))、TSoftClassPtr<UActorComponent> ComponentClass、位字段 uint8 bClientComponent:1 和 uint8 bServerComponent:1,以及 uint8 AdditionFlags(meta=(Bitmask, BitmaskEnum="/Script/ModularGameplay.EGameFrameworkAddComponentFlags"))。
对于各处都需要的组件,同时设置 bClientComponent 和 bServerComponent;对于权威游戏逻辑,仅设置服务器端;对于视觉效果,仅设置客户端。此操作会为每个游戏实例保留一个 TSharedPtr<FComponentRequestHandle>,并在停用时释放它们,从而移除注入的组件。
组件注入
UGameFrameworkComponentManager 是一个 UGameInstanceSubsystem。可以通过 Actor 获取它:
#include "Components/GameFrameworkComponentManager.h"
UGameFrameworkComponentManager* CompMgr = UGameFrameworkComponentManager::GetForActor(MyActor);
GetForActor(const AActor* Actor, bool bOnlyGameWorlds = true) 在游戏世界之外会返回 null。在持有游戏实例的地方,也可以使用 GetGameInstance()->GetSubsystem<UGameFrameworkComponentManager>()。
接收方
只有在 Actor 注册自身后,它才会接收注入的组件:
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
静态辅助函数会为 Actor 找到管理器,并转发到 AddReceiver(AActor* Receiver, bool bAddOnlyInGameWorlds = true) 和 RemoveReceiver(AActor* Receiver);你也可以直接在管理器上调用这两个函数。
请求与扩展处理程序
TSharedPtr<FComponentRequestHandle> RequestHandle = CompMgr->AddComponentRequest(
TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
UMyHealthComponent::StaticClass(),
EGameFrameworkAddComponentFlags::AddUnique);
FComponentRequestHandle 使用 RAII:只要需要持续注入,就让它保持存活;销毁它即可移除请求及其创建的组件。它还提供 IsValid(),当所属管理器不存在时,该函数会返回 false。
EGameFrameworkAddComponentFlags | 值 | 行为 |
|---|---|---|
None | 0 | 始终在接收方上创建组件。 |
AddUnique | 0b00000001 | 如果接收方已拥有该类的组件,则跳过。 |
AddIfNotChild | 0b00000010 | 如果请求的类是现有组件类的子类,则跳过。 |
UseAutoGeneratedName | 0b00000100 | 生成新名称,而不是重用类名。 |
扩展处理程序会观察接收方,而不是添加组件。委托在管理器内部声明为 DECLARE_DELEGATE_TwoParams(FExtensionHandlerDelegate, AActor*, FName):
ExtensionHandle = CompMgr->AddExtensionHandler(
TSoftClassPtr<AActor>(AMyCharacter::StaticClass()),
UGameFrameworkComponentManager::FExtensionHandlerDelegate::CreateUObject(
this, &UMyFeatureManager::HandleActorExtension));
void UMyFeatureManager::HandleActorExtension(AActor* Actor, FName EventName)
{
if (EventName == UGameFrameworkComponentManager::NAME_GameActorReady)
{
// Actor has finished its default initialization
}
}
五个 static FName 事件常量为 NAME_ReceiverAdded、NAME_ReceiverRemoved、NAME_ExtensionAdded、NAME_ExtensionRemoved 和 NAME_GameActorReady。可以通过 CompMgr->SendExtensionEvent(Receiver, EventName, bOnlyInGameWorlds) 发送自定义事件,或者使用静态函数 UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(Receiver, EventName, bOnlyInGameWorlds)。
初始化状态系统
初始化状态用于解决来自不同插件的组件按顺序初始化的问题。状态是 FGameplayTag 值,按顺序在管理器上注册;关于如何声明这些标签,请参阅 ue-gameplay-tags-messaging。
CompMgr->RegisterInitState(TAG_InitState_Spawned, false, FGameplayTag());
CompMgr->RegisterInitState(TAG_InitState_DataAvailable, false, TAG_InitState_Spawned);
CompMgr->RegisterInitState(TAG_InitState_DataInitialized, false, TAG_InitState_DataAvailable);
CompMgr->RegisterInitState(TAG_InitState_GameplayReady, false, TAG_InitState_DataInitialized);
RegisterInitState(FGameplayTag NewState, bool bAddBefore, FGameplayTag ExistingState) 会在现有状态之前或之后插入新状态,因此引擎不会提供固定的状态列表——状态列表由项目定义。只需注册一次状态,例如在 UGameInstanceSubsystem 或项目的策略类中注册。
| 管理器调用 | 用途 |
|---|---|
ChangeFeatureInitState(Actor, FeatureName, Implementer, FeatureState) | 推进一个功能;返回 bool。 |
GetInitStateForFeature(Actor, FeatureName) | 当前状态标签。 |
HasFeatureReachedInitState(Actor, FeatureName, FeatureState) | 检查一个功能。 |
HaveAllFeaturesReachedInitState(Actor, RequiredState, ExcludingFeature) | 检查整个 Actor。 |
IsInitStateAfterOrEqual(FeatureState, RelativeState) | 按注册顺序比较两个标签。 |
GetImplementerForFeature(Actor, FeatureName, RequiredState) | 查找实现某项功能的对象。 |
RegisterAndCallForActorInitState(Actor, FeatureName, RequiredState, Delegate, bCallImmediately) | 等待一个 Actor;返回 FDelegateHandle。 |
RegisterAndCallForClassInitState(ActorClass, FeatureName, RequiredState, Delegate, bCallImmediately) | 等待某个类的所有 Actor;返回 FDelegateHandle。 |
UnregisterActorInitStateDelegate(Actor, Handle) / UnregisterClassInitStateDelegate(ActorClass, Handle) | 移除原生绑定(接受 FDelegateHandle&)。 |
原生委托为 DECLARE_DELEGATE_OneParam(FActorInitStateChangedDelegate, const FActorInitStateChangedParams&)。FActorInitStateChangedParams 是一个 USTRUCT,包含 OwningActor、FeatureName、Implementer 和 FeatureState。
IGameFrameworkInitStateInterface
在组件上实现 IGameFrameworkInitStateInterface(UINTERFACE UGameFrameworkInitStateInterface,NotBlueprintable),即可使用标准的状态推进流程。值得重写的虚函数如下,摘自头文件:
virtual FName GetFeatureName() const { return NAME_None; }
virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const { return true; }
virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) {}
virtual void CheckDefaultInitialization() {}
virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) {}
其余函数由你调用,而不是重写:
| 调用 | 效果 |
|---|---|
RegisterInitStateFeature() | 向管理器注册此实现者(在 OnRegister 中调用)。 |
UnregisterInitStateFeature() | 移除注册和已绑定的委托(在 EndPlay 中调用)。 |
BindOnActorInitStateChanged(FeatureName, RequiredState, bCallIfReached) | 绑定 OnActorInitStateChanged,并将句柄存储在 ActorInitStateChangedHandle 中。 |
TryToChangeInitState(DesiredState) | 依次运行 CanChangeInitState、HandleChangeInitState,然后通知管理器。 |
ContinueInitStateChain(const TArray<FGameplayTag>& InitStateChain) | 从当前状态遍历状态链,对每个步骤运行 CanChangeInitState 和 HandleChangeInitState 并通知管理器;返回已到达的最远状态。 |
CheckDefaultInitializationForImplementers() | 对同一 Actor 的每个其他功能调用 CheckDefaultInitialization。 |
HasReachedInitState(DesiredState) / GetInitState() | 通过管理器查询此功能的状态。 |
CheckDefaultInitialization 重写通常会调用 CheckDefaultInitializationForImplementers(),然后使用项目的有序标签列表调用 ContinueInitStateChain;完整的组件实现见 references/game-feature-patterns.md。
模块化组件基类
| 基类 | 所需的所有者 | 类型化访问器 |
|---|---|---|
UGameFrameworkComponent | 任意 AActor | GetGameInstance<T>()、GetGameInstanceChecked<T>()、HasAuthority() |
UPawnComponent | APawn | GetPawn<T>()、GetPawnChecked<T>()、GetPlayerState<T>()、GetController<T>() |
UControllerComponent | AController | GetController<T>()、GetControllerChecked<T>()、GetPawn<T>()、GetViewTarget<T>()、GetPawnOrViewTarget<T>()、GetPlayerState<T>()、GetPlayer<T>() |
UPlayerStateComponent | APlayerState | GetPlayerState<T>()、GetPlayerStateChecked<T>() |
UGameStateComponent | AGameStateBase | GetGameState<T>()、GetGameStateChecked<T>()、GetGameMode<T>() |
这五个类都位于 ModularGameplay 中,头文件为 #include "Components/<Name>.h"。UGameFrameworkComponent 派生自 UActorComponent,并标记为 Blueprintable, BlueprintType。对于注入的组件,优先使用这些类,而非原始的 UActorComponent:这些访问器会通过 static_assert 根据所有者类型进行检查,且初始化状态接口就是围绕它们设计的。
项目策略与观察器
UGameFeaturesProjectPolicies 决定允许加载哪些插件,以及每种构建类型加载哪些数据。在项目设置中选择子类后,会写入:
[/Script/GameFeatures.GameFeaturesSubsystemSettings]
GameFeaturesManagerClassName=/Script/MyGame.MyGameFeaturesProjectPolicies
实用的虚函数:InitGameFeatureManager()、ShutdownGameFeatureManager()、IsPluginAllowed(const FString& PluginURL, FString* OutReason) const、GetGameFeatureLoadingMode(bool& bLoadClientData, bool& bLoadServerData) const、ShouldReadPluginDetails(const FString& PluginDescriptorFilename) const、GetPreloadBundleStateForPlugin(const FString& PluginName) const、IsLoadingStartupPlugins() const。UDefaultGameFeaturesProjectPolicies 是回退实现。子类模板见 references/game-feature-patterns.md。
UGameFeaturesSubsystemSettings::LoadStateClient 和 ::LoadStateServer 是用于客户端/服务器资源筛选的 FName 资源包状态;该设置类还包含 EnabledPlugins、DisabledPlugins 和 AdditionalPluginMetadataKeys。
对于不依赖某个功能数据资源的跨领域响应,可实现 IGameFeatureStateChangeObserver 并注册:
UGameFeaturesSubsystem::Get().AddObserver(MyObserver,
UGameFeaturesSubsystem::EObserverPluginStateUpdateMode::CurrentAndFuture);
EObserverPluginStateUpdateMode 为 FutureOnly 或 CurrentAndFuture;后者会在添加时重放当前插件状态,当项目中插件较多时开销较大。使用 RemoveObserver(MyObserver) 移除。观察器钩子包括 OnGameFeatureRegistering(const UGameFeatureData*, const FString& PluginName, const FString& PluginURL)、OnGameFeatureLoading(const UGameFeatureData*, const FString& PluginURL)、OnGameFeatureActivating(const UGameFeatureData*, const FString& PluginURL)、OnGameFeatureDeactivating(const UGameFeatureData*, FGameFeatureDeactivatingContext& Context, const FString& PluginURL),以及下载和挂载钩子。只要涉及数据,就优先在功能的数据资源上使用 UGameFeatureAction。
已弃用 — 请勿使用
| 请勿使用 | 在 5.8 中使用 | 来源 |
|---|---|---|
.uplugin 中的 "Type": "GameFeature" | 将插件放在 Plugins/GameFeatures/ 下,并设置 "ExplicitlyLoaded": true | PluginDescriptor.cs 中没有插件级别的 Type 键 |
"BuiltInAutoRegister" / "BuiltInAutoLoad" / "BuiltInAutoActivate" | "BuiltInInitialFeatureState" | GameFeaturesSubsystem.cpp:5079-5090 中的旧版回退逻辑 |
AddObserver(Observer) | AddObserver(Observer, EObserverPluginStateUpdateMode::CurrentAndFuture) | GameFeaturesSubsystem.h:488 中的 UE_DEPRECATED(5.7) |
IsPluginAllowed(PluginURL) | IsPluginAllowed(PluginURL, OutReason) | GameFeaturesProjectPolicies.h:102 中的 UE_DEPRECATED(5.6) |
GetStreamingAssetInstallBundles(PluginURL) | GetStreamingAssetInstallModes(PluginURL, InstallBundleNames) | GameFeaturesProjectPolicies.h:154 中的 UE_DEPRECATED(5.6) |
FInstallBundlePluginProtocolOptions::ReleaseInstallBundleFlags | 无 — 释放标志会在内部应用 | GameFeaturesSubsystem.h:409 中的 UE_DEPRECATED(5.6) |
基于 FString 的插件详情(FPluginString) | 设置 UE_GAME_FEATURE_PLUGIN_DETAILS_USE_UTF8_STRING=1 并使用 UTF8 形式 | GameFeaturesSubsystem.h:258 中的 UE_DEPRECATED(5.8) |
不带参数重写 OnGameFeatureActivating() | 重写 OnGameFeatureActivating(FGameFeatureActivatingContext& Context) | GameFeatureAction.h:38-39 中的“旧式激活函数” |
GetGameFeaturePluginState(URL) | GetPluginState(URL) | 这不是引擎函数;实际调用位于 GameFeaturesSubsystem.h:731 |
直接访问 UGameFeatureData::Actions | GetActions() | Actions 在 GameFeatureData.h:120 中受保护;访问器位于 :102 |
ULyraExperienceDefinition、ULyraExperienceManagerComponent、UGameFeatureAction_AddInputContextMapping | Lyra 示例代码,不是引擎 API — 请编写自己的等效实现,参见 references/experience-system.md | 不在任何 5.8 引擎头文件中 |
常见错误
虚构插件 Type: .uplugin 没有插件级别的 Type 键,因此 "Type": "GameFeature" 会被静默忽略,插件也不会成为功能;请将插件放在 Plugins/GameFeatures/ 下,并设置 "ExplicitlyLoaded": true。
忘记注册接收方: 组件只会注入已注册的 Actor;忘记注册时不会记录任何日志。
// WRONG — no components are ever added
void AMyCharacter::BeginPlay() { Super::BeginPlay(); }
// RIGHT
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
丢弃 FComponentRequestHandle: 该句柄采用 RAII,因此丢弃它会立即移除请求。
// WRONG — the temporary destructs at the end of the statement
CompMgr->AddComponentRequest(ActorClass, UMyHealthComponent::StaticClass(), AdditionFlags);
// RIGHT — store it for the lifetime of the injection
RequestHandle = CompMgr->AddComponentRequest(ActorClass, UMyHealthComponent::StaticClass(), AdditionFlags);
将组件管理器当作引擎子系统获取: 它是 UGameInstanceSubsystem,因此 GEngine->GetEngineSubsystem<UGameFrameworkComponentManager>() 会返回 null;请使用 UGameFrameworkComponentManager::GetForActor(MyActor)。
暂停停用后未恢复: 如果调用了 PauseDeactivationUntilComplete,却从未执行返回的 FSimpleDelegate,插件就会永远停留在 Deactivating 状态;请确保在所有路径上执行它,包括失败路径。
在 BeginPlay 中跨组件进行设置: 另一个功能注入的组件可能尚不存在。
// WRONG — the other component may be added later
void UMyModularComponent::BeginPlay()
{
Super::BeginPlay();
GetOwner()->FindComponentByClass<UMyOtherComponent>()->Configure();
}
// RIGHT — react once the shared state is reached
void UMyModularComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
{
if (DesiredState == TAG_InitState_DataInitialized)
{
GetOwner()->FindComponentByClass<UMyOtherComponent>()->Configure();
}
}
同时重写两个 OnGameFeatureActivating 重载: 基类的上下文版本会调用无参数版本,因此激活代码会运行两次;只重写上下文版本。
将 Lyra 类视为引擎 API: ULyraExperienceDefinition、ULyraExperienceManagerComponent 和 Lyra 自有的操作子类随 Lyra 示例提供,且不包含在引擎中;引擎提供的组件是 UGameFeatureData、UGameFeatureAction 和 UGameFrameworkComponentManager。
相关技能
ue-gameplay-tags-messaging— 声明用作初始化状态、标签容器和查询的FGameplayTag值ue-module-build-system— 插件和模块布局、Build.cs依赖项、加载阶段ue-actor-component-architecture— 组件的创建、注册、附加和 Tickue-gameplay-abilities— 从功能的组件授予能力和属性集ue-data-assets-tables— 主数据资产、Asset Manager 扫描和资产包ue-input-system— 功能添加和移除的 Enhanced Input 映射上下文ue-gameplay-framework— GameMode、GameState、PlayerController 和 PlayerState 生命周期ue-world-level-streaming— 功能添加的 World Partition 内容包和数据层