02-GAS的核心模型
第 2 章:GAS 的核心心智模型
问题导入
上一章已经看到,火球术的问题不是“缺一个更大的技能基类”,而是同一个 Cast() 同时回答了太多不同的问题。团队开始接触 GAS 后,通常会遇到另一个误区:把 Ability、Effect、Tag 等术语记住,却仍然不知道一段新逻辑应该放在哪里。
例如,火球术按下后要扣 30 Mana、进入 2 秒冷却、命中时造成 120 点直接伤害、给目标施加 5 秒 Burning,并且允许在抬手期间被眩晕打断。下面这些问题看起来都属于“技能”:
- 技能什么时候开始、等待什么、何时结束;
- 角色当前有多少 Mana;
- 这次施法到底修改了哪些状态;
- 眩晕和冷却如何阻断激活;
- 动画事件和目标确认如何异步到达;
- 哪些客户端表现可以提前播放;
- 服务器如何确认最终结果。
1. 观察一次火球术
| 维度 | 火球术要回答的问题 | 典型状态或信息 |
|---|---|---|
| 流程 | 从输入到结束经过哪些阶段 | 激活、等待、命中、取消、完成 |
| 数值状态 | 角色和目标当前有哪些可计算数值 | Mana、Health、AttackPower |
| 状态变化 | 这次能力对世界做了什么改变 | 扣 30 Mana、造成 120 伤害、添加 Burning |
| 条件 | 什么情况下允许或阻断能力 | Mana 足够、没有眩晕、冷却结束 |
| 异步等待 | 需要等待哪些未来事件 | 动画标记、目标确认、命中、超时 |
| 表现 | 玩家和旁观者应该看到什么 | 动画、投射物、特效、音效、UI |
| 权威 | 谁能把意图变成最终事实 | 服务器验证并结算 |
1.1 流程
流程描述的是时间顺序和生命周期:
输入 -> 检查条件 -> 提交资源和冷却 -> 等待目标或动画事件 -> 生成投射物 -> 命中并应用变化 -> 等待收招 -> 结束或取消流程需要知道自己是否激活、正在等待、被取消或已经完成,还需要在结束时清理自己创建的运行时对象。
1.2 数值状态
数值状态描述可以参与计算、查询、复制或保存的量:
施法者 Mana = 80目标 Health = 500施法者 AttackPower = 100火球术的 Mana Cost = 30 是配置,施法者当前 Mana = 80 是运行时属性。前者不会因为某个角色受伤而改变,后者会被回复、装备、Buff 和服务器校正影响。
把数值集中到统一的属性上下文后,能力只需要请求“应用一个消耗变化”,不必知道 Mana 存在字段、网络变量还是某个组件内部。这种解耦也让伤害、治疗和资源回复可以共享同一套聚合规则。
1.3 状态变化
状态变化关注一次操作的差值和生命周期:
立即变化: Mana -= 30 Target.Health -= 120
持续变化: Target 获得 Burning Burning 持续 5 秒 每 1 秒触发一次周期结算1.4 条件
条件的规则:
Required: Resource.Mana >= 30
Blocked: State.Dead State.Stunned State.Silenced Cooldown.Fireball条件至少要区分三种时机:
- 激活前检查:当前是否允许开始;
- 流程中检查:等待期间是否被打断;
- 应用时检查:目标、资源和服务器状态是否仍然有效。
1.5 异步等待
火球术可能需要等待动画事件来生成投射物,等待目标确认来确定方向,等待命中事件来应用伤害,还要等待超时或取消:
等待 Animation.Release等待 Target.Confirmed等待 Projectile.Hit等待 Cast.Cancelled 或 Cast.Timeout而等待对象必须知道:
- 谁创建了它;
- 能力取消时如何结束;
- 事件来自本地、服务器还是网络消息;
- 超时后是否撤销预测;
- 结束时是否注销回调。
因此,异步等待需要自己的生命周期语义。执行工具可以是协程、Task、事件订阅或自定义调度器,但工具本身不能代替取消和清理规则。
1.6 表现
玩家看到的是动画、火球、命中特效、灼烧火焰和 UI 冷却条,而不是 EffectInstance 的生命周期。表现层应该消费规则事件:
Fireball.CastStartedFireball.ProjectileSpawnedFireball.HitConfirmedTarget.BurningStartedTarget.BurningEnded1.7 权威
在多人游戏中,客户端拥有输入和体验,服务器拥有最终裁决。客户端可以说:
“我在本地时间 T 按下了火球术,目标方向是 D。”服务器需要独立验证:
“这个角色在服务器状态下确实有资格施法,目标也确实有效。”只有服务器确认后,扣除 30 Mana、启动 2 秒冷却、应用 120 点伤害和添加 Burning 才成为最终事实。客户端可以预测部分结果,但必须能接受确认、拒绝和校正。
通用原则:网络协议传输的是意图、上下文和结果,不是让不可信的一端直接提交最终战斗事实。
2. 如何落到对象
2.1 Ability
在 UE GAS 中:
GameplayAbility表示一个可激活的能力,负责能力流程、激活条件、提交、等待、取消和结束。
在本文的 Unity 方案中:
AbilityRuntime是本文自定义的运行时对象,用来保存某次能力激活的阶段和取消状态;AbilityDefinition保存静态配置。
Ability 应该回答:
- 什么时候开始;
- 要先检查什么;
- 什么时候提交资源和冷却;
- 要等待哪些事件;
- 何时应用一个或多个 Effect;
- 哪些条件会取消它;
- 结束时清理什么。
2.2 Attribute
在 UE GAS 中:Attribute 通常由
AttributeSet承载和访问,表示 Health、Mana、Armor 等可量化状态。
在本文的 Unity 方案中:
AttributeStore是本文自定义的规则层容器,负责基础值、修改器和当前聚合结果;它不是 Unity 的组件或内置服务。
Attribute 负责“当前是多少”,不负责“为什么在等待动画”。它可以参与:
- Mana 是否足够的条件判断;
- 伤害和治疗的最终计算;
- 装备、Buff、Debuff 的修改器聚合;
- 服务器快照和客户端显示。
属性系统可以只提供查询和应用变化的边界,具体的能力流程仍由 Ability 组织。
2.3 Effect
在 UE GAS 中:
GameplayEffect描述属性修改、持续时间、周期、授予标签等效果语义。
在本文的 Unity 方案中:
EffectDefinition和EffectInstance是本文自定义的静态定义与运行时实例,用来表达即时、持续和周期性变化。
火球术至少可以拆成:
Mana Cost Effect -> Mana 减少 30
Cooldown Effect -> 添加 Cooldown.Fireball -> 持续 2 秒
Direct Damage Effect -> 目标受到 120 点直接伤害
Burning Effect -> 添加 Effect.Burning -> 持续 5 秒 -> 每 1 秒触发一次周期结算2.4 Tag
在 UE GAS 中:
GameplayTag是结构化标签,用来表达状态、能力条件、阻断和冷却等可查询事实。
在本文的 Unity 方案中:
TagSet、TagQuery和标签路径是本文自定义的数据结构与查询规则,不等于 Unity 的字符串 API。
火球术可能使用:
State.StunnedState.SilencedState.CastingCooldown.FireballEffect.BurningTag 适合描述“是否处于某种状态”,不适合代替所有数值。State.Stunned 可以阻断能力,Mana 是否大于等于 30 仍然是 Attribute 条件。两者组合后,激活检查才完整。
2.5 Task
在 UE GAS 中:
AbilityTask为能力提供等待事件、延迟、目标确认、动画通知和取消等异步流程。
在本文的 Unity 方案中:
AbilityTask可以是本文自定义的纯 C# 等待对象、事件订阅对象或调度器节点。
Task 的职责是把等待纳入能力生命周期:
Task 创建 -> 订阅事件或注册计时 -> 收到结果、取消或超时 -> 通知 AbilityRuntime -> 注销订阅并释放自己如果使用协程实现等待,也需要额外的取消令牌、生命周期绑定和事件注销。
2.6 Event:把外部事实送入流程
在 UE GAS 中:Gameplay Event 可以把输入、命中、动画标记或其他上下文送入能力系统。
在本文的 Unity 方案中:
GameplayEvent是本文自定义的事件消息,可能来自输入桥接、动画适配器、投射物系统或服务器结果。
Event 描述“发生了什么”,不自动决定“这件事是否有权改变最终状态”。
例如 Projectile.Hit 可以触发 Ability 申请应用伤害,但服务器仍然需要验证命中上下文。
2.7 Cue:规则到表现的边界
在 UE GAS 中:
GameplayCue用来根据 Gameplay Event 或 Effect 状态驱动动画、粒子、音效、镜头和 UI 等表现。
在本文的 Unity 方案中:
GameplayCue是本文自定义的表现事件或适配接口,不是 Unity 内置的视觉系统。
Cue 应该让规则层:
Fireball.CastStartedFireball.HitConfirmedTarget.BurningStarted表现层再决定是播放粒子、更新 UI,还是在服务器拒绝时撤销预测效果。Cue 不应该反过来修改 Mana 或直接确认伤害。
2.8 ASC
在 UE GAS 中:
AbilitySystemComponent(ASC)把 Actor 的 Ability、Attribute、Effect、Tag、Event 和 Task 放进同一个能力系统上下文,并提供激活、取消、查询和相关运行时协调。
在本文的 Unity 方案中:
AbilitySystemComponent可以作为本文自定义的协调接口;项目也可以选择CombatRuntime或其他名称,只要保留相同语义。
ASC 的价值是让 Ability 能在同一个 Actor 上下文中找到属性、标签、效果和事件,而不是让它直接拥有每一个具体技能的业务分支。
2.9 Server
在 UE GAS 中:Server 不是 GAS 对象,而是多人网络中的运行角色。GAS 可以在网络模型中配合服务器权威、客户端预测和复制工作。
在本文的 Unity 方案中:Server 表示负责最终验证和结算的权威运行端。它可能由 Unity Server、Dedicated Server 或项目自己的网络宿主承载。
Server 的边界包括:
- 验证能力归属和请求合法性;
- 检查 Mana、冷却和阻断标签;
- 验证目标、范围和命中上下文;
- 提交权威 Effect;
- 广播结果和必要的校正;
- 处理重复请求和失序消息。
3. 完成一次火球术
| 阶段 | 流程 | 数值状态 | 状态变化 | 条件 | 异步等待 | 表现 | 权威 |
|---|---|---|---|---|---|---|---|
| 输入 | 请求激活 | 读取当前 Mana | 尚未提交 | 是否拥有能力 | 等待输入事件 | 按键反馈 | 服务器接收请求 |
| 提交 | 进入施法 | Mana 至少为 30 | Mana -30、Cooldown 2 秒 | 无眩晕、无冷却 | 可等待动画标记 | 播放抬手 | 服务器确认提交 |
| 释放 | 生成投射物 | 不直接改变属性 | 创建投射物上下文 | 目标仍有效 | 等待命中 | 火球飞行 | 服务器决定命中 |
| 命中 | 应用效果 | 目标 Health 参与计算 | 直接伤害 120、添加 Burning | 命中规则通过 | 等待 Effect 生命周期 | 命中特效、灼烧 | 服务器写入结果 |
| 结束 | 清理能力 | 读取最终状态 | Burning 仍可持续 5 秒 | 取消或完成 | 注销等待与回调 | 收招、UI 更新 | 广播确认或校正 |
这张表里,Ability 主要组织“流程”列,Attribute 主要承载“数值状态”列,Effect 描述“状态变化”列,Tag 和查询负责“条件”列,Task/Event 处理“异步等待”列,Cue 处理“表现”列,Server 贯穿“权威”列。ASC 则把这些运行时对象放在同一个 Actor 上下文里协调。