UE5 GAS 源码深度解析 | 第1篇:整体架构与设计思想
前言
如果你正在用虚幻引擎做一款带复杂技能、Buff、连招的单机或者多人游戏,很大概率已经听说过 GAS(Gameplay Ability System)。
自从GAS在UE4上推出以来,GAS已经被应用在数不尽的项目中,现在,熟悉GAS几乎已经成为了UE Gameplay开发的必修课之一了。
现在网上也有着无数的GAS相关的文章,其中不乏介绍如何使用GAS的教程。这些内容对新手来说是非常有用的,但本系列并不会把重点放在如何使用GAS上。本系列默认读者你已经是一个对GAS略知一二,甚至有着利用GAS搭建一个小的RPG的经验了。这个系列的教程,会尽量的从设计理念和源码的角度,来剖析GAS。
GAS的起源
GAS的起源于Epic的一款叫做《Paragon》的第三人称MOBA游戏,在这款游戏中,Epic面临着以下几个设计难点:
- 极致的表现力:需要实现华丽且同步的3C技能效果。
- 复杂的逻辑:需要处理诸如击飞、眩晕、变身、持续伤害等多样化的技能机制。
- 严苛的网络环境:作为一款多人竞技游戏,它必须在服务器-客户端的网络架构下,保证所有玩家看到的技能效果和伤害计算是完全一致的。
- 可拓展能力和复用性:需要方便后续各式各样的角色技能的开发。
面对这些挑战,Epic Games的工程师们意识到,UE4原有的gameplay框架无法优雅地满足所有需求。于是,他们开始专门为《Paragon》量身打造一套全新的技能框架,这便是GAS的雏形。它的核心目标非常明确:处理联机环境下的复杂技能逻辑,不仅要能实现多样的技能效果,还必须可靠地在网络环境中执行技能。
不幸的是《Paragon》这个项目没能活起来,最终停运,但是GAS这套极具价值的框架被Epic Games保留了下来,并被开放给社区。后来在Epic Games爆火的游戏《Fortnite》中,GAS这套框架也经历了严苛的试炼,最终成为一套高度通用、久经考验的“成熟框架”。
版本说明
- 文中示例基于 Unreal Engine 5.7 的源码,若出现和你本地版本不一致的情况请参照实际情况。
- 关键源码路径主要位于:
Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilitiesEditor/
当你阅读本系列文章时,可以直接在上述目录中搜索文中提到的类名,对照查看源码实现。
设计一套技能系统框架
组件式架构:把“会技能”从角色身上拆出来
在聊 GAS 的设计哲学之前,我们先从一个最直观的问题说起:在没有 GAS 之前,我们一般是怎么写技能的?
如果你是一个“不太讲究”的开发,可能会把各种技能逻辑一股脑塞进 ACharacter 或者某个“角色基类”里:按键监听在这里、属性和状态在这里、Buff 列表也在这里。技能逻辑和属性数据都混在这个类里,项目跑到中后期,这个类的 cpp 文件随随便便上千行,谁都不太敢动。
但你已经是个成熟的 UE 开发了,熟练掌握了 Actor-Component 架构,所以会自然而然地把所有技能相关的内容塞进一个组件里。这样,想让某类 Actor 拥有技能,只需要挂上这个组件即可——可以是玩家角色、AI 控制的 NPC、自动炮塔,甚至是游戏里没有实体的 Actor(比如 APlayerState)。这比“大家都继承同一个基类”灵活多了。这个组件我们就叫它 UAbilitySystemComponent,简称 ASC。
flowchart LR
subgraph 任意Actor["任意 Actor"]
direction TB
A[Character / NPC / 炮塔 / PlayerState...]
end
A -->|挂载| ASC["UAbilitySystemComponent\n(ASC)"]
ASC -->|管理| 能力["技能 · Buff · 属性 · 状态"]
图 1:组件式架构——谁需要「会技能」,就给谁挂一个 ASC。
好了,技能相关的东西都抽象进 ASC 了。接下来想一想:技能一般会影响到什么?无非是属性值和状态。打伤害会扣血量,增益 Buff 会加攻击力,有些技能还会让目标进入无敌、麻痹之类。所以必须先能把属性和状态管起来,技能才有意义。
属性这块,不同目标的属性种类各不相同,但“有一坨属性、能读能写”这件事是共通的,所以可以从一个基类继承,专门用来挂一堆属性字段——就叫它属性集 AttributeSet。一个角色完全可以有多套属性集(比如基础属性一套、战斗属性一套),都交给 ASC 持有和管理。状态则需要高度可定制,不同项目要的状态不一样,技能也只需要知道“目标当前处于什么状态”就够了,那就用一套可扩展的标识来表示。用树形命名空间下的标签既好扩展又好读,就叫它 GameplayTag 吧。
属性修改与状态修改:抽象成“效果”
状态和属性搞定之后,我们来设计“技能”本身。假设要做一个最基础的闪电术:施术者消耗一定魔法值,从天而降一道闪电,扣目标一定血量。这个技能会改施术者的 魔法值,也会改目标的 生命值。
你会发现,修改属性对技能来说又频繁又基础,而且需要高度可复用——有的技能扣血、有的加血,有的是耗蓝、有的是耗血,但“对某个属性做某种运算”这件事可以抽成同一套逻辑,只是数值和对象不同。同样,技能往往还会改变目标的状态(比如被闪电击中后麻痹),这就又和我们前面设计的状态(GameplayTag)对上了。
所以我们干脆把“修改属性 + 修改状态”封装成一个原子操作,可配置、可复用:扣 100 血和扣 10 血可以是同一套逻辑只改个数,上麻痹和上冰冻也可以是同一类操作只改 Tag。这个原子操作就叫 GameplayEffect。每一份 Effect 在施加时都会有“谁施加的”和“对谁施加”这两个上下文,同一份配置就能复用在各种技能、各种施放者与目标上。为了能批量生产和维护(把锅甩给策划),它当然得是 Blueprintable 的。
技能流程:从“能放”到“放完”全包
现在我们有了“改属性和改状态”的 GameplayEffect,还缺的是技能的流程:什么时候能放、中间要做哪些事、对谁施加哪些效果。
技能本身也是高度可复用的。同一个闪电术,玩家用和敌人用,走的流程可以完全一致;你还可以用“单发火球术”组合出一个“连续火球术”。所以我们把“一次技能从触发到结束”的流程也封装成一个可配置、可扩展、可蓝图的类,由它来负责:这个技能能不能放、释放的中间逻辑是什么、在什么时机对谁施加哪些 Effect。注意:真正改属性和状态的还是 Effect,Ability 只负责“什么时候、对谁、用哪个 Effect”;这样职责清晰,也方便复用。
技能往往还有消耗(比如耗蓝)和冷却,这些都可以用 Effect 和 Tag 来表达;某些状态(比如被眩晕)要挡住一类技能的释放,同样用 Tag 就能统一判断。如果是联网游戏,这套流程会由框架在底层和网络同步配合好,我们主要关心逻辑和时机即可。这个类就叫 GameplayAbility。
表现与逻辑分离:GameplayCue
到这一步,一个技能在逻辑和配置上已经齐了,差的只剩表现。我们希望每个技能都能通过配置匹配到对应的视觉/听觉效果,而且表现不能反过来影响逻辑判定——判定留在 GameplayAbility 里,表现只是“提示一下这里发生了什么事”。这套纯表现、可配置的提示机制,就叫 GameplayCue。
最后,如果是联网游戏,我们还得在框架层面定一条规矩:谁算出来的结果算数。通常我们应该让服务器当权威,客户端在按下技能时先本地演一遍(预测),等服务器结果回来再对账,不对就拉齐或回滚。这样既保证手感,又避免作弊和不同步。这套“谁说了算、怎么预测、怎么对账”的规则,会贯穿整个能力与效果的执行过程,我们在设计时就要把“可预测、可回滚”考虑进去,而不是事后补网络。
小结
好了,我们现在总结一下我们设计的这套框架:有了 AttributeSet 和 GameplayTag,就能表示对象的属性和状态(属性集由 ASC 持有,可以有多套);有了 GameplayEffect,就知道用什么方式和幅度去改这些属性与状态,且同一份 Effect 可复用于不同施加者与目标;有了 GameplayAbility,就能实现技能的触发与流程,并在合适的时机对合适的对象施加合适的 Effect;有了 GameplayCue,就能给技能配上对应的视听表现。ASC 负责把上面这一整套管起来、挂到任意 Actor 上,联网时再配合“服务器权威 + 客户端预测与回滚”的规则,技能系统就既灵活又能在多人环境下站稳。
——不愧是你,这套框架设计完,可以准备给 Epic Games 发简历了,因为这套框架已经和 GAS 的核心思路八九不离十了。
flowchart TB
subgraph 表示["表示层:对象有啥"]
AS["AttributeSet\n属性"]
GT["GameplayTag\n状态"]
end
subgraph 修改["修改层:怎么改"]
GE["GameplayEffect\n改属性 / 改状态"]
end
subgraph 流程["流程层:何时对谁用"]
GA["GameplayAbility\n触发 · 时机 · 对谁施加 Effect"]
end
subgraph 表现["表现层:看到啥"]
GC["GameplayCue\n视听表现"]
end
ASC["ASC 统筹"]
ASC --- AS
ASC --- GT
ASC --- GE
ASC --- GA
ASC --- GC
GA -->|施加| GE
GE -->|修改| AS
GE -->|修改| GT
GA -.->|触发| GC
图 2:GAS 核心概念关系——从「表示」到「修改」到「流程」到「表现」,ASC 把整条链管起来。
收尾
推演到这儿,骨架就齐了。你以后看 GAS 源码的时候,会反复撞见这几个词:ASC、AttributeSet、GameplayTag、Effect、Ability、Cue——它们不是拍脑袋命名出来的,背后就是咱们上面捋的这套思路。所以第一篇只讲到这儿、不往下抠具体类和图,就是想先把这张“为啥长这样、几块之间咋搭的”的图塞进你脑子里;后面按模块拆 ASC、Ability/Task、Effect/Execution、网络预测、GameplayCue 的时候,你才能一边看代码一边对得上号。
本系列默认你会用 GAS,不讲入门,只讲设计和实现。下一篇就从 ASC 开始,我们下篇见。

