GameFeatureAction_AddComponents.h
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once // 防止头文件被重复包含
#include "GameFeatureAction.h" // 包含游戏功能动作基类
#include "GameFeaturesSubsystem.h" // 包含游戏功能子系统
#include "GameFeatureAction_AddComponents.generated.h" // 包含UHT生成的代码
#define UE_API GAMEFEATURES_API // 定义模块导出宏
// 前置声明
class AActor;
class UActorComponent;
class UGameFrameworkComponentManager;
class UGameInstance;
struct FComponentRequestHandle;
struct FWorldContext;
// 枚举前置声明
enum class EGameFrameworkAddComponentFlags : uint8;
/**
* 描述当此游戏功能启用时,要向某类Actor添加的组件
* (Actor类必须对游戏功能感知,这不是自动发生的)
* @TODO: 在此处编写更多关于如何使Actor感知游戏功能/模块化游戏性的文档
*/
USTRUCT() // UHT结构体声明
struct FGameFeatureComponentEntry
{
GENERATED_BODY() // UHT生成代码宏
UE_API FGameFeatureComponentEntry(); // 构造函数声明
// 要添加组件的基础Actor类
UPROPERTY(EditAnywhere, Category="Components", meta=(AllowAbstract="True")) // 可编辑属性,允许抽象类
TSoftClassPtr<AActor> ActorClass; // Actor类的软引用
// 要添加到指定类型Actor的组件类
UPROPERTY(EditAnywhere, Category="Components") // 可编辑属性
TSoftClassPtr<UActorComponent> ComponentClass; // 组件类的软引用
// 此组件是否应该为客户端添加
UPROPERTY(EditAnywhere, Category="Components") // 可编辑属性
uint8 bClientComponent:1; // 位域成员,占1位
// 是否应该在服务器上添加此组件
UPROPERTY(EditAnywhere, Category="Components") // 可编辑属性
uint8 bServerComponent:1; // 位域成员,占1位
// 添加组件时遵循的规则,如果有的话
UPROPERTY(EditAnywhere, Category = "Components", meta = (Bitmask, BitmaskEnum = "/Script/ModularGameplay.EGameFrameworkAddComponentFlags")) // 可编辑属性,位掩码
uint8 AdditionFlags; // 添加标志位
};
//////////////////////////////////////////////////////////////////////
// UGameFeatureAction_AddComponents
/**
* 向组件管理器添加Actor<->组件生成请求
*
* @see UGameFrameworkComponentManager
*/
UCLASS(MinimalAPI, meta = (DisplayName = "添加组件")) // UCLASS宏,最小化API,显示名称为"添加组件"
class UGameFeatureAction_AddComponents final : public UGameFeatureAction // 最终类,继承自UGameFeatureAction
{
GENERATED_BODY() // UHT生成代码宏
public:
//~开始 UGameFeatureAction 接口
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context) override; // 游戏功能激活时调用
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override; // 游戏功能停用时调用
#if WITH_EDITORONLY_DATA // 仅在编辑器数据下编译
virtual void AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData) override; // 添加额外的资产包数据
#endif
//~结束 UGameFeatureAction 接口
//~开始 UObject 接口
#if WITH_EDITOR // 仅在编辑器下编译
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override; // 验证数据有效性
#endif
//~结束 UObject 接口
/** 当此游戏功能启用时,要添加到游戏性Actor的组件列表 */
UPROPERTY(EditAnywhere, Category="Components", meta=(TitleProperty="{ActorClass} -> {ComponentClass}")) // 可编辑属性,标题显示格式
TArray<FGameFeatureComponentEntry> ComponentList; // 组件条目数组
private:
/**
* 上下文句柄结构体
*/
struct FContextHandles
{
FDelegateHandle GameInstanceStartHandle; // 游戏实例开始的委托句柄
TArray<TSharedPtr<FComponentRequestHandle>> ComponentRequestHandles; // 组件请求句柄的共享指针数组
};
/**
* 添加组件到世界
*/
void AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles);
/**
* 处理游戏实例开始事件
*/
void HandleGameInstanceStart(UGameInstance* GameInstance, FGameFeatureStateChangeContext ChangeContext);
TMap<FGameFeatureStateChangeContext, FContextHandles> ContextHandles; // 上下文句柄映射表
};
#undef UE_API // 取消定义宏
GameFeatureAction_AddComponents.cpp
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameFeatureAction_AddComponents.h" // 包含头文件
#include "AssetRegistry/AssetBundleData.h" // 包含资产包数据
#include "Components/GameFrameworkComponentManager.h" // 包含游戏框架组件管理器
#include "Engine/GameInstance.h" // 包含游戏实例
#include "GameFeaturesSubsystemSettings.h" // 包含游戏功能子系统设置
#include "Engine/AssetManager.h" // 包含资产管理器
#if WITH_EDITOR // 仅在编辑器下编译
#include "Misc/DataValidation.h" // 包含数据验证
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameFeatureAction_AddComponents) // 包含内联生成的代码
#define LOCTEXT_NAMESPACE "GameFeatures" // 定义本地化文本命名空间
//////////////////////////////////////////////////////////////////////
// FGameFeatureComponentEntry
/**
* FGameFeatureComponentEntry构造函数
*/
FGameFeatureComponentEntry::FGameFeatureComponentEntry()
: bClientComponent(true) // 默认客户端组件为true
, bServerComponent(true) // 默认服务器组件为true
, AdditionFlags(static_cast<uint8>(EGameFrameworkAddComponentFlags::None)) // 默认添加标志为None
{
}
//////////////////////////////////////////////////////////////////////
// UGameFeatureAction_AddComponents
/**
* 游戏功能激活时的处理函数
*/
void UGameFeatureAction_AddComponents::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
{
FContextHandles& Handles = ContextHandles.FindOrAdd(Context); // 查找或添加上下文句柄
Handles.GameInstanceStartHandle = FWorldDelegates::OnStartGameInstance.AddUObject(this, // 添加游戏实例开始的委托绑定
&UGameFeatureAction_AddComponents::HandleGameInstanceStart, FGameFeatureStateChangeContext(Context)); // 绑定到HandleGameInstanceStart函数
ensure(Handles.ComponentRequestHandles.Num() == 0); // 确保组件请求句柄数组为空
// 添加到任何已经初始化的关联游戏实例的世界
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) // 遍历所有世界上下文
{
if (Context.ShouldApplyToWorldContext(WorldContext)) // 如果应该应用到该世界上下文
{
AddToWorld(WorldContext, Handles); // 添加到世界
}
}
}
/**
* 游戏功能停用时的处理函数
*/
void UGameFeatureAction_AddComponents::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
{
FContextHandles& Handles = ContextHandles.FindOrAdd(Context); // 查找或添加上下文句柄
FWorldDelegates::OnStartGameInstance.Remove(Handles.GameInstanceStartHandle); // 移除游戏实例开始的委托绑定
// 释放句柄也将从任何已注册的Actor中移除组件
Handles.ComponentRequestHandles.Empty(); // 清空组件请求句柄数组
}
#if WITH_EDITORONLY_DATA // 仅在编辑器数据下编译
/**
* 添加额外的资产包数据
*/
void UGameFeatureAction_AddComponents::AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData)
{
if (UAssetManager::IsInitialized()) // 如果资产管理器已初始化
{
for (const FGameFeatureComponentEntry& Entry : ComponentList) // 遍历组件列表
{
if (Entry.bClientComponent) // 如果是客户端组件
{
AssetBundleData.AddBundleAsset(UGameFeaturesSubsystemSettings::LoadStateClient, Entry.ComponentClass.ToSoftObjectPath().GetAssetPath()); // 添加到客户端资产包
}
if (Entry.bServerComponent) // 如果是服务器组件
{
AssetBundleData.AddBundleAsset(UGameFeaturesSubsystemSettings::LoadStateServer, Entry.ComponentClass.ToSoftObjectPath().GetAssetPath()); // 添加到服务器资产包
}
}
}
}
#endif
#if WITH_EDITOR // 仅在编辑器下编译
/**
* 验证数据有效性
*/
EDataValidationResult UGameFeatureAction_AddComponents::IsDataValid(FDataValidationContext& Context) const
{
EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(Context), EDataValidationResult::Valid); // 合并基类验证结果
int32 EntryIndex = 0; // 条目索引
for (const FGameFeatureComponentEntry& Entry : ComponentList) // 遍历组件列表
{
if (Entry.ActorClass.IsNull()) // 如果Actor类为空
{
Result = EDataValidationResult::Invalid; // 设置为无效
Context.AddError(FText::Format(LOCTEXT("ComponentEntryHasNullActor", "ComponentList中索引{0}处的ActorClass为空"), FText::AsNumber(EntryIndex))); // 添加错误信息
}
if (Entry.ComponentClass.IsNull()) // 如果组件类为空
{
Result = EDataValidationResult::Invalid; // 设置为无效
Context.AddError(FText::Format(LOCTEXT("ComponentEntryHasNullComponent", "ComponentList中索引{0}处的ComponentClass为空"), FText::AsNumber(EntryIndex))); // 添加错误信息
}
++EntryIndex; // 递增索引
}
return Result; // 返回验证结果
}
#endif
/**
* 添加组件到世界
*/
void UGameFeatureAction_AddComponents::AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles)
{
UWorld* World = WorldContext.World(); // 获取世界
UGameInstance* GameInstance = WorldContext.OwningGameInstance; // 获取游戏实例
if ((GameInstance != nullptr) && (World != nullptr) && World->IsGameWorld()) // 如果游戏实例和世界有效,且世界是游戏世界
{
if (UGameFrameworkComponentManager* GFCM = UGameInstance::GetSubsystem<UGameFrameworkComponentManager>(GameInstance)) // 如果获取到游戏框架组件管理器子系统
{
const ENetMode NetMode = World->GetNetMode(); // 获取网络模式
const bool bIsServer = NetMode != NM_Client; // 判断是否为服务器
const bool bIsClient = NetMode != NM_DedicatedServer; // 判断是否为客户端
UE_LOG(LogGameFeatures, Verbose, TEXT("将%s的组件添加到世界%s (客户端: %d, 服务器: %d)"), *GetPathNameSafe(this), *World->GetDebugDisplayName(), bIsClient ? 1 : 0, bIsServer ? 1 : 0); // 记录详细日志
for (const FGameFeatureComponentEntry& Entry : ComponentList) // 遍历组件列表
{
const bool bShouldAddRequest = (bIsServer && Entry.bServerComponent) || (bIsClient && Entry.bClientComponent); // 判断是否应该添加请求
if (bShouldAddRequest) // 如果需要添加请求
{
if (!Entry.ActorClass.IsNull()) // 如果Actor类不为空
{
UE_SCOPED_ENGINE_ACTIVITY(TEXT("添加组件到世界%s (%s)"), *World->GetDebugDisplayName(), *Entry.ComponentClass.ToString()); // 引擎活动范围
TSubclassOf<UActorComponent> ComponentClass = Entry.ComponentClass.LoadSynchronous(); // 同步加载组件类
if (ComponentClass) // 如果成功加载组件类
{
Handles.ComponentRequestHandles.Add(GFCM->AddComponentRequest(Entry.ActorClass, ComponentClass, static_cast<EGameFrameworkAddComponentFlags>(Entry.AdditionFlags))); // 添加组件请求并存储句柄
}
else if (!Entry.ComponentClass.IsNull()) // 如果组件类引用非空但加载失败
{
UE_LOG(LogGameFeatures, Error, TEXT("[GameFeatureData %s]: 未能加载组件类%s。不应用组件。"), *GetPathNameSafe(this), *Entry.ComponentClass.ToString()); // 记录错误日志
}
}
}
}
}
}
}
/**
* 处理游戏实例开始事件
*/
void UGameFeatureAction_AddComponents::HandleGameInstanceStart(UGameInstance* GameInstance, FGameFeatureStateChangeContext ChangeContext)
{
if (FWorldContext* WorldContext = GameInstance->GetWorldContext()) // 如果获取到世界上下文
{
if (ChangeContext.ShouldApplyToWorldContext(*WorldContext)) // 如果应该应用到该世界上下文
{
FContextHandles* Handles = ContextHandles.Find(ChangeContext); // 查找上下文句柄
if (ensure(Handles)) // 确保句柄存在
{
AddToWorld(*WorldContext, *Handles); // 添加到世界
}
}
}
}
//////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE // 取消定义本地化文本命名空间
UGameFeatureAction_AddComponents 类成员分析
一、头文件 (.h) 成员分析
1. 结构体 FGameFeatureComponentEntry
a) 成员变量
TSoftClassPtr ActorClass
- 功能: 要添加组件的基础Actor类的软引用
- Lyra应用 : 在Lyra中用于指定哪些Actor类型需要添加额外组件,例如为
LyraCharacter、LyraPlayerController或特定游戏模式的Actor添加组件 - 示例 : 为Lyra的
B_ShooterCharacter添加LyraComboComponent或为LyraPlayerController添加LyraInventoryComponent
TSoftClassPtr ComponentClass
- 功能: 要添加到指定Actor的组件类的软引用
- Lyra应用 : 定义具体的组件类,如
LyraHealthComponent、LyraWeaponComponent等。软引用允许异步加载 - 示例 :
TSoftClassPtr<ULyraHealthComponent>,用于管理角色的生命值
uint8 bClientComponent:1
- 功能: 位域标志,指示此组件是否应在客户端添加
- Lyra应用: 控制组件在网络环境下的同步。客户端组件通常处理视觉效果、UI、输入等
- 示例 :
LyraInputComponent、LyraCameraComponent仅在客户端需要
uint8 bServerComponent:1
- 功能: 位域标志,指示此组件是否应在服务器添加
- Lyra应用: 服务器组件处理游戏逻辑、权威状态、伤害计算等
- 示例 :
LyraHealthComponent、LyraAbilitySystemComponent需要在服务器上运行
uint8 AdditionFlags
- 功能 : 添加组件时的额外标志位,使用
EGameFrameworkAddComponentFlags枚举 - Lyra应用: 控制组件初始化时机和行为,如是否在Actor构造时添加、是否添加多个实例等
- 示例 : 用于控制
LyraInventoryComponent是否允许多个实例或指定初始化顺序
2. 类 UGameFeatureAction_AddComponents
a) 成员变量
TArray ComponentList
-
功能: 存储所有要添加的组件条目的数组
-
Lyra应用: 在Lyra的GameFeatureData中配置,定义插件激活时要添加的所有组件
-
示例 :
cppComponentList = { { LyraCharacterClass, LyraHealthComponentClass, true, true, None }, { LyraCharacterClass, LyraAbilitySystemComponentClass, false, true, None }, { LyraPlayerControllerClass, LyraInputComponentClass, true, false, None } }
TMap<FGameFeatureStateChangeContext, FContextHandles> ContextHandles
- 功能: 映射表,关联每个游戏功能上下文与其处理句柄
- Lyra应用: 允许多个GameFeature实例同时激活,每个实例独立管理自己的组件
- 示例: 当多个Lyra游戏模式插件同时激活时,每个插件有自己的组件注册记录
b) 私有结构体 FContextHandles
FDelegateHandle GameInstanceStartHandle
- 功能 : 存储
OnStartGameInstance委托的句柄,用于游戏实例开始时的事件处理 - Lyra应用: 监听Lyra游戏实例启动,在合适的时机添加组件
- 示例: 当Lyra游戏开始时,为所有现有世界添加配置的组件
TArray<TSharedPtr> ComponentRequestHandles
- 功能: 存储组件请求句柄的数组,用于后续清理
- Lyra应用 : 记录所有通过
UGameFrameworkComponentManager添加的组件请求,以便在插件停用时正确清理 - 示例 : 当Lyra插件停用时,移除所有已添加的
LyraInventoryComponent实例
c) 成员函数
virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context) override
- 功能: 游戏功能激活时的回调函数
- Lyra应用 :
- 注册游戏实例开始事件的监听
- 为所有现有世界添加组件
- 在Lyra中用于初始化插件功能,如添加武器系统、能力系统等
- 调用时机: GameFeature激活时(如玩家加载特定游戏模式)
virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override
- 功能: 游戏功能停用时的回调函数
- Lyra应用 :
- 移除游戏实例事件监听
- 清理所有组件请求句柄
- 在Lyra插件卸载时移除添加的组件
- 示例: 玩家退出游戏模式时,移除该模式特有的组件
virtual void AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData) override
- 功能: 添加资产包数据,支持按平台分发
- Lyra应用: 为客户端/服务器分别打包所需组件资产
- 编译条件 :
WITH_EDITORONLY_DATA - 示例 : 将
LyraHealthComponent打包到客户端资产包,LyraServerComponent打包到服务器资产包
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override
- 功能: 验证数据有效性
- Lyra应用: 在编辑器中对GameFeatureData配置进行验证
- 编译条件 :
WITH_EDITOR - 验证内容 : 确保
ActorClass和ComponentClass不为空
void AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles)
- 功能: 向特定世界添加配置的组件
- Lyra应用 :
- 根据网络模式(客户端/服务器)筛选组件
- 通过
UGameFrameworkComponentManager注册组件请求 - 确保组件在正确的网络环境下添加
- 关键逻辑 : 根据
bClientComponent和bServerComponent标志决定添加哪些组件
*void HandleGameInstanceStart(UGameInstance GameInstance, FGameFeatureStateChangeContext ChangeContext)**
- 功能: 处理游戏实例开始事件的回调
- Lyra应用: 当Lyra游戏实例创建新世界时,为新世界添加组件
- 调用链 :
OnStartGameInstance→HandleGameInstanceStart→AddToWorld
二、源文件 (.cpp) 函数实现分析
1. FGameFeatureComponentEntry 构造函数
- 实现: 初始化默认值
- 默认值 :
bClientComponent = truebServerComponent = trueAdditionFlags = EGameFrameworkAddComponentFlags::None
- Lyra应用: 确保新组件条目有合理的默认配置
2. OnGameFeatureActivating 实现细节
cpp
// 核心逻辑:
// 1. 为每个上下文创建句柄
// 2. 监听游戏实例开始事件
// 3. 为所有现有世界添加组件
- Lyra应用: 确保插件激活时立即为现有世界生效,同时监听未来世界的创建
3. OnGameFeatureDeactivating 实现细节
cpp
// 核心逻辑:
// 1. 移除事件监听
// 2. 清空组件请求句柄数组(自动触发组件移除)
- Lyra应用: 确保插件停用时完全清理,避免内存泄漏和残留组件
4. AddToWorld 实现细节
cpp
// 关键流程:
// 1. 验证世界和游戏实例
// 2. 获取游戏框架组件管理器
// 3. 根据网络模式筛选组件
// 4. 加载组件类并注册请求
- 网络模式处理 :
- 服务器端:
NetMode != NM_Client - 客户端:
NetMode != NM_DedicatedServer
- 服务器端:
- Lyra应用: 确保多人游戏中客户端和服务器只获取必要的组件
5. HandleGameInstanceStart 实现细节
cpp
// 核心逻辑:
// 1. 获取游戏实例的世界上下文
// 2. 验证是否应该应用到该上下文
// 3. 查找对应的句柄
// 4. 调用AddToWorld
- Lyra应用: 动态处理游戏会话的创建,支持无缝地图切换和服务器旅行
三、LyraStarterGame 中的典型应用场景
场景1:添加角色能力组件
cpp
// 在Lyra的AbilitySystem GameFeature中
ComponentList.Add({
TSoftClassPtr<ALyraCharacter>, // ActorClass
TSoftClassPtr<ULyraAbilitySystemComponent>, // ComponentClass
false, // bClientComponent (客户端不需要权威组件)
true, // bServerComponent
EGameFrameworkAddComponentFlags::None
});
场景2:添加客户端UI组件
cpp
// 在Lyra的UI GameFeature中
ComponentList.Add({
TSoftClassPtr<ALyraPlayerController>,
TSoftClassPtr<ULyraHUDComponent>,
true, // bClientComponent
false, // bServerComponent
EGameFrameworkAddComponentFlags::None
});
场景3:武器系统组件
cpp
// 在Lyra的WeaponSystem GameFeature中
ComponentList.Add({
TSoftClassPtr<ALyraCharacter>,
TSoftClassPtr<ULyraWeaponComponent>,
true, // bClientComponent (需要客户端武器动画)
true, // bServerComponent (需要服务器伤害计算)
EGameFrameworkAddComponentFlags::None
});
四、设计模式优势
1. 模块化设计
- 优势: 允许插件化添加/移除功能
- Lyra应用: 不同游戏模式可以携带不同的组件集
2. 网络感知
- 优势: 自动区分客户端/服务器组件
- Lyra应用: 优化网络带宽,只同步必要数据
3. 懒加载机制
- 优势: 软引用支持异步加载
- Lyra应用: 减少启动时间,按需加载组件
4. 生命周期管理
- 优势: 自动清理,防止内存泄漏
- Lyra应用: 确保插件切换时的资源释放
五、总结
在LyraStarterGame中,UGameFeatureAction_AddComponents是一个核心的系统扩展机制,它:
- 实现了插件化架构: 允许动态添加/移除游戏功能
- 支持网络环境: 智能区分客户端/服务器组件
- 提供完整生命周期管理: 从激活到停用的完整流程
- 与Lyra的模块化设计深度集成: 支持能力系统、武器系统、UI系统等的动态扩展
通过这个类,Lyra实现了高度可扩展的游戏架构,开发者可以轻松添加新功能而不修改核心代码。