Gameplay 能力系统
Gameplay Ability System
Gameplay 能力系统 (Gameplay Ability System, GAS) 是 UE 用来构建「Actor 可拥有并反复触发的能力」的框架:能力系统组件 (Ability System Component, ASC) 是持有者,Gameplay 能力 (Gameplay Ability) 编排执行过程,属性集 (Attribute Set) 存数值,Gameplay 效果 (Gameplay Effect, GE) 改数值和标签,Gameplay 标签 (Gameplay Tags) 做状态与互斥,Gameplay Cue 负责表现。它内建复制与客户端预测,代价是概念多、上手重,适合 RPG、动作、MOBA 这类技能密集型游戏。
为什么需要一个框架
官方把「能力」难做的原因归结为三点:要追踪所有者(计算时用谁的属性、得分记给谁)、追踪状态(已激活 / 进行中 / 已结束,Lyra 还追踪“已授予”)、协调执行(播蒙太奇、临时接管移动、特效、碰撞检测、改属性、扣资源、阻止其他能力、冷却、被打断、取消别的能力、响应中途输入、刷新 UI……)。单独写一次攻击很简单,但随着消耗、Buff、连招叠加,复杂度会爆炸。GAS 的做法是把每个能力建模为自己负责自己执行的独立实体。(Gameplay Ability System Overview)
注意:GAS 里的“所有者 (Owner)”和网络复制里的所有权 (Ownership) 不是一回事。(Gameplay Ability System Overview)
组件关系
下图概括各部件的从属与作用方向:ASC 挂在 Actor 上,持有能力、属性集、激活中的 GE、标签和 Cue;能力在执行中施加 GE,GE 改属性与标签并按标签触发 Cue。
flowchart LR Actor --> ASC["能力系统组件 ASC"] ASC --> GA["已授予的 Gameplay Ability"] ASC --> AS["Attribute Set 属性集"] ASC --> AGE["激活中的 GE 容器"] ASC --> Tags["拥有的 Gameplay Tags"] GA -- "施加" --> GE[Gameplay Effect / Spec] GE -- Modifier/Execution --> AS GE -- "授予/移除" --> Tags GE -- "匹配 GameplayCue.* 标签" --> Cue["Gameplay Cue 表现"] GA -- "启动" --> Task["Ability Task 异步任务"]
图中箭头表示“持有”或“作用于”。没有 ASC 的 Actor 无法使用能力;ASC 也是 Actor 在能力系统里的代表,提供属性、激活中效果、标签、Gameplay 事件的追踪接口。(Gameplay Ability System Overview)
| 部件 | 职责 | 关键约束 |
|---|---|---|
ASC (UAbilitySystemComponent) | 授予/移除/激活能力;多人下负责复制、向服务器转达操作、校验授权 | 远程激活要求 ASC 的父 Actor 由本地控制的玩家拥有 |
Gameplay Ability (UGameplayAbility) | 编排一次能力的全部事件 | 蓝图或 C++;主要工作交给 Ability Task,而非 Tick |
Attribute Set (UAttributeSet) | 存放 FGameplayAttributeData 属性并监管修改 | 只能用 C++ 创建;一个 ASC 可有多个属性集但类不能重复 |
| Gameplay Effect | 改属性与标签 | 是资产,运行时不可变;运行时操作的是 GE Spec |
| Gameplay Tags | 层级标签,状态/类别/事件 | 必须注册到标签字典 |
| Gameplay Cue | 音效/视觉反馈 | 非可靠复制,只能做表现 |
(Gameplay Ability System Overview、Gameplay Ability、Gameplay Effects、Gameplay Attributes and Attribute Sets)
Gameplay Ability:授予、激活与结束
- 授予:
GiveAbility以FGameplayAbilitySpec授予并返回FGameplayAbilitySpecHandle;只有服务器能授予或撤销能力。撤销用ClearAbility、SetRemoveAbilityOnEnd(执行完再移除)、ClearAllAbilities。(Gameplay Ability) - 生命周期:
CanActivateAbility(只检查,可用于 UI 置灰)→CallActivateAbility(不检查直接执行);常用的TryActivateAbility= 检查 + 执行。重写ActivateAbility写主体逻辑;CommitAbility实际扣除消耗并上冷却;CancelAbility可被CanBeCanceled拒绝。(Gameplay Ability) - 必须调用
EndAbility:否则系统认为能力仍在运行,后续使用和被它阻挡的能力都会卡住。官方例子:“喝血瓶”没结束,角色就再也不能喝药、冲刺、爬梯子。这是最常见的坑。(Gameplay Ability) - 四种激活途径:Spec Handle 显式激活;Gameplay 事件(匹配 Ability Trigger,最适合把输入与决策抽象开);带匹配标签的 GE(如“睡眠”Debuff 触发倒地能力——受击反应也可以是能力);输入码 (Input Codes)。由事件触发时走
ActivateAbilityFromEvent,携带FGameplayEventData,需单独处理这条路径。(Gameplay Ability System Overview、Gameplay Ability)
能力的互斥与条件靠标签表达,而不是手写 if:
| 标签变量 | 作用 |
|---|---|
| Cancel Abilities With Tag | 本能力执行时取消带这些标签的正在执行的能力 |
| Block Abilities With Tag | 本能力执行时阻止带这些标签的能力执行 |
| Activation Owned Tags | 执行期间给所有者加上这些标签 |
| Activation Required / Blocked Tags | 激活者必须全有 / 不能有任一 |
| Target Required / Blocked Tags | 目标必须全有 / 不能有任一 |
实例化策略 (Instancing Policy):性能与功能的取舍
| 策略 | 行为 | 适用 | 限制 |
|---|---|---|---|
| Instanced per Execution | 每次执行新建对象 | 低频能力,如 MOBA 大招(60–90 秒冷却、约 10 名角色) | 开销大;数百小兵的普攻会频繁创建(并可能复制)对象 |
| Instanced per Actor | 每个 Actor 首次使用时建一个,之后复用 | 需要复制变量/RPC 的能力,大规模战斗也表现好 | 每次执行之间要手动清理成员变量 |
| Non-Instanced | 不建对象,直接用 CDO | 高频、多人使用的能力,如 RTS/MOBA 普攻 | 必须纯 C++;不能改成员变量、不能绑委托、不能复制变量或处理 RPC |
另外,OnGiveAbility、CanActivateAbility 等回调可能在未激活时被调用:Per Actor 在实例上调用,另两种在 CDO 上调用。CDO 的概念见 UObject 与反射系统。(Gameplay Ability)
Ability Task:为什么不用 Tick
普通蓝图函数节点立即完成,而 Ability Task 会追踪自己处于未激活/进行中/完成,可在执行中触发事件,并感知父能力被取消以便清理。能力在激活时启动 Task,通过委托(C++)或输出执行引脚(蓝图)处理结果。项目通常会扩展自定义 Task 实现自己的玩法。这与 委托与 Tick 机制 中“能用事件就别用 Tick”的思路一致。(Gameplay Ability、Gameplay Ability System Overview)
Attribute 与 Attribute Set:基础值与当前值
每个属性同时保存基础值 (Base) 与当前值 (Current)。官方例子:跳跃高度基础值 100,“疲劳”效果为 70% → 当前值 70;升级后基础值 110 → 当前值 。即时 (Instant) GE 修改基础值;有持续时间的 GE 修改当前值,效果结束即撤销。(Gameplay Attributes and Attribute Sets、Gameplay Ability System Overview)
实践要点(Gameplay Attributes and Attribute Sets、Gameplay Effects):
- 用
GAMEPLAYATTRIBUTE_PROPERTY_GETTER / VALUE_GETTER / VALUE_SETTER / VALUE_INITTER生成访问函数,是推荐做法;Actor 上以const指针持有属性集,防止代码绕过系统直接改。 - 可用行类型为
AttributeMetaData的数据表初始化,但表里的 MinValue/MaxValue 不生效——属性没有内建钳制,需自己做。 - 钳制写在属性集里:
PreAttributeChange/PreAttributeBaseChange只负责约束取值、不应触发游戏反应;PostGameplayEffectExecute负责最终钳制与反应(如血量归零死亡);自定义 Setter 应调用SetNumericAttributeBase改基础值,保证已有修饰器继续生效。 - 多人下用
ReplicatedUsing+GAMEPLAYATTRIBUTE_REPNOTIFY+DOREPLIFETIME_CONDITION_NOTIFY(..., COND_None, REPNOTIFY_Always)复制,机制见 网络复制与 RPC。
Gameplay Effect:资产 vs Spec
GE 是资产,运行时不可变;运行时实际传递的是 GE Spec(包在资产外的实例数据),蓝图库也以 Spec 为主。(Gameplay Effects)
| 持续类型 | 是否进入激活容器 | 术语 |
|---|---|---|
| Instant | 否 | “Executed” |
| Has Duration / Infinite | 是(ASC 中的 Active Gameplay Effects Container) | “Applied/Added” |
| 周期性 (Periodic) | 是 | 每周期既 Added 又 Executed |
GE 的能力来自:Modifier(对属性做数学运算,例如护甲 +5% 基础值)、Execution(UGameplayEffectExecutionCalculation,处理 Modifier 写不清的复杂公式,可同时读施放者与目标)、Stacking(叠层上限、刷新/追加时长、独立计时)、以及 GE 组件 (GEComponent)——如授予目标标签、标签条件、免疫、概率施加、移除其他 GE 等。GEComponent 只能用 C++ 写,且所有已施加实例共享同一个组件对象。(Gameplay Effects)
Gameplay Tags
标签是用 . 分层的字符串,如 Character.Enemy.Zombie、Movement.Mode.Swimming,用法类似布尔/标志位,但可按层级匹配。必须先注册:项目设置(存为 Config/DefaultGameplayTags.ini 等,文本编辑后需重启编辑器)、数据表(行类型 GameplayTagTableRow)、或 C++ 宏 UE_DECLARE_GAMEPLAY_TAG_EXTERN / UE_DEFINE_GAMEPLAY_TAG(_COMMENT/_STATIC)(需在 Build.cs 加 GameplayTags 模块,见 模块、插件与构建系统)。容器 FGameplayTagContainer 提供 HasTag/HasAny/HasAll;注意空容器作为参数时只有 All 类函数返回 true。复杂条件用 FGameplayTagQuery。(Gameplay Tags)
Gameplay Cue:只做表现
Cue 是负责音效和视觉效果的 Actor/UObject,与标签关联(必须以 GameplayCue. 开头),匹配标签的 GE 会自动生成并运行它,从而可以建立一个“通用特效库”而不必在代码里手动触发;也可脱离 GE 直接触发(Lyra 的开火反馈)。Cue 不使用可靠复制,部分客户端可能收不到,因此只能用于表现;与玩法相关、必须送达的反馈应交给 Ability Task(如 Play Montage Task)。(Gameplay Ability System Overview、Gameplay Effects)
Cue 的处理类与事件(UE 5.8 API)
5.8 没有找到 Gameplay Cue 的概念文档页,下列内容来自 C++ API 参考。(Gameplay Cue API (UGameplayCueNotify_Static, UGameplayCueManager, UGameplayAbility))
UGameplayCueNotify_Static:不实例化的 UObject 处理器,适合一次性「爆发」特效;派生类有UGameplayCueNotify_Burst、UGameplayCueNotify_HitImpact。持续型、需要状态的效果则由UGameplayCueManager按需生成 GameplayCueNotify Actor。- 事件:
OnActive(带持续时间的 Cue 首次激活,只有客户端亲眼看到激活才调用)、WhileActive(首次看到处于激活状态,包括中途加入)、OnExecute(瞬时效果或周期 tick)、OnRemove(移除)。中途加入的客户端只会收到WhileActive,所以持续表现应放在WhileActive而不是OnActive。 - 标签层级与覆盖:
IsOverride决定是否覆盖父标签的 Cue,例如Damage.Physical.Slash处理后不再调用Damage.Physical,参见 Gameplay 标签深入。 - 复制选择:
UGameplayCueManager是负责分发 Cue 的单例;官方注释说明动画总用非复制的 Cue(动画本就在模拟代理上执行),能力总用复制的 Cue(能力不在模拟代理上执行)。 - 不经 GE 直接触发:能力里可用
K2_ExecuteGameplayCue(一次性)、K2_AddGameplayCue/K2_RemoveGameplayCue(持续,可随能力结束自动移除)。
网络:复制什么、预测什么
- ASC 不会把能力和 GE 复制给所有客户端,只复制它们影响到的属性和标签——省带宽也防作弊。(Gameplay Ability System Overview)
- Net Execution Policy 四选一:Local Predicted(本地立即执行,服务器拥有最终裁决,手感好)、Local Only、Server Initiated(准确但有延迟)、Server Only。(Gameplay Ability)
- 预测被服务器拒绝时要回滚。多数非即时 GE 支持回滚,但伤害等即时属性/标签变化不支持。正是因为这种回滚能力,官方建议所有标签、Cue、属性变化都通过 GE 施加,而不是直接改。(Gameplay Ability System Overview)
- 与服务器拥有的对象(NPC 等)交互时,要经由本地拥有的 Actor(通常是 Pawn)上的复制能力或其他服务器函数。所有权概念见 网络复制与 RPC 与 Gameplay 框架。(Gameplay Ability System Overview)
取舍与常见坑
| 问题 | 说明 |
|---|---|
忘记 EndAbility | 能力永远“进行中”,阻塞后续能力 |
| 用 Cue 驱动玩法 | Cue 非可靠复制,会不同步 |
| 直接改属性/标签 | 绕过 GE 就失去预测回滚 |
| 以为数据表 Min/Max 会钳制 | 不生效,需在属性集里写 |
| 高频能力用 Per Execution | 大量对象创建与复制开销 |
| 在客户端授予能力 | 只有服务器能授予/撤销 |
适用性上,官方定位是 RPG、动作冒险、MOBA 等能力需要协调机制、特效、动画、音效与数据驱动元素的游戏,也可改造用于更多项目;对只有少量简单交互的游戏,引入 GAS 的学习与搭建成本可能高于收益。(Gameplay Ability System Overview)输入如何触发能力可结合 增强输入;能力中播放蒙太奇见 动画蓝图与动画系统。
出处
- Gameplay Ability System Overview UE 5.8 文档
- Gameplay Ability UE 5.8 文档
- Gameplay Effects UE 5.8 文档
- Gameplay Attributes and Attribute Sets UE 5.8 文档
- Gameplay Tags UE 5.8 文档
- Gameplay Cue API (UGameplayCueNotify_Static, UGameplayCueManager, UGameplayAbility) UE 5.8 API 参考(Cue 概念页未找到)