一、回顾:GAS 是什么
在第 8 篇(Gameplay 框架)里简介了 GAS 的四个核心概念——ASC、Ability、Effect、Tag。这篇往下钻一层——实际做一个技能需要知道什么。
GAS 的典型技能执行流程:
玩家按下技能键 → Input → ASC::TryActivateAbility(AbilityTag) → CanActivateAbility? (Cost? Cooldown? Tag Blocked?) → 是 → CommitAbility() → ActivateAbility() → 应用 GameplayEffect (Cost, Cooldown) → 执行技能逻辑(生成 Projectile、播放动画、造成伤害) → 结束时应用 GameplayEffect (Damage, Buff)二、技能注册:如何把技能配到角色身上
1 三种授予方式
// 方式 1:C++ 硬编码——最简单void AMyCharacter::BeginPlay() { Super::BeginPlay(); if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent()) { ASC->GiveAbility(FGameplayAbilitySpec(MyAbilityClass, 1)); // ↑ Level }}
// 方式 2:DataTable 批量授予——策划在表格里配// DT_Abilities.csv:// Character | Ability | Level | InputTag// Sofia | GA_RevealItem | 1 | InputTag.Skill.Q// Raven | GA_SeeAll | 3 | InputTag.Skill.Q
// 方式 3:GameplayEffect 授予——技能给技能("获得新技能"本身是一种 GE)// 当玩家升级时,一个 GE 可以 GrantAbilityFGameplayAbilitySpec 不只是存技能类类型——它还存技能的 Level、InputTag 绑定、激活状态、冷却剩余时间等运行时状态。
2 ASC 放在哪
| 放在哪 | 适用 | 为什么 |
|---|---|---|
| PlayerState | 多人游戏(推荐) | PlayerState 跨 Pawn 持久——死亡/重生 ASC 还在 |
| Pawn/Character | 单人或简单场景 | Pawn 销毁 ASC 就没了——断线重连丢技能状态 |
| PlayerController | 特殊用途 | Controller 换 Pawn 时技能不丢,但 AI 没有 Controller |
多数项目放在 PlayerState 上——和 第 8 篇说的”PlayerState 是跨 Pawn 的个人记分卡”一脉相承。
三、GameplayEffect 的计算管线
1 GE 的结构
GameplayEffect├── Duration Policy(Instant / HasDuration / Infinite)├── Modifiers(属性修改——伤害、治疗、Buff)│ ├── Attribute(要改哪个属性——如 Health)│ ├── ModifierOp(Add / Multiply / Override)│ └── Magnitude(改多少——固定值/曲线/AttributeBased/自定义计算)├── Executions(自定义计算——比 Modifier 更灵活)├── GameplayTags(GE 运行时给目标贴 Tag、移除 Tag)├── Granted Abilities(这个 GE 给目标添加技能)└── Stacking(叠层规则——如"中毒"能不能叠 5 层)2 Instant vs Duration
// Instant:立刻生效一次// 如:GA_Fireball 中的 DamageEffect// 应用 → 扣 50 血 → 效果结束(GE 被销毁)
// HasDuration:持续一段时间// 如:GA_PoisonBlade 中的 PoisonEffect// 应用 → 每 1 秒扣 5 血 → 持续 10 秒 → 效果结束// Duration 期间,GE 一直"挂"在目标身上
// Infinite:永久有效,直到手动移除// 如:被动技能"剑圣——基础攻击力 +20%"// 应用 → 永不被移除 → 直到剑圣死亡或技能被清3 Modifier 的计算顺序
当 Health 需要计算当前值时:
1. 找到所有影响 Health 的 GE(Active Effects)2. 按优先级排序3. 从 BaseValue 开始4. 累加所有 Add 类型的 Modifier5. 乘以 Multiply 类型的 Modifier6. 应用 Override(如果有,直接覆盖前面的结果)这就是为什么 GE 体系比手写属性逻辑强——你不管理更新顺序、不管理 GE 的生效/失效通知。ASC 自动追踪所有 Active GE,任何 GE 变化时自动重算属性。
四、Tag 的阻挡与免疫
1 Ability 的 Tag 约束
// GA_Stun(眩晕技能)AbilityTags: Ability.CC.Stun // 这个技能是什么CancelAbilitiesWithTag: Ability.Movement // 激活时取消移动技能BlockAbilitiesWithTag: Ability.Active // 激活时阻挡所有主动技能ActivationBlockedTags: Status.Dead // 死亡时无法激活ActivationRequiredTags: Status.Alive // 必须存活才能激活Tag 之间的关系形成了技能系统的”交通规则”。 眩晕期间不能移动、死亡期间不能放技能——不用在 GameMode 里写 if (IsDead) return;,全由 Tag 规则来表达。
2 免疫
// 法师的 魔法护盾 GEGrantedTags: Status.ImmuneToStun // 有这个 Tag,眩晕技能无法激活
// GA_Stun 的配置ActivationBlockedTags: Status.ImmuneToStun // 如果目标有这个 Tag,技能灰掉策划在技能表里配 Tag 规则就行——不需要程序员加 if (HasMagicShield)。
五、客户端预测
GAS 在多人场景下支持技能预测——和第 7 篇(网络模型)的客户端预测是同样的思路:
1. 客户端按下技能键2. 客户端 ASC 本地执行 CanActivate(Cost 检测、Tag Block、Cooldown) → 如果本地判断可以放,立刻播放技能动画(Prediction)3. 同时发送 ServerTryActivateAbility RPC4. 服务端重新执行 CanActivate(防作弊) → 如果通过 → 权威执行技能 → 如果不通过 → 发回 PredictionRejected → 客户端回滚预测键(Prediction Key):每次预测激活有一个唯一的 Key。服务端回传确认时带上这个 Key——“Key=42 的技能批准了”或”Key=42 被拒绝了”。客户端根据 Key 匹配自己发出的预测。
六、一个完整技能:RPG 里的”法师火球”
GA_Fireball(法师——向目标区域释放火球)
[Ability Definition] AbilityTags: Ability.Mage.Fireball ActivationBlockedTags: Status.Dead ← 死亡时不能用 ActivationRequiredTags: Status.Weapon.Equipped ← 必须装备法杖
[Activation Logic] 1. CommitAbility() → 消耗 Cooldown GE 2. 服务端:验证目标区域和施法者状态 3. 客户端:播放施法动画,生成预测的火球特效 4. 服务端:计算命中结果 → 对范围内敌人应用伤害 GE
[Cooldown] 一个 GameplayEffect (Duration=5s, 结束后移除冷却 Tag)这个技能横跨了 Tag、GE、RPC 三个 GAS 子系统——但每个子系统只做自己的事。 这就是 GAS 的设计哲学:把复杂的技能逻辑拆成可组合的小块,而不是在代码里手写长 if-else。
七、总结
| 概念 | 一句话 |
|---|---|
| GiveAbility | 把技能注册到 ASC——可以代码给、DataTable 给、GE 给 |
| Modifier | GE 里声明”改什么属性、怎么改”——BaseValue → Add → Multiply |
| Execution | GE 里自定义计算——比 Modifier 更灵活(可访问多个属性、做复杂判断) |
| Tag 阻挡 | 用 Tag 声明技能的前置条件/互斥关系——不需要写 if |
| Prediction | 客户端本地预执行 → 服务端验证 → Rejected 就回滚 |
| GAS 的核心哲学 | 技能规则化(Tag + GE)而非代码化(if-else) |
GAS 学习曲线陡峭——但一旦理解了”ASCD能力注册 + GE状态变化 + Tag阻挡规则”这三件事,你就能把大部分 Gameplay 需求表达为”给这个角色配什么技能”而不是”在这个类的第 300 行加一个 else if”。