mcpField
U
ue-game-features

ue-game-features

github.com/quodsoler开发4191.1k 次安装4 周前更新
代码仓库

关于 ue-game-features

面向 UE 5.8 开发者的 Game Features 与 Modular Gameplay 参考技能,介绍功能插件、状态管理、动作和组件注入。内容按开发任务组织,并以引擎头文件验证 API。

主要功能

  1. 01Game Feature 插件布局与描述符
  2. 02插件状态及子系统控制
  3. 03自定义 Game Feature Action
  4. 04向 Actor 注入组件
  5. 05初始化状态系统与项目策略

适用场景

  1. 01创建可独立加载的游戏功能插件
  2. 02运行时加载、激活或卸载功能
  3. 03编写功能激活与停用逻辑
  4. 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[].TypeModuleHostType 名称:Runtime、RuntimeNoCommandlet、Editor、Developer、Program、……
Modules[].LoadingPhaseModuleLoadingPhase 名称: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值行为
None0始终在接收方上创建组件。
AddUnique0b00000001如果接收方已拥有该类的组件,则跳过。
AddIfNotChild0b00000010如果请求的类是现有组件类的子类,则跳过。
UseAutoGeneratedName0b00000100生成新名称,而不是重用类名。

扩展处理程序会观察接收方,而不是添加组件。委托在管理器内部声明为 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任意 AActorGetGameInstance<T>()、GetGameInstanceChecked<T>()、HasAuthority()
UPawnComponentAPawnGetPawn<T>()、GetPawnChecked<T>()、GetPlayerState<T>()、GetController<T>()
UControllerComponentAControllerGetController<T>()、GetControllerChecked<T>()、GetPawn<T>()、GetViewTarget<T>()、GetPawnOrViewTarget<T>()、GetPlayerState<T>()、GetPlayer<T>()
UPlayerStateComponentAPlayerStateGetPlayerState<T>()、GetPlayerStateChecked<T>()
UGameStateComponentAGameStateBaseGetGameState<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": truePluginDescriptor.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::ActionsGetActions()Actions 在 GameFeatureData.h:120 中受保护;访问器位于 :102
ULyraExperienceDefinition、ULyraExperienceManagerComponent、UGameFeatureAction_AddInputContextMappingLyra 示例代码,不是引擎 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 — 组件的创建、注册、附加和 Tick
  • ue-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 内容包和数据层

评论

支持 Markdown