Lyra学习6:GameFeatureAction_AddComponents分析

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类型需要添加额外组件,例如为LyraCharacterLyraPlayerController或特定游戏模式的Actor添加组件
  • 示例 : 为Lyra的B_ShooterCharacter添加LyraComboComponent或为LyraPlayerController添加LyraInventoryComponent

TSoftClassPtr ComponentClass

  • 功能: 要添加到指定Actor的组件类的软引用
  • Lyra应用 : 定义具体的组件类,如LyraHealthComponentLyraWeaponComponent等。软引用允许异步加载
  • 示例 : TSoftClassPtr<ULyraHealthComponent>,用于管理角色的生命值

uint8 bClientComponent:1

  • 功能: 位域标志,指示此组件是否应在客户端添加
  • Lyra应用: 控制组件在网络环境下的同步。客户端组件通常处理视觉效果、UI、输入等
  • 示例 : LyraInputComponentLyraCameraComponent仅在客户端需要

uint8 bServerComponent:1

  • 功能: 位域标志,指示此组件是否应在服务器添加
  • Lyra应用: 服务器组件处理游戏逻辑、权威状态、伤害计算等
  • 示例 : LyraHealthComponentLyraAbilitySystemComponent需要在服务器上运行

uint8 AdditionFlags

  • 功能 : 添加组件时的额外标志位,使用EGameFrameworkAddComponentFlags枚举
  • Lyra应用: 控制组件初始化时机和行为,如是否在Actor构造时添加、是否添加多个实例等
  • 示例 : 用于控制LyraInventoryComponent是否允许多个实例或指定初始化顺序

2. 类 UGameFeatureAction_AddComponents

a) 成员变量

TArray ComponentList

  • 功能: 存储所有要添加的组件条目的数组

  • Lyra应用: 在Lyra的GameFeatureData中配置,定义插件激活时要添加的所有组件

  • 示例 :

    cpp 复制代码
    ComponentList = {
      { 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应用 :
    1. 注册游戏实例开始事件的监听
    2. 为所有现有世界添加组件
    3. 在Lyra中用于初始化插件功能,如添加武器系统、能力系统等
  • 调用时机: GameFeature激活时(如玩家加载特定游戏模式)

virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override

  • 功能: 游戏功能停用时的回调函数
  • Lyra应用 :
    1. 移除游戏实例事件监听
    2. 清理所有组件请求句柄
    3. 在Lyra插件卸载时移除添加的组件
  • 示例: 玩家退出游戏模式时,移除该模式特有的组件

virtual void AddAdditionalAssetBundleData(FAssetBundleData& AssetBundleData) override

  • 功能: 添加资产包数据,支持按平台分发
  • Lyra应用: 为客户端/服务器分别打包所需组件资产
  • 编译条件 : WITH_EDITORONLY_DATA
  • 示例 : 将LyraHealthComponent打包到客户端资产包,LyraServerComponent打包到服务器资产包

virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override

  • 功能: 验证数据有效性
  • Lyra应用: 在编辑器中对GameFeatureData配置进行验证
  • 编译条件 : WITH_EDITOR
  • 验证内容 : 确保ActorClassComponentClass不为空

void AddToWorld(const FWorldContext& WorldContext, FContextHandles& Handles)

  • 功能: 向特定世界添加配置的组件
  • Lyra应用 :
    1. 根据网络模式(客户端/服务器)筛选组件
    2. 通过UGameFrameworkComponentManager注册组件请求
    3. 确保组件在正确的网络环境下添加
  • 关键逻辑 : 根据bClientComponentbServerComponent标志决定添加哪些组件

*void HandleGameInstanceStart(UGameInstance GameInstance, FGameFeatureStateChangeContext ChangeContext)**

  • 功能: 处理游戏实例开始事件的回调
  • Lyra应用: 当Lyra游戏实例创建新世界时,为新世界添加组件
  • 调用链 : OnStartGameInstanceHandleGameInstanceStartAddToWorld

二、源文件 (.cpp) 函数实现分析

1. FGameFeatureComponentEntry 构造函数

  • 实现: 初始化默认值
  • 默认值 :
    • bClientComponent = true
    • bServerComponent = true
    • AdditionFlags = 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是一个核心的系统扩展机制,它:

  1. 实现了插件化架构: 允许动态添加/移除游戏功能
  2. 支持网络环境: 智能区分客户端/服务器组件
  3. 提供完整生命周期管理: 从激活到停用的完整流程
  4. 与Lyra的模块化设计深度集成: 支持能力系统、武器系统、UI系统等的动态扩展

通过这个类,Lyra实现了高度可扩展的游戏架构,开发者可以轻松添加新功能而不修改核心代码。

相关推荐
组合缺一15 分钟前
Solon AI 开发学习11 - chat - 工具调用与定制(Tool Call)
人工智能·学习·ai·chatgpt·llm·solon·toolcall
湫兮之风20 分钟前
C++: 一文掌握std::vector::assign函数
开发语言·c++
离离茶21 分钟前
【笔记2-8】ESP32:SmartConfig一键配网
笔记
影林握雪24 分钟前
M|大佛普拉斯 (2017)
经验分享·笔记·其他·生活
mmz120743 分钟前
双指针问题5(c++)
c++·算法
Rock_yzh1 小时前
LeetCode算法刷题——56. 合并区间
数据结构·c++·学习·算法·leetcode·职场和发展·动态规划
sheeta19981 小时前
LeetCode 每日一题笔记 日期:2025.12.02 题目:3623. 统计梯形的数目 I
笔记·算法·leetcode
@木辛梓1 小时前
结构体 结构体c++
开发语言·c++
kyle~1 小时前
虚拟仪器LabView(VI)
c++·python·ros·labview