Skip to content

UT框架用户指南

面向第一次接触 UTFramework 的游戏开发者。

目标:从一个启用了插件的 Unreal 项目开始,配置出具有 Attribute、GameplayEffect、主动技能、响应技能、Task/Execution、道具、装备和基础存档能力的游戏雏形。

本文只使用当前已经落地的主流程。实验能力、暂缓设计和已知风险会明确标出,不把未来方案当作现成功能。

English edition: UT框架用户指南_英文.md.


本指南适用于:

  • 使用蓝图配置角色、技能、效果和道具。
  • 在 C++ 中扩展 Ability、Execution、EffectBehavior 或游戏角色。
  • 单机、Listen Server 或客户端/服务器项目的基础接入。
  • 保存和恢复当前 World 中已经存在的 UT 角色对象。

本指南不承诺:

  • 完整 World Save、动态 Actor 重建或跨地图恢复。
  • 完整任务/成就系统。
  • Equipment 套装系统和所有装备失败事务。
  • Ability DataTable 自动生成 Active/Reactive Asset。

启用 UTGameFramework 插件。插件包含:

  • UTGameFramework:核心 Runtime 模块。
  • UTGame:角色和 Save 等游戏层入口。
  • UTEditor:强类型 ID Pin、资产配置和 K2 节点。
  • UTTest:项目内测试夹具,正式发布时可按产品策略处理。

插件依赖 GameplayTagsEditor 和 DataRegistry。

直接引用框架类型的游戏模块至少加入:

PublicDependencyModuleNames.AddRange(new[]
{
"UTGameFramework"
});

若直接引用 UUTSaveManagerSubsystem 或 UTGame 中的角色层类型,再加入:

"UTGame"
  • 修改模块或新增反射类后,关闭编辑器并执行完整编译;普通函数实现的小改动才优先使用 Live Coding。

在项目 Config/DefaultEngine.ini 中加入:

[/Script/Engine.Engine]
AssetManagerClassName=/Script/UTGameFramework.UTAssetManager

配置错误时,UUTAssetManager::Get() 会直接报告致命错误,不会静默退回普通 AssetManager。

在 Config/DefaultGame.ini 的 [/Script/Engine.AssetManagerSettings] 下注册:

+PrimaryAssetTypesToScan=(PrimaryAssetType="LevelingAttribute",AssetBaseClass="/Script/UTGameFramework.LevelingAttribute",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/Attribute")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="ParameterAttribute",AssetBaseClass="/Script/UTGameFramework.ParameterAttribute",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/Attribute")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="GameplayEffect",AssetBaseClass="/Script/UTGameFramework.UTGameplayEffect",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/Effect")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="StaticProp",AssetBaseClass="/Script/UTGameFramework.UTStaticGamePropBase",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/GameProps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="AbilityAsset",AssetBaseClass="/Script/UTGameFramework.UTAbilityAsset",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/Ability")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))
+PrimaryAssetTypesToScan=(PrimaryAssetType="InstancePropAsset",AssetBaseClass="/Script/UTGameFramework.UTInstancePropAsset",bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Blueprints/UT/GameProps")),SpecificAssets=,Rules=(Priority=-1,ChunkId=-1,bApplyRecursively=True,CookRule=Unknown))

如果修改了内容目录,必须同步修改这里。不要依赖插件中历史 DefaultGame.ini 的旧工程路径。

推荐结构:

/Game/Blueprints/UT
/Attribute
/Parameter
/Leveling
/Sets
/Effect
/Ability
/ActiveAbility
/ReactiveAbility
/Sets
/GameProps
/Static
/Instance
/Equipment
/Character
/Input
/Test

可以使用自己的目录,但必须让3.2中配置的 Asset Manager 扫描路径与实际目录一致。

UT 框架的主资产是可以被 UTAssetManager 扫描、注册和按 ID 加载的配置资产,都是继承自PrimaryDataAsset的类。主资产的 作用是保存可被系统识别的稳定配置;运行时再由 AbilityComponent、InventoryComponent 等创建对应的运行时 Item 或实例。

当前常用的主资产包括:

主资产 主要用途 通常的运行时对象
LevelingAttribute 等级、经验等成长属性定义 LevelingAttribute Item
ParameterAttribute Health、Mana、Attack 等属性定义 Attribute Item
UTGameplayEffect 属性变化、Tag、授予 Ability、响应规则和其它效果配置 Active Effect Item
UUTAbilityAsset 技能的配置类,但技能又分为主动技能和响应技能,所以应该创建对应的AbilityAsset Ability的运行实例为UUTAbility及其子类
UUTStaticGamePropBase 静态道具的名称、描述、图标和使用配置 StaticProp 容器项
UUTInstancePropAsset 装备、武器等实例道具的基础定义 InstanceProp 实例

主资产、蓝图类和运行时实例是三个不同层次:

主资产(配置和稳定 ID)
-> 蓝图类(需要编写逻辑时使用)
-> 运行时实例(一次注册、施放或使用产生)

例如,主动技能通常由 UUTActiveAbilityAsset 引用一个继承自 UUTActiveAbility 的 蓝图类。资产决定技能的 ID、网络策略和其它配置;蓝图类中的 AbilityBehave 决定技能 实际执行的逻辑。响应技能的 UUTReactiveAbilityAsset 则引用继承自 UUTReactiveAbility 的蓝图类,并通过 IntentTag 指定它响应哪一类 Intent。

主资产的对象名会参与 Primary Asset ID 的生成。创建后应尽量避免随意重命名;重命名可能 导致 ID 变化,并影响蓝图引用、网络两端匹配、存档数据以及其它按 ID 保存的配置。

类型 前缀 示例
ParameterAttribute UAT_ UAT_Health
LevelingAttribute ULV_ ULV_CharacterLevel
GameplayEffect UGE_ UGE_Damage_20
- - -
ActiveAbilityAsset UABP_A_ UABP_A_Fireball
ActiveAbility UAB_A_ UAB_A_Fireball
ReactiveAbilityAsset ``UABP_R_` ``UABP_R_OnHit`
ReactiveAbility UAB_R_ UAB_R_OnHit
- - -
StaticProp USP_ USP_GlassBall
StaticProp子类,如消耗品等 USP_Con USP_Con_HealthPotion
InstancePropAsset UIP_ UIP_IronSword

前缀只是约定。资产对象名本身才是 PrimaryAssetName;重命名资产等价于修改配置 ID,会影响引用、网络和存档。


UT 框架在内容浏览器中提供了快捷创建入口。右键内容浏览器空白区域,打开:

右键
-> UTGameFramework

常用菜单项如下:

菜单项 用途
UTAbility 创建 Ability 逻辑蓝图类
UTAssetSet 创建用于集中配置资产集合的 Set
UTPrimary Data Asset 创建 UT 主资产配置对象
UTTaskExecution 创建 Task/Execution 逻辑蓝图类

创建主资产的基本步骤:

  1. 在内容浏览器中进入与 DefaultGame.ini 扫描路径对应的目录。
  2. 右键空白处,选择 UTGameFramework -> UTPrimary Data Asset。
  3. 在 Pick Parent Class 窗口中选择需要的具体主资产父类,例如:
    • LevelingAttribute;
    • ParameterAttribute;
    • UTGameplayEffect;
    • UUTActiveAbilityAsset;
    • UUTReactiveAbilityAsset;
    • UUTStaticGamePropBase;
    • UUTInstancePropAsset。
  4. 确认创建并使用约定的名称保存资产。
  5. 在 Details 面板中填写该类型要求的配置。
  6. 编译并保存资产,然后确认它出现在对应的强类型 ID 下拉列表中。

Pick Parent Class 中显示的类层级反映了当前 C++ 继承关系。应选择实际业务需要的 具体父类,不要仅因为名称相近而选择通用 DataAsset 或普通 Blueprint。例如:

  • 需要配置 Attribute 定义时,选择 ParameterAttribute 或 LevelingAttribute;
  • 需要配置主动技能资产时,选择 UUTActiveAbilityAsset;
  • 需要配置响应技能资产时,选择 UUTReactiveAbilityAsset;
  • 需要编写技能执行逻辑时,另外创建 UTAbility 蓝图类,并让对应 Asset 的 Class 属性指向它。

需要编写逻辑的类型,如Ability,InstanceProp通常要创建两部分:

配置主资产:UTPrimary Data Asset
逻辑蓝图类:UTAbility / UTTaskExecution 或具体业务蓝图父类

以响应技能为例:

UABP_R_OnHit <- 继承 UUTReactiveAbility 的逻辑蓝图
UABP_R_OnHitAsset <- 继承 UUTReactiveAbilityAsset 的主资产

然后在主资产中设置:

ReactiveAbilityClass = UABP_R_OnHit
IntentTag = 对应的 Intent Channel

创建后必须编译和保存蓝图。仅创建一个逻辑蓝图类,并不会自动生成可被 UTAssetManager 按 ID 查询的主资产;反过来,仅创建主资产也不会自动产生自定义执行逻辑。

如果资产没有出现在 ID 下拉列表中,依次检查:

  • 是否选择了正确的 UT 主资产父类;
  • 资产是否保存于配置的扫描路径下;
  • 资产是否已编译并保存;
  • PrimaryAssetType 与当前类型是否一致;
  • 是否已经重启编辑器使 Asset Manager 完成扫描。

UTAssetID 是 UT 对 Unreal FPrimaryAssetId 的强类型封装,用来在系统中标识一个 UT 主 资产。它通常由两部分组成:主资产类型和主资产名称,例如:

ParameterAttribute:UAT_Health
GameplayEffect:UGE_Burning

不同主资产使用不同的 ID 结构体,例如 FUTLevelingID、FUTAttributeID、FUTEffectID、 FUTAbilityID、FUTStaticGamePropID 和 FUTInstanceGamePropID。这些类型都具有具体的 资产类型约束,不能随意用一种 ID 代替另一种 ID。ID 还可以判断是否有效、读取名称和类型, 并支持序列化,适合保存到运行时容器、网络结构或存档中。

UTAssetID 与主资产的关系是“身份引用”,不是主资产对象本身:

UT 主资产
-> AssetManager 扫描并登记 PrimaryAssetId
-> 业务系统保存对应的 UTAssetID
-> 需要使用时通过 AssetManager 按 ID 获取主资产或其 CDO

因此,Attribute、Effect、Ability、Prop 等运行时结构通常保存 ID,而不长期保存主资产的 硬引用。主资产名称参与 ID,创建后应保持稳定;资产必须位于对应的扫描路径,并使用正确的 PrimaryAssetType,否则 ID 可能无效或无法从 AssetManager 找到资产。

UTAssetID 是 BlueprintType 结构体,可以直接创建为蓝图变量、结构体成员、数组元素或 Map 的键。不同 ID 支持以下常用蓝图操作:

  • ==:判断两个 ID 是否指向同一个主资产;
  • !=:判断两个 ID 是否不同;
  • IsValid:检查 ID 是否已经包含有效的主资产身份;
  • Switch:根据具体的 ID 值分支处理不同主资产。

ID 的比较基于完整的 Primary Asset ID,而不是仅比较显示名称。因此,即使两个资产名称相同, 只要主资产类型不同,它们也不是同一个 ID。使用 Switch 时应确保分支值来自同一类 ID,并且 对应资产已经被 AssetManager 扫描和登记。

UUTAbilityComponent 是 UT 运行时游戏状态的核心承载组件。它通常挂在角色、Pawn 或其它 实现 UT 游戏对象接口的 Actor 上,集中管理该对象的属性、等级、Ability、GameplayEffect、 Tag、技能槽和响应技能状态。

它不是单个 Ability,也不是单纯的属性组件。AbilityComponent 负责保存和同步运行时数据, Ability 负责执行技能逻辑,Effect 负责状态变化,InventoryComponent 则负责道具和装备容器。

AbilityComponent 主要包含以下运行时容器:

容器或状态 作用
LAContainer 保存已注册 LevelingAttribute 的运行时等级和经验
PAContainer 保存已注册 ParameterAttribute 的 BasicValue、MiddleValue 和 FinalValue
AbilityContainer 保存已注册 AbilityAsset 对应的运行时 AbilityItem
ActivatedEffectContainer 保存当前有效的 Active Effect
AbilitySlotContainer 保存运行时技能槽及其最终绑定,服务器为权威端
DynamicallyTagPlans 保存由 Effect 等系统产生的动态 Tag 计划
Reactive 状态 保存 Intent 监听、响应实例及网络协调数据

组件还提供属性查询和修改、Ability 注册与施放、Effect 应用与清除、技能槽解析、Tag 查询、 Intent 路由以及存档恢复等接口。外部系统应优先通过这些接口访问运行时状态。

5.3 AbilityComponent 的配置与初始化

Section titled “5.3 AbilityComponent 的配置与初始化”

在拥有 AbilityComponent 的 Actor 上配置默认数据:

配置项 内容
DefaultLevelingAttributeSets UUTLevelingAttributeSet 数组
DefaultAttributeSets UUTAttributeSet 数组
DefaultAbilitySets 默认 AbilitySet 数组
DefaultAbilitySlots 初始技能槽配置
StaticTags 组件固定 Tag

游戏开始时,组件按以下顺序建立新状态:

BeginPlay
-> 注册 LevelingAttribute
-> 注册 ParameterAttribute
-> 注册 AbilitySet
-> 初始化 AbilitySlot
-> 重建输入绑定缓存

属性集中的主资产会被转换为运行时 Item;属性之间的依赖会在注册后刷新。默认配置用于新 状态初始化,不能把默认数组当作当前实时存档数据。

恢复存档时,组件先建立用于承载数据的默认运行时条目,再用存档值恢复等级、属性和 Effect, 最后重新计算依赖属性和聚合结果。项目应确保存档恢复入口只在权威端重建正式状态,客户端 主要接收复制后的运行时镜像。

5.4 AbilityComponent 与其他系统的关系

Section titled “5.4 AbilityComponent 与其他系统的关系”
Actor / Pawn
-> AbilityComponent
-> Attribute / LevelingAttribute
-> Ability / AbilityTask
-> GameplayEffect / Aggregator
-> AbilitySlot / Intent / Tag
InventoryComponent
-> StaticProp / InstanceProp / Equipment
-> 为关联 AbilityComponent 施加或清除 Effect
#### 网络边界
多人游戏中,AbilityComponent 的属性、已注册 Ability、技能槽和 Active Effect 状态以服务
器为权威。客户端可以读取复制到本地的镜像并刷新 UI,但不能直接修改属性值、AbilityID 或
Effect 容器。客户端输入应提交请求,由服务器验证后修改组件,再将结果复制回来。

AbilityComponent 是这些系统的连接点,但不替代它们的职责:

  • Ability 通过组件施放技能,并可向自身或其它组件申请 Effect;
  • Effect 将属性、Tag、Ability 和响应规则变化应用到目标组件;
  • AbilityTask 在 AbilityBehavior 中执行具体动作;
  • InventoryComponent 管理道具和装备,并通过组件接口产生 CarryEffect 或 EquipEffect;
  • Intent 将动态 Payload 发送给目标组件,由目标 Reactive Ability 响应。

一个 Actor 可以拥有多个 AbilityComponent。跨组件交互时,调用方必须明确目标组件;来源 组件会写入 EffectContext 或 IntentContext,服务器负责验证跨组件的 Effect、Ability 和 Intent 请求。

LevelingAttribute 用于描述具有等级和经验变化的成长属性,例如角色等级、职业等级、 熟练度或经验等级。它是一个 UT 主资产,保存成长规则和初始值;运行时由 UUTAbilityComponent 创建对应的 LevelingAttribute Item,保存当前等级和经验。

LevelingAttribute 与 ParameterAttribute 的职责不同:

类型 作用 示例
LevelingAttribute 记录等级、经验和升级规则 角色等级、武器熟练度

开发者先创建并配置主资产,再将它放入 AttributeSet,由 AbilityComponent 在初始化时注册。 其它系统通过 FUTLevelingID 引用它,而不是直接依赖资产对象指针。

在内容浏览器中选择:

右键 -> UTGameFramework -> UTPrimary Data Asset -> LevelingAttribute

为资产设置唯一名称,例如 ULV_CharacterLevel,然后在 Details 面板中配置:

配置项 说明
DefaultLevel 没有存档时使用的初始等级
MaxLevelBound 等级允许达到的范围
CanFallDown 是否允许等级下降
LevelUpType 使用固定经验值还是经验曲线升级
DefaultXP 初始经验值
AllowXPAccumulate 升级时是否保留溢出经验
ConstXp 固定升级模式下的升级经验
ExperienceCurve 曲线升级模式下的升级经验曲线
ExperienceCurvesDatatable 可选的经验曲线 DataTable 来源

LevelUpType 使用固定经验时配置 ConstXp;使用经验曲线时配置 ExperienceCurve 或 ExperienceCurvesDatatable。经验曲线的横轴表示当前等级,纵轴表示升到下一级所需经验。 配置完成后编译并保存资产。主资产对象名参与 FUTLevelingID 和 Primary Asset ID,创建 后不要随意重命名。

6.3 向 AbilityComponent 注册 LevelingAttribute

Section titled “6.3 向 AbilityComponent 注册 LevelingAttribute”

AbilityComponent 通过 UUTLevelingAttributeSet 批量注册,而不是逐个填写属性。

  1. 创建 UTAssetSet,选择父类 UUTLevelingAttributeSet。
  2. 在 Set 的 LevelingAttributes 数组中添加 LevelingAttribute 资产。
  3. 将 Set 添加到角色或 AbilityComponent 的 DefaultLevelingAttributeSets。
  4. 游戏开始时,RegisterLevelingAttributes() 遍历这些 Set,并创建运行时 Item。
  5. 使用 GetRegisterLevelingIDs() 检查注册结果,并用 GetLevel(LevelingID) 读取等级。

最小配置关系如下:

ULV_CharacterLevel
-> UUTLevelingAttributeSet.LevelingAttributes
-> AbilityComponent.DefaultLevelingAttributeSets
-> RegisterLevelingAttributes()
-> FUTLevelingID / Runtime Leveling Item

多个 Set 不应重复配置同一个 LevelingAttribute,避免重复注册和默认值来源不明确。默认值 只用于新状态;读档时,保存的等级和经验优先于资产默认值。

验证时应确认 ID 下拉列表可以找到资产、AbilityComponent 已注册对应 ID,且新状态的等级和 经验等于 DefaultLevel、DefaultXP。等级和经验变化还应触发对应委托。

ParameterAttribute 是 UT 中不带等级系统的基础属性定义,适合表示生命值、法力值、攻击力、 敏捷和防御等数值。它是一个 UT 主资产,保存属性的初始值和取值范围;运行时由 UUTAbilityComponent 为它创建 FUTPAItem,保存当前属性状态。

属性通过 FUTAttributeID 标识。GameplayEffect、属性计算、UI 和存档都应使用 Attribute ID 查找属性,而不是在系统之间传递主资产对象指针。

运行时属性包含三类值:

值 作用
BasicValue 属性的基础值,可由基础逻辑直接修改,也是存档中的基础值
MiddleValue 根据基础值和当前有效 GameplayEffect 修饰器计算出的中间值
FinalValue 对外表现值,可被即时属性变化、受击、治疗等直接改变

因此,装备或持续效果通常影响 MiddleValue 的计算;伤害、治疗和消耗等不需要创建可回滚 效果项的即时变化,则可以直接作用于 FinalValue。读档时,保存的运行时值优先于主资产的 默认值,默认配置主要用于新状态初始化。

在内容浏览器中选择:

右键 -> UTGameFramework -> UTPrimary Data Asset -> ParameterAttribute

为资产设置唯一名称,例如 UAT_Health 或 UAT_Agility。在 Details 面板的 AttributeSetting 中配置:

配置项 说明
BasicValueSource 属性初始基础值的来源
ValueRangeLowSource 属性允许的下限来源
ValueRangeHighSource 属性允许的上限来源

每一个 *Source 都有 SourceType:

来源类型 需要配置的内容 用途
None 无 不提供该项的来源
ByStaticValue StaticValue 使用固定数值
ByAnotherAttribute AttributeID 使用另一个已注册属性的值
ByFunction FunctionName 在 AbilityComponent 所属 Actor 上调用一个无参数、返回 float 的函数

例如,最简单的生命值配置是将 BasicValueSource 设为固定值 100,并将上下限来源分别 设为固定值 0 和 150。若最大生命值依赖于体质,则将 ValueRangeHighSource 设为 ByAnotherAttribute,并填写对应的 FUTAttributeID。

使用 ByAnotherAttribute 或 ByFunction 时,依赖的属性或函数必须确实存在。AbilityComponent 注册完属性后会重新解析初始值;循环依赖或无效来源会导致结果无法按预期计算,应在配置阶段避免。 配置完成后编译并保存资产,且不要随意重命名,因为资产名称参与 FUTAttributeID 和 Primary Asset ID。

7.3 向 AbilityComponent 注册 ParameterAttribute

Section titled “7.3 向 AbilityComponent 注册 ParameterAttribute”

ParameterAttribute 通过属性集批量注册,而不是直接逐个填入 AbilityComponent。

  1. 创建一个 UTAssetSet,选择父类 UUTAttributeSet。
  2. 在属性集的 ParameterAttributes 数组中添加 ParameterAttribute 主资产。
  3. 将属性集添加到角色或 AbilityComponent 的 DefaultAttributeSets。
  4. 游戏开始时,RegisterAttributes() 遍历这些属性集,为每个资产创建运行时 FUTPAItem。
  5. 注册完成后,使用 GetRegisterAttributeIDs() 检查 ID,使用 GetAttributeValue() 读取对外属性值。

最小配置关系如下:

UAT_Health / UAT_Agility
-> UUTAttributeSet.ParameterAttributes
-> AbilityComponent.DefaultAttributeSets
-> RegisterAttributes()
-> FUTAttributeID / FUTPAItem

注册阶段会根据 BasicValueSource、上下限来源以及属性间依赖计算初始值。多个属性集不应 重复注册同一个 Attribute ID,避免重复创建运行时属性项和产生不明确的默认值来源。

验证时应确认:属性资产能够在 ID 下拉列表中找到;属性集已加入 DefaultAttributeSets; GetRegisterAttributeIDs() 包含目标 ID;新状态下的 BasicValue、取值范围和 FinalValue 符合资产配置。若属性由另一个属性或函数提供初始值,还应验证依赖来源已经正确解析。

UTGameplayEffect 是 UT 中描述可复用游戏效果的主资产。它保存应用规则、执行策略、持续 时间、周期和一组 UTEffectBehavior;运行时应用到 AbilityComponent 后形成一个 FActivatedUTGEItem。

Effect 负责描述何时有效、持续多久、多久触发一次以及包含哪些行为。具体的属性变化、标签 修改、授予技能、响应规则或其它业务由 Behavior 完成。Ability、道具、装备等系统负责决定 何时向目标 AbilityComponent 申请 Effect,Effect 不负责 Ability 的 Commit 调度。

Active Effect 的重要运行时信息包括 EffectID、ActiveEffectItemHandle、EffectContext、 成功激活的 Behavior 索引、Behavior 快照和聚合器成员关系。Effect 的执行顺序由资产中的 EffectBehaviors 数组决定;清除时只清理本次 Effect 实际产生的结果。

在内容浏览器中选择:

右键 -> UTGameFramework -> UTPrimary Data Asset -> UTGameplayEffect

设置唯一名称后,在 Details 面板配置 EffectApplyRule、EffectExecutePolicy 和 EffectBehaviors。根据需要填写 DurationTime、IsPeriod、TriggerPeriod,并在 AggregationRules 中添加聚合规则类。最后编译并保存,确认 Effect ID 能在下拉列表中找到。

在 Execute 分类中配置 EffectExecutePolicy:

策略 说明
Instant 应用时执行一次,不建立持续时间或周期计时器
HasDuration 在 DurationTime 内保持有效,到期后清除
其它非 Instant 策略 按框架对应的持续生命周期管理

当策略不是 Instant 时,可以启用 IsPeriod,并在 TriggerPeriod 中配置周期秒数。周期 到达后,Active Effect 再次触发周期行为,不会创建新的 Effect Item,也不会改变原有 Handle。

通常,即时伤害、治疗和消耗使用 Instant;持续修饰、标签或授予行为使用 Duration;周期 伤害和回血使用 Duration 加 IsPeriod。需要反向恢复的持续行为由 Active Effect 管理;直接 写入 FinalValue 的不可回滚即时变化不会因清除持续 Effect 而恢复。

EffectApplyRule 是一次 Apply 的整体入口条件,用于判断目标是否允许接收这个 Effect。它 与单个 Behavior 的 CanThisBehaviorApply() 不同:前者决定整个 Effect 是否进入应用流程, 后者只决定某个 Behavior 是否能在当前目标上执行。

Apply 失败时不应把请求当作成功的 Active Effect。即使整体检查通过,某个 Behavior 仍可能因 目标缺少属性、Ability 或其它前置条件而失败;系统只记录实际成功的 Behavior。

UUTGameplayEffectBehavior 是 Effect 的可复用业务行为,以 Instanced Behavior 对象的形式 配置在 UTGameplayEffect.EffectBehaviors 数组中。具体 Behavior 子类负责实现应用、清除和 必要的周期处理。

常见行为包括属性修饰、属性即时变化、组件 Tag 修改、授予或移除 Ability、授予响应规则、 嵌套 Effect,以及 Behavior_Calculate 自定义属性计算。持续修饰应在 Effect 清除时撤销; 伤害、治疗或消耗等直接改变 FinalValue 的行为属于不可回滚的即时变化。

Behavior 应按业务顺序配置,并只引用目标组件已注册的属性、标签或技能。只有应用成功的 Behavior 才记录到 IndexOfActivatedEffectBehaviors;需要清除的行为应保存 Effect Handle 或 对应的运行时收据。

Behavior_Calculate 在 OnUTEffectApplied 阶段捕获源、目标属性并执行 Calculation 类中的 公式,直接修改目标属性。公式返回 bool,由实现者决定计算成功条件。启用快照时,只有 整个计算成功后,才把该 Behavior 的快照写入 FActivatedUTGEItem.BehaviorSnapshots;即时 Effect 通常不需要快照。

常用 Behavior 包括:

Behavior 用途 生命周期
AttributeValueModifier 添加、缩放或覆盖属性修饰 可回滚
AttributeValueChanger 直接改变属性 FinalValue 不可回滚
CalculateAttribute 捕获属性并运行自定义公式 通常不可回滚
ComponentTagsModifier 添加或移除组件 Tag 可回滚
GrantAbility / TriggerAbility 授予或触发 Ability 按行为管理
GrantReactiveRule 授予响应规则 可回滚
AddOrSetAbilitySlotBinding 添加或修改技能槽绑定 按绑定规则清理
RemoveAbilitySlot 移除整个技能槽 即时操作
TryToApplyEffect / RemoveActiveEffect 申请嵌套 Effect 或移除已有 Effect 按运行时收据管理

持续修饰、Tag 和授予类 Behavior 通常保存清除所需的 Handle 或收据;直接改变 FinalValue 的伤害、治疗和消耗不会因清除 Effect 而反向恢复。

Ability 或 AbilityTask 可以向自身或其它 AbilityComponent 施加 Effect;StaticProp、 InstanceProp、Equipment 和 Potion 等系统可以为所属组件施加 CarryEffect、EquipEffect 或 使用效果;Effect 也可以通过 TryToApplyEffect 申请嵌套 Effect。

Apply 时必须明确目标 AbilityComponent。自身效果通常由目标组件同时作为来源;Ability 对其它 对象施加时,SourceAbilityComponent 与目标组件不同,并写入 EffectContext。命中位置、 骨骼和 HitResult 等业务数据应通过 Intent Payload 或其它消息传递。

Apply -> 创建 Active Effect/Handle -> 写入 Context -> 按顺序执行 Behavior
-> 注册 Aggregator -> 创建 Duration/Period 定时器 -> 周期触发
-> 到期或 Clear -> 清理 Behavior、Aggregator 和嵌套 Effect

ApplicationMultiplicity 表示一个 Active Item 承载的逻辑应用次数,不一定对应相同数量的 物理 Item。清除应使用 Effect Handle 定位,不能只按 Effect ID 清除。周期触发沿用原 Handle, 不会创建新的 Active Effect。

当前 FUTEffectContext 主要保存本次应用的 CasterAbilityComponent,用于计算和行为判断。 它不是完整的 GAS GameplayEffectContext,也不会自动包含 HitResult、命中位置或命中骨骼。 需要传递命中数据时,推荐由 Ability 构造 Intent Payload,由目标响应技能读取并结合目标实时 属性计算。

多人游戏中 Effect 的权威创建、清除和属性结果由服务器决定。客户端可以拥有预测或表现用的 镜像,但不能把客户端结果当作最终权威。EffectContext 中的组件指针是本地运行时引用;跨网 络数据应使用可复制的 ID、Handle 或 Payload。

需要保存的持续 Effect 由组件存档流程决定。恢复时根据 Effect ID 获取主资产,重建 Active Effect、Behavior 和 Aggregator;属性 MiddleValue 应在持续 Effect 和聚合器恢复后重新计算。

客户端可以预测技能开始或播放表现,但不能把本地 Apply 结果当作最终属性结果。服务器负责 验证 Effect 的来源、目标、规则和应用结果;跨网络传递的数据应使用可复制的 ID、Handle 或 Payload,不能依赖 FUTEffectContext 中的本地组件指针。

Aggregator 是目标 AbilityComponent 上独立维护的聚合状态,用于处理多个 Active Effect 对 同一规则的共同贡献。它把 Effect 是否存在与聚合结果分开,因此 Effect 不需要实现旧式的 按来源堆叠逻辑。

Effect 可以配置 AggregationRules。规则以类作为配置和计算定义,运行时在目标组件中形成 对应 Aggregator。每个成员由 ActiveEffectHandle 标识来源,并记录该 Effect 提供的贡献数量; Aggregator 根据当前成员和规则的 MaxStackCount 计算有效堆叠数。

成员增加、移除或数量变化时,Aggregator 重新计算结果,并按运行时贡献收据更新目标组件。 当最后一个成员移除时,相关贡献也会清理。Effect 清除时只移除自己的成员和贡献;Aggregator 的生命周期属于目标 AbilityComponent,而不是某一个 Effect。

配置 AggregationRule 时,应明确聚合键、贡献类型、最大堆叠数和周期行为,避免不同规则意外 合并。读档和网络复制需要使用成员关系及必要的贡献状态恢复目标侧聚合结果。

持续加攻击力可使用 HasDuration 加 AttributeValueModifier;周期回血可使用带 IsPeriod 的 Duration Effect 加 CalculateAttribute;命中伤害则通常由 Ability 通过 Intent 传递数据,再由目标响应技能或 Calculate Behavior 修改 FinalValue。

验证时至少检查 Effect ID、目标组件、Active Effect Handle、Behavior 成功结果和 Clear 后的 状态。周期效果还应检查触发次数与到期行为,多人游戏还要分别确认服务器权威值和客户端同步值。

UTAbility 是 UT 中可执行游戏行为的运行时对象。主资产保存 Ability 的配置和类型, AbilityComponent 负责注册、创建、查询和管理运行时 Ability。Ability 的具体业务由 AbilityBehavior 编排;AbilityBehavior 再创建和管理 AbilityTask,Task 通过 TaskExecution 完成具体动作。Ability 本身负责生命周期、阶段、权威判定和结果收口。

UUTAbilityComponent 是 Ability 运行时状态的归属者。它保存已注册的 Ability、属性、Effect、 技能槽和响应技能监听状态。AbilitySet 中的 AbilityAsset 会在初始化阶段注册为运行时 FUTAbilityItem,需要执行时再创建对应的 Ability 实例。

FUTAbilityID 标识 Ability 主资产,FUTActivatedAbilityHandle 标识一次运行实例。不要把 Ability ID 当作某次施放实例的 Handle,也不要在业务系统中依赖 Ability 对象的硬引用。

创建 AbilityAsset 后,在资产中配置 Ability 类型、对应的 Ability Class 以及业务参数。再 创建匹配的 Ability 蓝图类,并让 Asset 的 Class 指向该类。Asset 负责静态配置,Ability 蓝图负责运行逻辑;两者不是同一个对象。

主动技能使用 UUTActiveAbilityAsset,响应技能使用 UUTReactiveAbilityAsset。配置完成后 编译并保存 Asset 和 Ability 蓝图,并确认 Ability ID 已被 AssetManager 扫描。

类型 触发方式 典型用途
Active Ability 输入或系统主动调用 攻击、移动技能、主动治疗
Reactive Ability 收到 Intent 后自动响应 受击、命中、交互事件

两者都使用 AbilityTask 完成业务,但入口不同。主动技能由 Cast 流程创建,响应技能由 Intent 路由、筛选并创建响应实例;响应技能不能通过主动技能入口直接施放。

Ability 的主体运行逻辑由 AbilityBehavior 驱动,主动技能和响应技能都遵守同一套基本生命周期。 AbilityBehavior 根据当前阶段创建 Task,等待 Task 结束,再决定继续、提交或结束 Ability。

正常合法结束:

CastAbility -> AbilityBehavior 执行 -> CompleteAbility -> EndAbility

取消或非法结束:

CastAbility -> Cancel / Reject / Rollback -> EndAbility

CompleteAbility() 是合法完成入口;EndAbility() 是最终清理入口,不负责把非法状态判定为 成功。取消、服务器拒绝和非法续段都应先执行对应的回滚或清理,再结束 Ability。

Ability 的 Commit 是服务器对当前技能施放进行的权威判定,主动技能和响应技能都适用。服务器 检查 Ability 是否已注册、施放条件、目标、Tag、资源和网络请求是否合法;只有确认成功后, 当前 Casting 才能进入已提交状态。

Ability Commit 与 Task Commit 不是同一个动作。Ability Commit 决定整次施放能否被权威接受; Task Commit 则跟随 Ability 的提交结果,让需要服务器确认的具体 Execution 执行最终动作并结束。

Task 是 Ability 中可组合的业务单元。Task Shell 管理任务身份、阶段和生命周期, TaskExecution 承担一次具体执行。常见 Execution 包括 Business、Data、Listen、Tick、 Performance 和 EmitIntent。

主动技能和响应技能都可以使用这些 Task。ListenExecution 用于等待消息或事件,DataExecution 用于数据操作,EmitIntent Execution 用于向其它 AbilityComponent 投递 Intent。

Task 创建后绑定到当前 Ability 和 Paragraph,随后进入执行、等待、完成或取消状态。阻塞型 Task 只等待自己结束,不应阻塞无关 Task。Task 结束后必须执行清理,解除监听、计时器、 临时对象和输入绑定。

Execute 是 Task/Execution 专有动作:Task 调用 Execution 的 Execute(),由具体 Execution 创建本次业务状态。需要服务器确认的 Execution 应保持 Running,等待 Ability Commit 后再执行 自己的 Commit();不需要等待的 Execution 可以在 Execute 后正常结束。

非法阶段或创建失败的 Task 不应继续执行;Ability 完成或取消时,仍存活的 Task 会进入最终 清理流程。

类型 主要用途
BusinessExecution 通用业务动作和运行时逻辑
DataExecution Effect、属性等权威数据修改
ListenExecution 等待 Gameplay Message、Intent 或其它事件
TickExecution 按 Tick 持续执行逻辑
PerformanceExecution 播放 Montage、Niagara、声音、UI 等表现
EmitIntent Execution 向 AbilityComponent 或 MessageSystem 发出 Intent

Task 没有 Settle 动作。Settle 属于 Ability;Settle 期间创建的 Task 只是 Ability 结算事务 中的执行单元。

Cancel 表示主动或系统原因中断 Ability;Rollback 恢复当前未提交的 Casting 状态和 相关快照;Cleanup 负责释放 Task、监听器、定时器和临时对象。已经提交或明确不可回滚的 变化不能被普通 Rollback 随意撤销。

多段 Ability 需要区分当前段回滚和跨段快照。响应技能还可能因为上游 Intent 失败而整链 回滚,只应回滚与该 Intent Handle 关联的响应实例。

主动技能的基本链路是:

输入或调用 -> TryCastAbility -> 创建 Ability/Paragraph -> AbilityBehavior
-> 创建并执行 Task -> Ability Commit -> Task Commit
-> Ability Settle -> Complete,或 Cancel/Rollback -> Cleanup

AbilityComponent 负责验证 Ability 是否已注册、是否允许施放以及是否存在冲突状态。Ability 蓝图中的具体业务应通过 Task 组织,不要把所有动作堆在单一事件节点中。

推荐将输入绑定到 Ability Slot,而不是直接绑定某个 Ability。Slot 由 AbilitySlotName 标识, 并保存默认 Ability 和运行时绑定。AbilitySet 提供默认槽位绑定;Effect 可以通过 AddOrSetAbilitySlotBinding 添加或覆盖运行时绑定,通过 RemoveAbilitySlot 删除整个槽位。

槽位存在但 AbilityID 为 None 时表示空槽,不能施放。Ability 可以被替换,而输入方只需要 选择槽位,不需要知道当前槽位内的具体技能。

CastAbilitySlot 的调用方只提供 SlotName。AbilityComponent 解析槽位当前的最终 AbilityID, 确认它是已注册的主动技能后进入施放流程。空槽、未注册技能或不满足施放条件时应失败。

网络环境下客户端可以先使用本地槽位镜像进行预测;服务器 RPC 只传递 SlotName,由服务器 重新解析并校验,客户端最终以服务器的槽位和施放结果为准。

旧方案允许 InputAction 或 Legacy Input Tag 直接绑定 Ability。当前推荐由 Enhanced Input 或其它输入层识别按键,再调用 CastAbilitySlot。这样输入层只关心槽位,技能替换不需要修改 输入逻辑。

角色移动、视角和 UI 输入适合放在 PlayerController;技能的实际执行仍属于 AbilityComponent 和 Ability。项目可以暂时保留旧输入结构用于兼容,但新功能不应继续扩大对旧直接绑定方案的依赖。

Reactive Ability 由 Intent 触发。发送方通过 Intent Channel、Intent Tag 和 Payload 描述 事件,目标 AbilityComponent 根据监听记录找到可响应的 AbilityAsset,并创建响应 Ability 实例。

Payload 使用 FInstanceStruct 承载业务数据。响应 Ability 可通过 GetIntentPayload() 读取 Payload,并使用对应的 ScriptStruct 判断和解析数据。IntentContext 还包含 HandleData 和 RuntimeRefs 等运行时信息,但这些不是业务 Payload 的替代品。

响应技能首先按 Intent Channel、Intent Tag 和匹配类型筛选,再按响应模式决定执行一个还是 多个响应技能:

模式 语义
SingleBest 选择一个最匹配的响应技能
AllMatches 并发执行所有符合条件的响应技能

Priority 用于筛选或选择规则;Payload 不参与响应技能选型,只在响应实例执行时读取。响应 技能的业务仍通过 Task 完成,可以监听后执行动画、Apply Effect、Emit Intent 或其它操作。

Intent 的网络行为由 AbilityAsset 的执行策略决定,可支持客户端等待、客户端预测和仅客户端 表现等模式。服务器负责验证来源、目标、请求身份和响应结果;客户端预测状态必须能够按 Intent Handle 对齐,并在服务器拒绝或超时时回滚。

Intent 的来源身份和网络字段应使用可复制的 HandleData;RuntimeRefs 只保存本端可解析的 临时引用。一个 Intent 的响应实例、ACK、超时和回滚都应使用同一个 Intent Handle 关联。

9.18 Ability 与 Effect、Task、属性系统的关系

Section titled “9.18 Ability 与 Effect、Task、属性系统的关系”
输入 -> Active Ability -> Task/Execution -> Apply Effect 或 Emit Intent
-> 目标 AbilityComponent
-> Attribute / Effect / Reactive Ability

Ability 决定何时执行,Task 负责具体动作,Effect 负责持续状态和属性、Tag 等变化,Intent 负责携带动态业务数据并触发响应。AbilityComponent 是这些运行时状态和网络同步的归属者。

本节给出主动技能和响应技能的最小配置流程。两者的入口不同,但蓝图内的业务组织方式相同:

Ability 实例
-> AbilityBehavior 主体逻辑
-> Run Business Task / Start Listen Task
-> TaskExecution 执行具体业务
-> CompleteAbility() 或 CompleteParagraph()
  1. 在内容浏览器中创建 ActiveAbilityAsset,例如 UABP_A_Fireball。
  2. 创建一个继承 UUTActiveAbility 的 Ability 蓝图,例如 UAB_A_Fireball。
  3. 回到 AbilityAsset,将 ActiveAbilityClass 或对应的 Class 指向 UAB_A_Fireball。
  4. 在 AbilitySet 的默认 Ability 列表中添加这个 AbilityAsset。
  5. 如果通过技能槽施放,在 AbilitySet 中配置 AbilitySlotName 与该主动技能的默认绑定。
  6. 编译并保存 Asset、Ability 蓝图和 AbilitySet,确认 Ability ID 能被 AssetManager 找到。

在主动技能蓝图中组织 AbilityBehavior

Section titled “在主动技能蓝图中组织 AbilityBehavior”

在 Ability 蓝图的 AbilityBehavior 事件或对应行为入口中,按业务顺序放置节点:

  1. 使用 Run Business Task 创建并运行一个 Business Task,在节点上选择所需的 BusinessExecution 子类,完成一次性业务,例如播放动作、执行命中检测或申请 Effect。
  2. 需要等待消息、通知或其它事件时,使用 Start Listen Task,选择对应的 ListenExecution 子类,并设置监听通道、Tag 或其它筛选参数。
  3. 如果业务需要多个 Task,按依赖顺序连接它们;后续逻辑应放在 Task 完成回调或任务结果 分支之后,不要假设异步 Task 会在创建节点后立即完成。
  4. 需要向目标施加 Effect 或发送 Intent 时,使用对应的 Data/EmitIntent Task,并明确目标 AbilityComponent。

AbilityBehavior 的主体逻辑完成后,必须显式调用合法结束函数:

  • 单段主动技能在最后调用 CompleteAbility();
  • 多段主动技能在每个非最后段调用 CompleteParagraph(),最后一段调用 CompleteAbility();
  • 如果仍有 Blocking Task,Complete 请求会等待这些 Task 正常结束,而不是立即强制结束。

CompleteAbility() 和 CompleteParagraph() 都表示本次逻辑已经合法完成,会进入 Ability 的 Settle 和最终清理流程。它们不是取消函数,也不能用来处理失败分支。

输入层识别按键后,推荐调用 AbilityComponent 的 CastAbilitySlot(SlotName)。组件根据槽位 解析当前的主动 Ability,再创建运行时 Ability 实例。输入层不应直接依赖槽位当前绑定的 Ability 蓝图,这样 Effect 替换槽位内容时无需修改输入逻辑。

  1. 在内容浏览器中创建 ReactiveAbilityAsset,例如 UABP_R_OnHit。
  2. 创建一个继承 UUTReactiveAbility 的 Ability 蓝图,例如 UAB_R_OnHit。
  3. 在响应 AbilityAsset 中将 ReactiveAbilityClass 或对应的 Class 指向该蓝图。
  4. 配置 Intent Channel、Intent Tag、匹配类型、响应优先级和网络执行策略。
  5. 在目标 AbilitySet 中注册响应 AbilityAsset,并编译保存相关资产。

发送方的 Ability 或 Task 通过 Emit Reactive Intent To Ability Component,或对应的 MessageSystem 节点,发送 Intent。目标 AbilityComponent 根据 Channel、Tag 和匹配规则创建 响应 Ability 实例。响应蓝图通过 GetIntentPayload() 读取 FInstanceStruct Payload,并 根据 Payload 的实际 ScriptStruct 解析业务数据。

在响应技能蓝图中执行并合法结束

Section titled “在响应技能蓝图中执行并合法结束”

响应技能同样在 AbilityBehavior 中使用 Run Business Task 和 Start Listen Task:

  1. 使用 Run Business Task 执行受击表现、Apply Effect、属性计算或其它业务。
  2. 需要等待动画通知、Gameplay Message 或后续 Intent 时,使用 Start Listen Task。
  3. 从 Intent Payload 获取动态数据,结合目标 AbilityComponent 的实时属性完成计算。
  4. 响应逻辑完成后调用 CompleteAbility(),使本次响应实例合法结束。

响应技能不是通过 CastAbilitySlot 主动施放的;它的生命周期从 Intent 路由开始,仍然经过 Commit、Task 执行、Settle(如果配置)和 Complete。

主动技能应依次验证:

  • AbilityAsset 的 Class 是否指向正确 Ability 蓝图;
  • AbilitySet 是否注册 Asset;
  • Ability Slot 是否解析到预期 AbilityID;
  • Run Business Task 是否创建并执行;
  • Start Listen Task 是否收到预期事件;
  • Ability Commit、Task Commit 和 Complete 是否按预期发生;
  • CompleteAbility() 或 CompleteParagraph() 后是否进入 Settle 和 Cleanup。

响应技能还应验证:

  • Intent Channel 和 Tag 是否完全匹配;
  • Payload 的 ScriptStruct 和字段值是否正确;
  • SingleBest 或 AllMatches 是否符合配置;
  • 响应 Ability 是否创建了对应实例和 Task;
  • Intent Handle 是否贯穿发送、响应、ACK 和回滚;
  • 服务器拒绝或超时时,响应实例和相关 Task 是否正确 Rollback/Cleanup。

UT 提供多种属性修改入口。选择方式时,首先要区分这是临时状态修饰,还是伤害、治疗、资源 消耗等已经发生的即时变化;还要区分是否需要动态 Payload、复杂公式和响应技能链路。

1. Apply Effect + Attribute Value Modifier

Section titled “1. Apply Effect + Attribute Value Modifier”

创建一个 UTGameplayEffect,添加 AttributeValueModifier Behavior,填写目标属性、修改 方式和修改值,然后通过 Apply Effect 施加。

这个 Behavior 会为目标属性创建一个可回滚的 Modifier,重新计算属性的 MiddleValue,并把 MiddleValue 的差值同步反映到 FinalValue。当 Active Effect 到期或被 Clear 时,Modifier 被移除,MiddleValue 和由它产生的差值会重新计算。

它适合 Buff、Debuff、装备属性和其它临时修饰。例如给攻击力增加 20,持续时间结束后该加成 消失。它不适合表示已经发生且不应因清除 Effect 而恢复的伤害或治疗。

添加 AttributeValueChanger Behavior,通过 TargetAttributeID 指定目标属性,通过 ChangeMethod 选择修改方式,并设置固定值或另一个属性作为 ChangeValue。

它直接作用于属性的 FinalValue,不创建用于 MiddleValue 的 Modifier,属于不可回滚行为。 Effect 被移除后,已经产生的最终值变化仍然保留。因此它适合一次性伤害、治疗、扣除法力或 其它已经结算的资源变化。

与 Modifier 的区别是:Modifier 表示“当前有一个持续修饰来源”,Changer 表示“现在直接 改变一次最终表现值”。

3. Apply Effect + Calculate Attribute Behavior

Section titled “3. Apply Effect + Calculate Attribute Behavior”

添加 Calculate Attribute Behavior,指定 CalculationClass,并按需要设置 bUseSnapshot。 Calculation 类负责读取源、目标 AbilityComponent 的属性,执行开发者定义的公式,并直接将 计算结果作用到目标属性。

该 Behavior 适合把可复用的计算公式配置进 Effect。公式返回 bool,由实现者决定本次计算 是否成功;只有成功时,Behavior 才被记录为已激活。启用快照时,成功的计算会把该 Behavior 对应的 Source/Target 属性快照保存到 Active Effect;不启用快照时,周期触发使用当时的实时 属性。Instant Effect 通常不需要快照。

在 AbilityBehavior 中使用 CalculateAttributeAnd Apply,创建 UUTTaskExecution_CalculateAttribute。配置 FCalculateAttributeMessage、目标组件、 目标 AttributeID,以及是否应用和是否预测应用。

该 Execution 的计算入口是可覆写的 ComputeValue()。它先读取消息中的 SourceValue 或 SourceAbilityComponent,再计算出 Delta;若允许应用,则将 Delta 作用到目标属性的 FinalValue,并广播属性变化和计算结果。它属于 Task/Execution 体系,不需要 GameplayEffect 或独立 Ability 资产之外的 Effect 实例。

它适合 Ability 已经掌握动态数据、需要在明确的 Task 阶段完成一次计算的场景,例如命中后把 攻击方计算出的伤害值传给目标,或在 Ability 内完成治疗、消耗和其它结算。需要网络确认时, 应遵循 DataExecution 的服务器权威和 Commit 规则;bPredictiveApply 只控制预测行为,不会 替代服务器校验。

当属性变化依赖命中信息、技能上下文或复杂的跨组件交互时,可以使用 Intent 和 Reactive Ability:

主动 Ability
-> 命中检测 / 捕获施法数据
-> Emit Reactive Intent
-> 目标 AbilityComponent 路由 Intent
-> 筛选 Reactive Ability
-> Reactive Ability 读取 Payload
-> AbilityBehavior 创建 Task
-> Apply Effect 或 CalculateAttribute
-> CompleteAbility()

发送方把动态业务数据放入 FInstanceStruct Payload,例如攻击力、命中部位或技能上下文。 目标组件根据 Intent Channel、Intent Tag、匹配类型和响应模式筛选响应技能;响应技能通过 GetIntentPayload() 读取并解析 Payload,再结合目标自己的实时属性执行计算。

该方式适合受击、命中、护盾、抗性、反击和多组件交互。它把“发送方捕获的数据”和“目标方 实时属性”分开,解决 Apply Effect 本身无法直接接收动态参数的问题。响应技能仍通过 AbilityTask 完成业务,并经过 Commit、Complete、Settle(如有配置)和网络回滚流程。

方法 主要结果 是否回滚 适用场景
AttributeValueModifier 修改 MiddleValue 并反映到 FinalValue 可以 Buff、Debuff、装备修饰
AttributeValueChanger 直接修改 FinalValue 不可以 伤害、治疗、资源消耗
Calculate Behavior 在 Effect 中执行可复用公式 通常不可以 Effect 驱动的属性计算
CalculateAttribute Task 在 Ability Task 中计算并应用 按 Task/网络策略 Ability 已持有动态数据
Intent + Reactive Ability Payload 加目标实时属性的响应计算 按 Intent 链路 命中、受击、跨组件业务

这些方法可以组合使用。例如主动 Ability 用 Intent 传递攻击数据,目标 Reactive Ability 读取 Payload 后,再使用 CalculateAttribute 或 CalculateAttribute Task 完成最终伤害 计算;但同一次业务不要同时重复应用多个入口,避免属性被重复修改。

UUTInventoryComponent 管理 Actor 所拥有的道具和装备运行时状态。它通常与 UUTAbilityComponent 一起挂在角色或 Pawn 上,负责 StaticProp 数量、InstanceProp 实例、 装备槽以及相关的使用、装备和存档操作。

容器或配置 作用
StaticGamepropsContainer 保存按 FUTStaticGamePropID 堆叠的静态道具数量
InstanceGamepropsContainer 保存按实例 GUID 区分的 InstanceProp
EquippedContainer 保存装备槽与已装备 InstanceProp 的关系
DefaultStaticPropsSets 新状态注入的默认 StaticProp 集合
DefaultInstancePropsSets 新状态注入的默认 InstanceProp 集合
EquipmentSlots 该组件可使用的装备槽名称

StaticProp 适合可堆叠、同类共享定义的物品;InstanceProp 适合每个实例拥有独立 GUID、属性或 装备状态的物品。Equipment 是 InstanceProp 的一种用途和运行时关系。

10.3 InventoryComponent 的配置与初始化

Section titled “10.3 InventoryComponent 的配置与初始化”

在拥有 InventoryComponent 的 Actor 上配置默认 StaticPropSet、InstancePropSet 和 EquipmentSlots。装备资产还应配置 AllowEquipSlots 以及装备效果。

新状态初始化时,组件把默认集合中的物品加入运行时容器,并初始化装备槽。读取存档时,应由 存档数据重建实际容器,而不是重复追加默认物品。

10.4 InventoryComponent 与 StaticProp、InstanceProp、Equipment 的关系

Section titled “10.4 InventoryComponent 与 StaticProp、InstanceProp、Equipment 的关系”
InventoryComponent
-> StaticProp ID -> 数量堆叠
-> InstanceProp ID + GUID -> 独立实例
-> EquipmentSlot -> 已装备 InstanceProp

StaticProp 通常由 ID 获取主资产;InstanceProp 由 ID 获取主资产,再由运行时实例承载 GUID 和 实例数据。Equipment 装备时检查目标实例允许的槽位,建立槽位关系并触发装备效果。

10.5 StaticProp 与 InstanceProp 的区别

Section titled “10.5 StaticProp 与 InstanceProp 的区别”
项目 StaticProp InstanceProp
身份 FUTStaticGamePropID FUTInstanceGamePropID 加实例 GUID
数量 通常可堆叠 通常按独立实例保存
个体状态 主资产统一定义 可拥有独立运行时数据
典型用途 药水、材料、任务物品 武器、装备、戒指

10.6 InventoryComponent 的运行时操作

Section titled “10.6 InventoryComponent 的运行时操作”

StaticProp 支持按 ID 添加、查询数量、取得、移除、清空和使用。使用前应检查数量和 CanUse();成功使用后才进入使用及消耗逻辑。InstanceProp 支持创建、加入、按 GUID 查询、 装备、卸下和移除。所有容器变更都应通过 InventoryComponent 接口完成。

可使用 StaticProp 继承 UUTStaticGameprop_Useable。组件先执行 CanUse(),通过后调用 OnUsed()。UUTStaticPropUseLogic 是无状态逻辑类,通过主资产上的 Class 指定,适合承载 可蓝图覆写的使用行为。

消耗品可在使用成功后执行 OnConsumed() 并扣除 UseExpend。药水等具体逻辑可以通过 InventoryComponent 获取关联 AbilityComponent,再申请 Buff 或其它 GameplayEffect。

InstanceProp -> 检查 AllowEquipSlots -> 选择 EquipmentSlot
-> Equip -> 应用 EquipmentEffect -> OnEquip
-> UnEquip -> 移除 EquipmentEffect -> OnUnequip

装备槽名称来自项目配置和组件的 EquipmentSlots;装备资产的 AllowEquipSlots 决定允许 进入的槽位。

10.9 InventoryComponent 与 AbilityComponent 的关系

Section titled “10.9 InventoryComponent 与 AbilityComponent 的关系”

InventoryComponent 通过拥有者或关联接口找到 AbilityComponent。道具使用、装备和卸下可以为 关联组件施加或清除 CarryEffect、EquipmentEffect;药水逻辑也可以申请治疗、Buff 或其它 Ability 业务。

10.10 InventoryComponent 的网络与存档

Section titled “10.10 InventoryComponent 的网络与存档”

物品数量、实例数据和装备槽由服务器权威修改,客户端接收复制后的运行时容器。客户端请求 操作时,服务器应重新验证数量、实例 GUID、槽位和使用条件。

存档记录 StaticProp 数量、InstanceProp 实例数据和装备槽关系。恢复时根据 ID 获取主资产并 由存档数据重建容器;默认集合不应重复追加。装备恢复后还需重新建立 EquipmentEffect。

网络操作必须由服务器修改容器。客户端只能请求拾取、使用、丢弃、装备或卸下;服务器重新 检查数量、实例 GUID、装备槽和使用条件后,才更新容器并复制结果。客户端 UI 应在收到复制 状态后刷新,不能通过本地修改数量来模拟成功操作。

10.11 InventoryComponent 配置示例与验证

Section titled “10.11 InventoryComponent 配置示例与验证”

创建 StaticProp 后,将它加入 StaticPropSet,再把 Set 添加到 DefaultStaticPropsSets, 开始游戏检查数量和 ID。创建药水时配置 UseLogic 和 UseExpend,调用 UseStaticProp 验证 使用、Effect 和扣除顺序。创建装备时配置 AllowEquipSlots,调用 Equip/UnEquip 检查槽位、 装备效果及属性变化。

验证时检查 Asset ID、默认集合、运行时容器、数量或 GUID、装备槽、关联 AbilityComponent、 网络同步和存档恢复,并确认读档不会再次注入默认物品或重复应用装备效果。

StaticProp 是由主资产统一描述、不具有状态,自有词条,自定义插槽等内容的静态道具,例如药水、材料和任务物品。 它继承自 UUTStaticGamePropBase,使用 FUTStaticGamePropID 标识。InventoryComponent 运行时保存的是 ID 和数量,具体名称、描述、图标、最大堆叠数及 CarryEffect 从主资产获取。

StaticProp 不为每一件物品创建独立实例;同一个 ID 的多个物品通常合并到同一个堆叠项中。 需要独立 GUID、耐久或随机属性的物品应使用 InstanceProp。

在内容浏览器中选择:

右键 -> UTGameFramework -> UTPrimary Data Asset -> StaticProp

配置主资产的通用信息、PropIcon、MaxStackAmount 和 CarryEffects。MaxStackAmount 未设置封顶时表示不限制持有数量;设置后,它表示该 StaticProp ID 的单条聚合数量上限, 超过上限不会拆分出第二个堆叠项。需要使用的道具选择 UUTStaticGameprop_Useable 或其子类,需要消耗的道具选择 UUTStaticGameProp_Consumables。

保存资产后确认它位于 StaticProp 扫描路径,AssetManager 已登记其 Primary Asset ID。主资产 名称参与 FUTStaticGamePropID,创建后不应随意重命名。

InventoryComponent 的 StaticGamepropsContainer 保存每个 StaticProp ID 的堆叠项。添加、 查询、取得、移除和清空都应通过 InventoryComponent 接口完成:

FUTStaticGamePropID -> FStaticGamePropStackItem -> StackAmount

常用蓝图操作包括 Get Static Prop Amount、Get All Static Prop Bases、Take StaticProps、 Remove StaticProps、Clear Static Props 和 Use StaticProp。数量变化会触发相应委托,便于 背包 UI 刷新。添加物品时,主资产的 CarryEffects 可以应用到关联 AbilityComponent;移除对应 数量时应清理由这些物品产生的效果。CarryEffect 是由数量派生的运行时状态,不是 StaticProp 的事实源:数量增加 Delta 时应用 Delta 份贡献,数量减少 Delta 时撤销 Delta 份贡献,数量 清零时清理剩余贡献。存档只需保存 StaticPropID 和数量,恢复后再按数量重建 CarryEffect。

UUTStaticGamePropBase 只描述静态道具本身。需要响应“使用”操作时,应创建它的子类。 当前主要有两层:

类型 职责
UUTStaticGameprop_Useable 可被使用,但不强制消耗数量的道具基类
UUTStaticGameProp_Consumables 在 Useable 基础上增加使用消耗的消耗品基类

普通 Useable 适合书籍、开关道具或使用后执行一次业务但不扣除堆叠数量的物品。Consumables 适合药水、卷轴和食物等使用后需要扣除数量的物品,并通过 UseExpend 配置一次使用消耗 的数量。

  • CanUse(InventoryComponent):使用前置检查,判断当前物品是否允许使用;
  • OnUsed(InventoryComponent):使用成功后执行主要使用行为,返回本次使用是否成功;
  • OnConsumed(InventoryComponent):使用后的附带处理,普通 Useable 默认可以不产生消耗。

Consumables 覆写这些函数,在 CanUse 中检查数量是否不少于 UseExpend,在 OnUsed 中 执行主要逻辑,并在使用成功后通过 OnConsumed 扣除 UseExpend。OnConsumed 不再重复 判断使用是否成功。

UseStaticProp
-> Cast to UUTStaticGameprop_Useable
-> CanUse
-> OnUsed
-> OnConsumed(UUTStaticGameProp_Consumables)
-> 更新堆叠数量(消耗品)

如果 CanUse() 失败,不应调用 OnUsed(),也不应修改库存数量。如果 OnUsed() 返回失败, 不应执行消耗。只有主要使用行为成功后,消耗品才执行 OnConsumed() 并更新堆叠数量。

UUTStaticPropUseLogic 是无状态的可蓝图化逻辑类,通过 Useable 主资产的 UseLogicClass 指定。它提供 CanUse、OnUsed 和 OnConsumed,并可通过 GetAbilityComponent() 从 InventoryComponent 获取关联 AbilityComponent。

例如药水可以在自己的 UseLogic 中将 SourceStaticProp 转换为药水类型,读取它配置的 GameplayEffect,再为关联 AbilityComponent 申请治疗或 Buff。书籍也可以在 UseLogic 中创建 UI,而不必把所有使用行为限制为简单的 Effect 数组。

创建药水时,建立继承 UUTStaticGameProp_Consumables 的主资产,配置图标、堆叠上限、 UseExpend 和 UseLogicClass,将它加入 StaticPropSet,再把 Set 添加到 InventoryComponent 的 DefaultStaticPropsSets。

验证时检查:主资产 ID 能在下拉列表中找到;BeginPlay 后数量正确;添加和移除数量符合堆叠 上限;CanUse 失败不会扣除数量;使用成功后 OnUsed、Effect 或其它逻辑按顺序执行; 消耗品只扣除配置的 UseExpend;存档恢复不会重复注入默认物品或重复应用 CarryEffect。

InstanceProp 用于表示需要独立运行时身份的道具,例如武器、装备或带有耐久、随机属性、 等级,或自定义插槽的物品。它由 UUTInstancePropAsset 描述静态定义,由 AUTInstancePropBase 及其子类 表示世界中的实际 Actor。

InventoryComponent 保存实例的数据,而不是永久保存世界 Actor 指针。实例进入背包后以 FInstanceGamePropStackItem 保存,取出、丢弃或装备时再根据主资产和动态数据重建 Actor。

在内容浏览器中创建 UUTInstancePropAsset 主资产,配置:

配置项 作用
InstancePropIcon UI 展示图标
AUTInstancePropBaseClass 从背包重建实例时使用的 Actor 子类
AllowEquipSlots 允许装备到的槽位
EquipmentEffect 装备时应用的 Effect
CarryEffects 实例放入背包时应用的携带 Effect

InstancePropAsset 保存模板定义,具体实例的 GUID 和运行时数据不应配置在资产中。配置完毕 后编译保存,并确认主资产 ID 能被 AssetManager 找到。

每个 AUTInstancePropBase 都有一个 InstanceGuid,用于区分同一主资产创建的不同实例; InstancePropID 则指向实例使用的主资产。两者职责不同:

InstancePropID -> 这是什么类型的物品
InstanceGuid -> 这是哪一个独立实例
DynamicData -> 这个实例当前的运行时数据

实例可以覆写 MakeInstancePropDynamicData() 和 RebuildAPropInstance() 保存及恢复自定义 数据,也可以使用 OnInstancePropTaken 和 OnPropInstanceRebuild 执行进入背包或重建后的逻辑。

InstanceProp 的持久事实源只有:

InstanceGuid + InstancePropID + DynamicData

世界 Actor、组件指针、CarryEffectHandles、装备对象缓存等都是运行时派生状态,不应作为背包 数据的稳定身份,也不应直接写入存档。InstanceProp 的核心操作是 Take(Actor -> Struct, 成功后销毁原 Actor)、Rebuild(Struct -> Actor,同时删除背包条目)和 Remove/Delete (直接删除背包数据,不重建 Actor)。直接删除背包数据时没有对应的 Actor,因此不会触发 Actor 删除事件。

实例的典型生命周期如下:

世界 Actor
-> TakePropInstance
-> 保存 ID + GUID + DynamicData 到背包
-> RebuildInstanceProp / 装备 / 丢弃
-> 根据数据重建 AUTInstancePropBase 子类

放入背包时,组件记录实例数据并可应用 CarryEffects;从背包重建或删除时,组件清理该实例 持有的携带效果。常用操作包括按 GUID 查询、Rebuild InstanceProp、RemoveInstanceProp、 EquipByGuid 和 UnEquipByGuid。

不要直接修改 InstanceGamepropsContainer 内部数组。所有实例变更应通过 InventoryComponent 接口完成,以便同步委托、效果收据和存档数据保持一致。

创建一个装备实例时,先创建 UUTInstancePropAsset,将 AUTInstancePropBaseClass 指向 对应的 Actor 子类,配置图标、允许槽位和 EquipmentEffect。Asset 负责模板配置;运行时 Equipment Actor 负责装备行为、应用和撤销装备效果,并保存对应的效果句柄;装备槽只负责 槽位名称和已装备实例 GUID。创建实例后将它放入 InventoryComponent,检查实例 GUID、主资产 ID 和动态数据。

验证时确认:实例进入背包后 Actor 生命周期符合预期;按 GUID 能找到唯一实例;重建后使用了 正确的 Actor 子类并恢复 DynamicData;CarryEffect 在进入和离开背包时正确添加和清理;删除 实例不会影响同一主资产的其它实例;存档恢复后 ID、GUID、动态数据和效果状态保持一致。

Equipment 是 InstanceProp 的特化类型,不是独立的库存系统,也不需要单独的 EquipmentComponent。 未装备时,它就是 InventoryComponent 中的一条 InstanceProp 数据;装备后迁移到某个常驻装备槽, 并由运行时装备 Actor 管理装备行为和效果。

装备的静态定义由 UUTInstancePropAsset 保存,运行时对象通常是 AUTInstanceGameProp_Equipment。主资产配置图标、AUTInstancePropBaseClass、允许装备槽位、 EquipmentEffect 和其它模板数据;实例自身保存 GUID 和 DynamicData。

UUTInstancePropAsset
-> InstancePropID / 模板配置
AUTInstanceGameProp_Equipment
-> InstanceGuid / 动态数据 / 装备行为

同一装备主资产可以创建多个独立实例,它们通过 InstanceGuid 区分。

先在项目全局配置中定义可用的 FUTEquipmentSlotName,再在 InventoryComponent 的 EquipmentSlots 中声明当前角色拥有的槽位。组件初始化时会为每个有效且不重复的槽位创建 常驻 FEquipmentSlotItem,即使当前没有装备,该槽位也存在。

FEquipmentSlotItem 只负责 EquipmentSlotName、EquippedInstanceGuid 和本地弱引用缓存。 弱引用失效时,应依靠 GUID 查询和重建,不应把弱引用当作网络或存档真值。

装备的运行时迁移流程是:

背包 InstanceProp 数据
-> Rebuild 为 AUTInstanceGameProp_Equipment
-> 检查 AllowEquipSlots
-> AssignEquipmentToSlot
-> OnEquip / ApplyEquipEffects

卸下时解除槽位绑定,调用 OnUnEquip 和 UnapplyEquipEffects,再按组件规则将实例返回 背包。支持按对象操作,也支持更稳定的 GUID 操作:EquipByGuid、UnEquipByGuid 和 UnEquipFromSlot。

装备失败时应保留原背包数据或将已重建对象回包,不能丢失实例。替换已有装备时,先卸下旧装备, 再将其返回背包,最后装备新实例。

装备效果由运行时 AUTInstanceGameProp_Equipment 负责应用和撤销,并由装备对象保存本次 应用产生的效果收据。装备槽不负责管理 Effect 生命周期。

OnEquip
-> ApplyEquipEffects
-> EquipmentEffect 生效
OnUnEquip
-> UnapplyEquipEffects
-> 根据收据清理效果

EquipmentEffect 通常是持续 Effect,装备存在于槽位时生效,卸下时清理。CarryEffect 和 EquipmentEffect 都是由实例归属状态派生的运行时结果,不是装备实例的持久身份。

多人游戏中,装备槽、装备实例归属和 EquipmentEffect 由服务器确认。客户端可以预测装备 按钮和表现,但服务器拒绝或纠正后必须以复制的槽位和实例状态为准。

创建装备时,先创建 UUTInstancePropAsset,将实例 Actor Class 指向 AUTInstanceGameProp_Equipment 或其子类,配置 AllowEquipSlots、图标和 EquipmentEffect。 创建装备实例并加入 InventoryComponent,再调用 EquipByGuid 指定槽位。

验证时检查:槽位是否在初始化时常驻存在;实例 GUID 是否保持不变;装备是否只能进入允许的 槽位;装备成功后是否触发 OnEquip 和 EquipmentEffect;卸下后是否触发 OnUnEquip、清理 效果并返回背包;替换装备和失败装备是否都不会丢失实例;读档顺序是否为先建槽位、再重建装备、 最后恢复装备效果。

UT 的 Save/Load 只保存运行时状态的事实源,并在加载时调用各组件已有的恢复接口重建派生 状态。SaveManager 不重新实现 Ability、Attribute、Effect 或 Inventory 的业务逻辑。

需要区分:

  • 主资产:由 AssetManager 根据 ID 查找,不作为运行时存档对象保存;
  • 事实源:应保存的真实游戏数据,例如属性值、道具数量、实例 GUID 和动态数据;
  • 派生状态:可以由事实源重建的数据,例如 Tag、Effect Handle、Aggregator、Task 和 MiddleValue。

UT 根据 UUSaveSettings.bOnlineMode 选择 SaveManager,单机和网游使用互斥的保存路线:

模式 Manager 存档对象 保存范围
单机 UUTStandaloneSaveManager UUTMainSaveGame 整个 World、角色以及持久 Actor
网游 UUTOnlineSaveManager UUTCharacterSaveGame 服务器管理的单个玩家角色

单机模式的重点是 World 状态。SaveManager 根据 PersistentActorGuid 找到当前世界中的 持久 Actor,并保存其 Actor 数据、AbilityComponent 数据和 InventoryComponent 数据。它不 要求玩家登录身份,也不把每个玩家拆成独立的在线角色存档。

网游模式的重点是玩家角色。Save 请求由服务器接收,并按 FPlayerID 找到对应的角色存档; 客户端不能直接写入权威存档。服务器保存角色的属性、等级、持久 Ability-owned Effect、 背包、实例道具和装备等数据,不保存客户端 Ability 实例、Task、临时 Actor 指针或预测状态。

两种模式共享组件级事实源和恢复规则,但顶层存档边界不同:单机可以包含整个 World,网游 通常只保存一个玩家角色。网游重连时,服务器按玩家身份读取并重建角色,再将复制状态同步给 客户端。当前 Standalone 路线只恢复世界中已经存在的持久 Actor;存档中存在但当前世界没有 的动态 Actor 暂不自动生成。

AbilityComponent 主要保存:

数据 是否保存 恢复方式
LevelingAttribute 等级和经验 是 注册默认条目后写回存档值
ParameterAttribute BasicValue 是 恢复到运行时属性项
ParameterAttribute FinalValue 是 恢复并在恢复过程期间冻结
持久 Ability-owned Effect 是 根据 EffectID 重建 Active Effect
Ability 实例、Task、Tag、Aggregator 否 由配置、Effect 和组件状态重新构建
Effect Handle、MiddleValue 否 由恢复流程重新生成或计算

FinalValue 是存档真值。恢复期间,后续 Effect、Prop、Ability、Tag 和依赖刷新不得把 MiddleValue 的差值再次传递到 FinalValue;所有派生效果恢复完成后,才解除 FinalValue 冻结。

InventoryComponent 保存道具和装备的事实源:

类型 存档内容
StaticProp StaticPropID + Amount
InstanceProp InstanceGuid + InstancePropID + DynamicData
Equipment SlotName + FUTInstancedPropSaveItem

不保存世界 Actor 指针、组件指针、CarryEffectHandles、EquipEffectHandles 或其它缓存引用。 恢复时根据 ID 获取主资产,按保存数据重建运行时容器;StaticProp CarryEffect、InstanceProp CarryEffect 和 Equipment EquipEffect 都属于恢复后的派生状态。

当前示例项目采用兼容现有工程的两阶段流程:

BeginPlay
-> 使用 Default 数据建立初始状态
-> SaveManager 成功读取存档
-> 清理可重建状态
-> 按存档数据恢复

没有存档时,使用 DefaultLevelingAttributeSets、DefaultAttributeSets、Default AbilitySet、 Default StaticPropSet 和 Default InstancePropSet。存在存档时,默认数据可能先短暂生效,随后 被存档真值覆盖;当前示例阶段不额外引入全局初始化状态机或默认数据去重保护。

未来正式流程可以采用 Deferred Spawn:先验证存档,再决定使用存档数据生成 Actor,或使用默认 数据正常生成,待数据准备完成后才开放游戏逻辑和 UI。这是后续演进方向,不是当前示例项目的 必需配置。

组件恢复的标准顺序是:

1. 清理旧运行时状态
2. 注册默认 Attribute 和 Ability 容器
3. 恢复 BasicValue、FinalValue,并冻结 FinalValue
4. 恢复需要保存的 Ability-owned 持久 Effect
5. 恢复 Inventory
6. 重建 StaticProp/InstanceProp/Equipment 的派生 Effect
7. 刷新依赖和 MiddleValue
8. 解除 FinalValue 冻结

装备恢复还必须先创建常驻槽位,再按存档重建装备实例,最后调用装备逻辑恢复 EquipmentEffect。 Instant Effect 不创建持久 ActiveEffectItem,不能直接从存档恢复;Consumable Effect 以及 Inventory 派生 Effect 通常不保存,恢复 Inventory 后重新产生。

重复 Load 必须幂等:不能产生重复道具、重复 GUID、重复装备或重复派生效果。恢复完成后, UI 和其它表现层应等待 OnUTActorRestoreCompleted 等恢复完成通知,再读取最终状态。

单机项目启用 Standalone SaveManager 保存整个世界;联机项目启用 Online SaveManager,由 服务器按玩家 ID 保存角色数据。具体 Manager 由 Save 设置的 Online 模式决定,二者不应同时 处理同一份存档请求。

验证新角色时,确认默认等级、属性、Ability 和背包数据正确;修改属性、添加道具、创建实例、 装备物品并施加持久 Effect 后保存,再加载并检查:

  • BasicValue 和 FinalValue 与保存时一致;
  • LevelingAttribute 的等级和经验一致;
  • StaticProp 数量一致且 CarryEffect 未重复;
  • InstanceProp 的 ID、GUID 和 DynamicData 一致;
  • Equipment 先恢复槽位,再恢复实例,最后恢复 EquipmentEffect;
  • Ability、Tag、Aggregator 和 MiddleValue 已由恢复流程正确重建;
  • 连续加载不会增加重复条目或重复效果。

联机验证还应确认 Save 请求由服务器处理,客户端不能直接写入权威存档,重连后服务器根据 玩家身份恢复数据并向客户端同步最终状态。

联机存档的网络规则属于 SaveManager 的权限边界:客户端只发送保存或加载请求,不能指定 其它玩家身份或绕过服务器恢复角色数据。单机 SaveManager 可以直接处理本地世界,但这不 代表同一套调用可以安全地用于联机权威存档。

UTGame 模块提供项目运行时的通用游戏类骨架,建立 Unreal 原生游戏框架类与 UT Ability、Inventory、Save/Load 和 UI 系统之间的连接。它们不是某个具体游戏的业务 实现,而是可被项目蓝图或 C++ 子类扩展的默认入口。

主要类及职责如下:

类 主要职责 运行范围
AUTGameMode 配置本局使用的游戏类,并负责服务器侧规则 服务器
AUTGameState 保存和复制本局的全局状态 服务器创建,客户端可见
AUTPlayerController 玩家输入、视角、玩家 UI 入口以及 Save/Load 请求 每个玩家,服务器和所属客户端
AUTPlayerState 保存需要随玩家存在并可复制的玩家状态 服务器创建,所有相关客户端可见
AUTHUD 管理本地玩家 HUD 和面板,并访问玩家组件 所属客户端
AUTGamePawn / AUTGameCharacter 作为 Pawn/Character 承载角色自身逻辑和 UT 组件 服务器和相关客户端

这些类遵守 Unreal 的生命周期和网络模型。不能把只存在服务器的 GameMode 当作客户端 数据来源,也不应把 HUD 当作权威游戏状态容器。

AUTGamePawn 继承 APawn,AUTGameCharacter 继承 ACharacter。二者都实现 IUTGameActorInterface,因此可以被 UT 的通用逻辑识别为 UT 游戏 Actor。项目可以根据 角色是否需要 CharacterMovement、骨骼和动画系统,选择 Pawn 或 Character 作为实际玩家类。

AUTGameCharacter 默认承载 UUTAbilityComponent 和 UUTInventoryComponent。Ability、属性、Effect、技能槽、背包和装备等运行时数据通常 归属于这些组件,而不是由 Pawn 自己重新维护。Pawn/Character 应主要负责:

  • 移动、碰撞、相机和角色表现;
  • 创建或持有 AbilityComponent、InventoryComponent;
  • 提供组件与角色实体之间的关联;
  • 执行角色专属逻辑。

角色蓝图可以保留角色特有行为;通用的输入、视角和 UI 打开入口宜放在 UTPlayerController,以便多个角色复用。

AUTPlayerController 是玩家侧输入和请求的主要入口。当前框架提供:

  • GetUTHUD():获取当前控制器拥有的 AUTHUD,配置为其它 HUD 类时返回空;
  • GetUTPawn():获取当前 Pawn,但只有 Pawn 实现 IUTGameActorInterface 时才返回;
  • GetUTPlayerState():获取 AUTPlayerState,如果实际配置的是其它 PlayerState 类则返回空;
  • 玩家身份获取,用于在线存档定位;
  • Save/Load 请求及其服务器 RPC;
  • 装备槽快照请求。

控制器中的 Save/Load 入口是请求入口,不代表客户端可以直接修改存档。在线模式下由 服务器根据玩家身份选择实际存档槽位并执行恢复。控制器也适合承载通用的 Enhanced Input 初始化、角色移动、视角控制和打开背包/角色面板等玩家级输入。

需要注意,GetUTPawn() 的返回类型仍是 APawn*。UT Pawn 与 UT Character 没有共同的 具体 C++ 派生类可供该函数安全返回,因此通过接口判断后返回 Pawn 是兼容两种角色形态的 做法;需要访问具体 Character 功能时再由调用方 Cast。

AUTGameMode 继承 AGameMode,只在服务器存在,负责决定本局使用的默认游戏类和规则。 UT 默认构造函数已经将以下类设为默认值:

PlayerControllerClass = AUTPlayerController
HUDClass = AUTHUD
GameStateClass = AUTGameState
PlayerStateClass = AUTPlayerState

它没有强制指定某一个项目 Pawn。项目应在 GameMode 子类、蓝图 GameMode 或 World Settings 中配置 DefaultPawnClass,这样不同示例或游戏模式可以使用不同角色。

AUTGameState 继承 AGameState,用于保存需要代表整局游戏、并向客户端复制的状态,例如 比赛阶段、倒计时、胜负状态和全局事件结果。它不应替代 AbilityComponent、InventoryComponent 或 PlayerState 保存玩家私有运行时数据。

AUTPlayerState 继承 APlayerState,用于保存与玩家身份或玩家在本局中的状态有关、且需要 被服务器和其它客户端了解的数据,例如玩家名、队伍、得分、在线身份关联信息等。

角色上的 Ability/Inventory 运行时容器仍然属于 Pawn/Character。只有确实需要跨 Pawn 重生、或需要向其它客户端公开的玩家状态,才应放进 PlayerState。这样可以避免角色重建时 误丢失玩家状态,也避免把大量私有组件数据无条件复制给所有客户端。

AUTHUD 继承 AHUD,是本地玩家 UI 的管理入口。它可以持有背包、角色面板、物品 Tooltip 等 Widget 的引用,并负责创建、显示、隐藏和切换这些面板。HUD 不是游戏规则或 存档的权威来源。

HUD 提供以下通用查询函数:

  • GetUTPlayerController():获取拥有此 HUD 的 UT 控制器;
  • GetUTPlayerPawn():检查 Pawn 是否实现 IUTGameActorInterface 后返回 Pawn;
  • GetPlayersAbilityComponent():获取并缓存 Pawn 上的 AbilityComponent;
  • GetPlayersInventoryComponent():获取并缓存 Pawn 上的 InventoryComponent。

HUD 在 BeginPlay 时刷新组件缓存;当缓存无效时,上述组件查询函数会再次查找。这适用于 Pawn 尚未及时生成、被替换或重生的情况。UI 初始化时应先通过 HUD 获取组件,再将组件引用 传入面板,而不是让每个 Widget 自己猜测玩家对象。

UTGameMode (Server)
-> 决定 PlayerController / HUD / GameState / PlayerState / Pawn
PlayerController
-> Possess -> Pawn or Character
-> owns -> HUD
-> accesses -> PlayerState
Pawn or Character
-> owns -> AbilityComponent
-> owns -> InventoryComponent
HUD
-> queries -> PlayerController -> Pawn
-> reads -> AbilityComponent / InventoryComponent
-> manages -> UI Widgets

典型调用链是:玩家输入先到 PlayerController,控制器获取 HUD 或 Pawn 上的组件;UI 打开 背包或角色面板时,由 HUD 创建或显示 Widget,并把 InventoryComponent 或 AbilityComponent 传入;技能施放和属性修改仍由 AbilityComponent、Ability、Task 和 Effect 系统完成。 服务器规则和权威状态则由 GameMode、GameState、PlayerState 负责。

建议为示例项目创建自己的 GameMode 蓝图或 C++ 子类,并在其中配置:

  1. Default Pawn Class:示例项目使用的 Pawn/Character;
  2. Player Controller Class:通常选择项目的 AUTPlayerController 子类;
  3. HUD Class:选择项目的 AUTHUD 子类,用于实现具体背包、角色面板和 Tooltip;
  4. Game State Class 与 Player State Class:选择项目需要扩展的 UT 子类;
  5. AbilityComponent、InventoryComponent 及其默认数据:在实际 Pawn/Character 上配置。

全局默认 GameMode 可以在项目设置的 Maps & Modes 中配置,也可以在关卡 World Settings 中 覆盖。World Settings 的配置优先用于该关卡,因此测试时应确认实际打开的关卡没有覆盖预期 的 GameMode。

示例项目中的蓝图角色可以只保留角色专属逻辑。通用的角色移动、视角和打开背包/角色面板 输入,可迁移到 PlayerController;Enhanced Input 的 Mapping Context 也应由负责玩家输入 的类初始化。技能输入则仍通过 AbilityComponent 的技能槽或 Ability 输入体系进入,最终 由 Ability 的服务器权威流程判定。

UT 的 UI 应被视为运行时数据的观察者和操作入口,而不是属性、背包或装备的实际拥有者。 Widget 不应复制一份可写的 Health、Level 或道具数量作为事实源;这些数据应由 UUTAbilityComponent、UUTInventoryComponent 及其运行时容器维护。UI 只在初始化时读取 当前值,并在数据变化时刷新显示。

推荐的职责分层是:

UTHUD
-> 管理本地玩家 UI 的创建、显示、隐藏和面板切换
-> 获取 PlayerController、Pawn 及 UT 组件
Panel Widget
-> 持有被观察组件的引用
-> 创建和刷新下级 Item Widget
Item Widget
-> 展示一条属性、一个道具或一个装备槽
-> 不负责保存游戏事实源

UTHUD 适合承担玩家级 UI 的总控职责。面板第一次创建时,由 HUD 或上级面板把 AbilityComponent、InventoryComponent 等引用传入;面板随后使用这些引用读取和监听数据。 这样可以避免每个 Widget 自己通过全局查找 Pawn,也避免 Widget 与某个具体角色蓝图强耦合。

属性面板的初始化通常分为两步:先取得注册的属性 ID,再为每个属性创建对应的显示项。 属性 ID 可以来自 AbilityComponent 已注册的属性容器,例如注册的 ParameterAttribute 或 其它属性集合。每个显示项初始化时保存属性 ID,用它作为后续查询和刷新时的键,而不是只 保存初始化时的数值。

读取某个属性的当前显示值时,应通过 AbilityComponent 的属性查询接口,根据 AttributeID 调用 GetAttributeValue(AttributeID),再读取返回运行时属性项的 FinalValue。FinalValue 是当前对外生效的属性值;BasicValue、MiddleValue 和 Aggregator 规则属于属性系统内部状态,不应由 UI 自己推导。

典型流程如下:

面板初始化
-> 获取 AbilityComponent
-> 获取已注册的 AttributeID
-> 遍历 AttributeID
-> GetAttributeValue(AttributeID)
-> 读取 FinalValue
-> 创建属性 Item 并显示

属性 ID 应保存在属性 Item 或刷新函数的参数中。收到变化通知后,使用通知携带的 AttributeID 找到对应 Item,再重新调用 GetAttributeValue 获取最新 FinalValue;不要 只使用委托传入的数值更新本地缓存,因为重新查询可以保证 UI 与组件的最终状态一致。

背包面板的事实源是 UUTInventoryComponent。面板初始化或背包打开时,应从组件读取 StaticProp 和 InstanceProp 的当前容器,并据此重建或刷新 TileView/GridView 中的 Item Widget。

StaticProp Item 通常由 StaticPropID + Amount 表示。刷新时使用 ID 获取对应的 UUTStaticGamePropBase 或 UUTPrimaryDataAsset,再将主资产名称、描述、图标等展示数据传入 Item Widget。数量来自 InventoryComponent 的运行时条目,不应由主资产推断。

InstanceProp Item 通常由实例 GUID、实例 ID 和动态数据表示。刷新时先根据实例条目获取其 主资产,再将实例需要展示的主资产信息和实例状态传入 Item Widget。实例的动态数据应继续 由 InventoryComponent 或实例系统管理,Widget 只展示结果。

刷新函数应先清理对应的 TileView,再遍历当前容器创建 Item Widget。背包打开、切换 Static/ Instance 标签或收到背包数据变化通知时,都可以调用对应的刷新函数。刷新操作必须以组件 当前容器为准,不能在原有 UI 列表上盲目追加,否则会产生重复显示。

装备面板显示的是 InventoryComponent 中的常驻装备槽和槽内实例。槽位本身可以先按全局 EquipmentSlotName 创建,之后根据每个槽位的 EquippedInstanceGuid 查找装备实例和其主资产。

每个装备槽 Widget 应至少知道自己的 SlotName,并在刷新时:

  1. 查询该 SlotName 当前装备的实例;
  2. 没有实例时清空图标、名称和描述;
  3. 有实例时获取对应的 UUTPrimaryDataAsset;
  4. 将主资产和装备状态传给槽位内的 Item Widget。

装备面板通常由角色面板统一刷新。装备变化后,应重新读取槽位状态,而不是仅在 UI 中修改 图标。这样可以保证装备、装备效果和界面显示仍然以 InventoryComponent 的运行时事实为准。

Tooltip 是展示层控件,可以由 HUD 创建一个实例并重复使用。Item Widget 在鼠标进入时, 将当前保存的主资产和显示位置传给 HUD 的显示函数;HUD 负责初始化 Tooltip、设置位置并 显示。鼠标离开时,HUD 将 Tooltip 隐藏而不是销毁。

Tooltip 的名称、描述和图标应从传入的 UUTPrimaryDataAsset 读取。它不应只接受 StaticProp 类型,因为 StaticProp、InstanceProp 和 Equipment 都可以通过共同的主资产 接口提供名称和描述。Tooltip 也不应直接修改 InventoryComponent。

属性 UI 不应依赖 Tick 轮询。初始化完成后,应把刷新函数绑定到 UUTAbilityComponent::OnAttributeValueChange 委托。当某个属性的 FinalValue 发生变化时, AbilityComponent 广播属性 ID 和新值,UI 事件据此刷新对应的属性 Item。

推荐流程是:

创建面板
-> 获取 AbilityComponent
-> 初始化当前属性显示
-> Bind Event to OnAttributeValueChange
属性系统发生变化
-> AbilityComponent 更新 FinalValue
-> 广播 OnAttributeValueChange(AttributeID, NewValue, ...)
-> UI 根据 AttributeID 定位 Item
-> 再次 GetAttributeValue(AttributeID)
-> 读取 FinalValue 并更新显示

等级和经验同理:面板初始化时读取当前 LevelingAttribute,随后绑定等级变化和经验变化 委托,变化事件中只刷新受影响的条目或进度条。

背包和装备应采用同样的原则:由 InventoryComponent 提供变化通知或由明确的刷新时机触发 面板刷新。打开面板时做一次完整刷新,单项数量变化时可以做局部刷新;如果组件发生清空、 读档重建或批量恢复,则应在恢复完成通知之后执行一次完整刷新。

绑定委托时要注意生命周期:Widget 被销毁、面板被替换或 Pawn 重生后,应解除旧组件上的 绑定并重新绑定新组件。UI 读取数据的时机还应晚于 Pawn 和组件初始化;读档场景则应等待 OnUTActorRestoreCompleted 等恢复完成通知,避免在派生 Effect、装备和属性尚未恢复时显示 中间状态。

UTHUD 中的 GetPlayersAbilityComponent() 和 GetPlayersInventoryComponent() 可以作为 统一入口。它们会优先返回缓存的组件,缓存失效时再从当前 UT Pawn 查找,因此适合处理 Pawn 重生或替换。具体面板仍应保存组件引用,并在引用失效时重新初始化,而不是永久依赖旧 Pawn。

多人游戏中 UI 始终是客户端观察者。它显示的是客户端收到的 AbilityComponent、InventoryComponent 和 Equipment 的复制镜像;按钮点击只发起请求,不能把 Widget 中的本地值写回作为权威状态。

本章只说明跨系统共同遵守的网络模型、权限边界和验证方法。Ability、Effect、Inventory、 Equipment、Save/Load 以及 UI 的具体网络规则,分别放在各自章节中说明;阅读本章时应将 这些章节的系统规则组合起来理解,而不是把本章当作各系统 API 的替代说明。

UT 遵循 Unreal 的服务器权威模型。服务器拥有游戏规则和持久运行时状态的最终决定权, 客户端负责输入、界面和必要的预测表现。客户端可以发起请求,但不能因为本地执行成功就 视为操作已经被服务器接受。

客户端输入 / UI 操作
-> 请求或 RPC
-> 服务器验证规则和资源
-> 服务器修改 Ability / Effect / Inventory
-> 复制权威状态
-> 客户端刷新 UI 和表现

网络设计的核心是区分三类数据:

  • 权威事实源:由服务器创建和修改,例如属性最终值、背包条目、装备槽和持久 Effect;
  • 可复制运行时镜像:客户端用于显示和预测,例如 FastArray 中的技能槽和容器条目;
  • 本地表现状态:UI、动画、特效、鼠标悬停和预测中的临时状态,不作为服务器事实源。

客户端不应直接修改 AbilityComponent、InventoryComponent 或 Active Effect Item。客户端的 输入通常只携带最小必要信息,例如技能槽名称、目标引用或操作请求;服务器收到请求后, 重新查询自己的运行时数据并判断操作是否合法。

服务器至少应检查:

  • 请求者是否拥有对应的 Pawn、AbilityComponent 或 InventoryComponent;
  • Ability 是否已注册、是否允许由当前输入方式施放;
  • 技能槽是否存在,槽内 Ability 是否仍然有效;
  • 目标是否有效,距离、权限、Tag 和其它规则是否满足;
  • 资源、冷却、消耗品数量和装备状态是否满足;
  • 请求参数是否越界或试图引用不属于请求者的对象。

AUTPlayerController 中的 Save/Load 请求就是这一原则的例子:客户端只能请求保存或 加载,真正的存档操作由服务器执行。在线模式下服务器还会根据复制的 FPlayerID 选择 角色存档,不能信任客户端传入任意存档身份。

主动技能的典型网络链路是:

客户端输入
-> 选择 AbilitySlot
-> 本地解析技能槽镜像并进行可选预测
-> 向服务器发送 SlotName 或施放请求
-> 服务器解析自己的槽位和 AbilityID
-> Commit / 规则验证
-> 创建 Ability、运行 AbilityBehavior 和 Task
-> 在服务器应用 Effect 与属性修改
-> 复制结果到客户端

客户端发送 SlotName 的好处是服务器不会接受客户端直接指定的 AbilityID。技能槽的 DefaultAbilityID 和 RuntimeBindings 由运行时规则产生,客户端只保留用于显示和预测的 镜像;服务器根据 EffectBehavior 的结果维护最终绑定,并同步 FinalAbilityID。

响应技能由服务器根据 Intent 和筛选规则触发。客户端可以接收已经发生的结果并更新表现, 但不能伪造命中 Intent 或直接要求目标执行响应 Ability。

Effect 的创建、Apply、周期触发、Calculate、Aggregator 更新以及属性最终值修改,应以 服务器执行结果为准。客户端可以预测本地表现或临时结果,但服务器拒绝请求、规则不一致或 同步新值后,必须接受服务器状态并修正本地镜像。

属性 UI 应监听客户端 AbilityComponent 的 OnAttributeValueChange 委托。这个委托用于 刷新显示,不是授权接口;客户端 UI 不应通过修改 Widget 数值来反向修改属性。

Inventory 的权威事实源位于服务器。StaticProp 的 StaticPropID + Amount、InstanceProp 的 实例 GUID 和动态数据、以及 EquipmentSlot 的装备实例 GUID,都应由服务器添加、删除和修改。

客户端可以根据复制到本地的容器刷新背包 UI,但以下操作必须变成服务器请求:

  • 拾取、使用、消耗或丢弃 StaticProp;
  • 创建、转移、删除或重建 InstanceProp;
  • 装备、卸下或交换装备;
  • 修改装备槽和由装备产生的 EquipEffect。

服务器完成操作后,复制容器事实源和必要的运行时状态。客户端再根据同步后的容器刷新 TileView、装备槽和 Tooltip。CarryEffect、EquipEffect 等派生 Effect 应由服务器根据新的 事实源重建,客户端不应自行生成一份独立的权威效果。

网络恢复或重连时,服务器先按角色身份恢复属性、背包、实例和装备,再向客户端复制最终 状态。UI 应等待恢复完成通知后刷新,避免把尚未完成的中间状态当成最终数据。

预测的目标是降低输入延迟,不是改变权威模型。适合预测的是本地即时表现,例如播放动画、 显示按钮反馈、生成临时特效或提前显示技能开始状态。涉及资源扣除、属性最终值、背包事实 源和装备效果的结果,必须由服务器确认。

当本地预测与服务器结果不一致时,处理顺序应是:

接受服务器结果
-> 撤销或覆盖预测状态
-> 应用服务器复制的属性 / Effect / Inventory 状态
-> 重新刷新相关 UI 和表现

UT Effect 的可回滚 Behavior 可以在技能或 Effect 被取消、拒绝时恢复其可回滚修改;不可 回滚的行为,例如 Calculate 直接修改属性的计算结果,应只在通过外层成功判定后执行,并由 服务器结果作为最终依据。客户端不应把本地预测的不可回滚结果当作永久事实。

技能槽也存在同步延迟窗口:客户端可能暂时持有旧的 FinalAbilityID。因此 CastAbilitySlot 只应把槽位名称作为请求依据,服务器重新解析槽位并校验 Ability;服务器 返回或复制的新槽位状态用于纠正客户端镜像。不存在的槽位、空槽位和未注册 Ability 都应 被服务器拒绝或视为不可施放。

配置网络示例时,应确认 GameMode 使用项目的 UT PlayerController、Pawn、GameState、 PlayerState 和 HUD 类。GameMode 只在服务器存在,GameState 和 PlayerState 才适合承载需要 复制给客户端的状态;HUD 只负责本地显示。

建议按以下流程验证:

  1. 使用至少一个服务器和一个客户端运行 PIE;
  2. 客户端输入施放技能,确认服务器重新验证并执行 Ability;
  3. 检查 Effect 和属性变化是否由服务器产生并复制到客户端;
  4. 在客户端使用、拾取或装备道具,确认服务器才修改 Inventory;
  5. 延迟或故意制造旧技能槽镜像时,确认服务器按 SlotName 重新解析;
  6. 检查客户端 UI 是否通过委托和复制数据刷新,而不是本地写入事实源;
  7. 断开重连后确认服务器按玩家身份恢复正确的角色数据。

Standalone 模式可以使用单个本地世界测试业务链路,但不能证明复制、RPC、权限校验和 预测修正正确。网络功能必须在 Listen Server 或 Dedicated Server 加客户端的模式下验证。

本章描述一个游戏项目接入 UTFramework 的完整顺序。插件启用、模块依赖、目录结构和基础 工程设置见第 4 章,本章不重复列出同一组配置,而是说明这些配置如何组成一个可运行的 游戏流程。

推荐先确认插件和模块已经正确接入,再开始创建项目自己的蓝图子类。接入过程中应优先 使用 UT 提供的基类和组件,避免在项目层复制 Ability、Effect、Inventory 或 Save/Load 的 核心运行时逻辑。

创建项目自己的 GameMode 子类或蓝图,并配置:

GameMode
-> Default Pawn Class
-> Player Controller Class
-> HUD Class
-> Game State Class
-> Player State Class

通常选择 AUTPlayerController、AUTHUD、AUTGameState 和 AUTPlayerState,再按项目 需要创建蓝图子类。Default Pawn Class 选择实际使用的 AUTGamePawn 或 AUTGameCharacter 子类。

在项目设置的 Maps & Modes 中设置全局默认 GameMode;需要特殊规则的关卡可在 World Settings 中覆盖。测试时必须确认关卡没有意外使用其它 GameMode,否则 Pawn、HUD 或 PlayerController 可能不是预期的类。

18.3 配置 Pawn、AbilityComponent 与 InventoryComponent

Section titled “18.3 配置 Pawn、AbilityComponent 与 InventoryComponent”

在项目 Pawn/Character 上添加并配置 UUTAbilityComponent 和 UUTInventoryComponent。AbilityComponent 至少应完成:

  1. 注册所需的 AttributeSet;
  2. 配置默认 LevelingAttribute 和 ParameterAttribute;
  3. 配置 AbilitySet;
  4. 配置默认 AbilitySlot;
  5. 按需要配置 Reactive Intent 监听。

InventoryComponent 至少应完成:

  1. 配置 Default StaticProp Set;
  2. 配置 Default InstanceProp Set;
  3. 配置 EquipmentSlotName;
  4. 配置需要的装备或实例类型;
  5. 确认其关联 AbilityComponent 可以被正确找到。

组件的默认数据只负责建立新游戏初始状态。Save/Load 场景下,最终状态仍由存档事实源 恢复,具体顺序参见第 14 章。

按照第 4.1、4.3 和 4.4 节的规则创建 UT 主资产,并确保:

  • 资产继承正确的 UT 主资产类;
  • 使用正确的 UTAssetID 类型;
  • 主资产已位于 AssetManager 配置的扫描路径;
  • 主资产名称、描述、标签、图标等展示数据已经填写;
  • 需要蓝图逻辑的资产使用可编辑逻辑的蓝图类方案;
  • Default Set 只引用有效的主资产 ID。

为角色配置初始内容时,先建立 Attribute、Ability、StaticProp、InstanceProp 和装备槽的 默认集合,再将这些集合配置到对应组件。不要把临时运行时对象、Effect Handle 或世界 Actor 引用写入默认集合。

18.5 连接输入、技能、道具和装备

Section titled “18.5 连接输入、技能、道具和装备”

玩家输入的推荐链路是:

Enhanced Input
-> PlayerController 或角色输入入口
-> 请求打开 UI / 施放技能 / 使用道具
-> AbilityComponent 或 InventoryComponent
-> Ability、Task、Effect 或 Prop 运行时逻辑

通用的移动、视角和打开背包/角色面板输入适合放在 PlayerController,使多个 Pawn 可以 复用。角色专属输入和技能业务仍可以保留在角色或 Ability 内。Enhanced Input Mapping Context 应在负责玩家输入初始化的类中添加,并确认只添加一次。

技能使用 AbilitySlot 时,输入只选择 SlotName;AbilityComponent 根据当前槽位绑定解析 Ability。主动技能继续经过 Ability 的 Commit 和服务器权威判定,响应技能则由 Intent 和 响应规则触发,不能通过普通主动技能输入直接施放。

道具使用由 InventoryComponent 检查数量和使用条件,再调用 StaticPropUseLogic 或实例 逻辑。装备操作由 InventoryComponent 检查实例和 EquipmentSlot,成功后由装备 Actor 应用 EquipmentEffect。属性改变应通过 Effect、Calculate、TaskExecution 或 Intent/响应技能 等已定义入口完成。

创建项目 HUD 子类和所需 Widget。HUD 负责创建和管理本地 UI,并通过 GetPlayersAbilityComponent()、GetPlayersInventoryComponent() 获取玩家组件。

面板初始化时传入组件引用:

UTHUD
-> 创建主 UI
-> 创建角色 / 背包面板
-> Set AbilityComponent / InventoryComponent
-> 面板读取当前数据并创建 Item Widget
-> 绑定属性或容器变化通知

属性面板通过 GetAttributeValue(AttributeID) 读取 FinalValue,并绑定 OnAttributeValueChange;背包和装备面板根据 InventoryComponent 的当前容器刷新。UI 只 展示和发起请求,不保存属性、数量、实例 GUID 或装备槽的权威副本。

根据项目模式选择 SaveManager:

  • 单机使用 UUTStandaloneSaveManager,保存整个 World 的持久状态;
  • 网游使用 UUTOnlineSaveManager,由服务器按 FPlayerID 保存角色数据。

将保存和加载请求连接到 PlayerController 或项目 UI。客户端只能发起请求,在线模式的实际 保存、加载和身份选择由服务器完成。保存前确认组件的事实源已经稳定;加载后等待恢复完成 通知,再刷新属性、背包、装备和其它 UI。

建议按以下顺序验收一个新接入项目:

  1. 启动项目后确认实际 GameMode、Pawn、PlayerController 和 HUD 类正确;
  2. 确认 AbilityComponent 和 InventoryComponent 已创建并完成默认数据初始化;
  3. 确认属性可以查询,属性变化委托能够驱动 UI 刷新;
  4. 确认默认 StaticProp、InstanceProp 和 EquipmentSlot 正确建立;
  5. 确认背包面板能够刷新物品,装备面板能够显示和切换装备;
  6. 确认主动技能经过 Commit,响应技能能够通过 Intent 触发;
  7. 确认 Effect、Task 和属性修改结果符合预期;
  8. 在多人 PIE 中确认请求由服务器验证,客户端不能直接修改权威状态;
  9. 保存后加载,确认属性、背包、实例、装备和持久 Effect 正确恢复;
  10. 重复打开 UI、重复加载和 Pawn 重生后,确认没有重复 Widget、道具、绑定或 Effect。

出现问题时,应从上游配置逐层检查:先检查 GameMode 和类引用,再检查 Pawn 组件,再检查 主资产 ID 和默认集合,最后检查 Ability、Effect、Inventory、UI 和 Save/Load 的调用链。 不要先在 UI 中修正一个由组件或存档恢复错误导致的显示问题。