做UE5项目这么多年,我见过太多人在GAS(Gameplay Ability System)面前铩羽而归。很多人并不是死在C++编译错误上,而是死在第一步——看不懂术语。打开官方文档,满屏的Ability、Effect、Attribute、Tag,每个单词都认识,拼在一起就完全不知道在说什么。更迷惑的是,你在社区搜“GAS是什么”,能搜出三种截然不同的答案:一个叫Gameplay Ability System的插件、一个叫Gameplay Attribute System的缩写、还有一个是“游戏贫血”的医疗术语。今天我就把GAS这套系统里最劝退人的专业术语全部拆开,对照着讲清楚,看完你至少能无障碍看懂大部分GAS相关的教程、论文和源码注释。
我自己第一次接触GAS是在UE4.26时期,当时想做一个完整的RPG技能系统,按照传统做法写了三百多行技能逻辑,结果一听到同事说“你为什么不直接用GAS”的时候,整个人是懵的。逼着自己啃了两周文档,才算是真正摸清楚这套系统的脉络。这篇文章的内容,就是我在那段时间以及后来在UE5正式版上迁移项目时积累下来的术语对照经验。适合三种人看:正在研究GAS但被英文术语卡住的开发者、准备从传统技能系统迁移到GAS的团队,以及想搞明白GAS到底是什么的策划或技术美术。
1. 别急着写代码,先把GAS的“三座大山”分清
GAS这套系统最大的问题不是技术难,而是术语层面存在大量同形异义和一词多指。你没理解清楚就去写代码,很容易出现“变量类型写对、逻辑方向搞反”这种让人抓狂的情况。我就遇到过一个新手,把GameplayEffect当成了GameplayAbility,硬是在Ability里调用Effect的Duration,结果运行起来技能只生效了一帧就没了。
1.1 最容易被混淆的“GAS”三种指代
先解决最大的坑。在UE5语境下,GAS这个缩写在不同的地方有完全不同的含义:
- Gameplay Ability System:这是官方插件,也就是Epic在Action RPG示例项目里集成的那套框架。它主要解决的是技能、Buff、属性、冷却、伤害计算、网络同步这些能力系统开发中的通用问题。
- Gameplay Attribute System:这是一些社区开发者对ASC(AbilitySystemComponent)的误称,严格来说并不存在一个叫“GAS组件”的类,你真正要创建的组件是AbilitySystemComponent,简写ASC。
- 游戏领域里的“Gas”:这是行业黑话,指代“伤害/治疗数字”或者“状态异常”,跟UE5八竿子打不着,搜索资料时要注意过滤。
所以当你在文档里看到“GAS架构”时,第一反应应当是“Gameplay Ability System的架构”;当有人跟你说“要添加GAS组件”时,他大概率是在说ASC组件。搞清楚这个前置概念,后面的路才能走顺。
1.2 ASC才是引擎里真正的“系统”
官方文档里经常出现“Get the ASC”这种说法,ASC的全称是AbilitySystemComponent。它不是一个独立System类,而是一个继承自ActorComponent的组件。高层的GameplayAbility、GameplayEffect、AttributeSet本身都只是数据或行为定义,真正驱动它们运行、进行属性计算、网络同步的,是这个ASC。
我把ASC类比成游戏中的“血液循环系统”:
- AttributeSet是血液里的各项指标,比如血量、蓝量、攻击力。
- GameplayEffect是血管里的药剂注入,负责改变指标。
- GameplayAbility是“动作”,比如挥拳、施法,它通过ASC去请求效果并应用。
- 而ASC本身连接着所有这些模块,同时还要负责将变化同步到服务器和客户端,确保没有作弊空间。
这个类比特别重要,因为80%的GAS开发困惑,都源于没有分清“谁是被动数据、谁是主动行为、谁是执行者”。你想让角色掉血,不应该直接在属性上减数值,而应当通过ASC去应用一个GameplayEffect。如果你想给角色一个技能,也不需要写复杂的血量修改逻辑,只需要创建一个GameplayAbility类的蓝图或C++类,然后在ASC里给予(GiveAbility)即可。
2. 六大核心术语逐项拆解,对照着用
在铺开细节之前,我先把GAS里最核心的六个术语做一张对照表,方便你随时回来查。
| 术语 | 全称/含义 | 一句话用途 | 对应类名或资源 |
|---|---|---|---|
| GameplayTag | 游戏性标签 | 标记状态、行为、物品属性,全系统通用 | FGameplayTag |
| AttributeSet | 属性集 | 集中定义角色的各种数值属性 | UAttributeSet |
| GameplayAbility | 游戏能力 | 定义技能/动作的行为逻辑 | UGameplayAbility |
| GameplayEffect | 游戏效果 | 以数据驱动方式修改属性 | UGameplayEffect |
| AbilityTask | 能力任务 | 异步节点,实现技能中的等待、移动、追踪等 | UAbilityTask |
| AbilitySystemComponent | 能力系统组件 | 承上启下的核心组件,也是网络同步的中枢 | UAbilitySystemComponent |
制表容易,理解难。下面逐项展开说,并给出我实际使用时积累的注意事项。
2.1 GameplayTag:全系统的最小单位
GameplayTag并不是GAS独有的新概念,它本质是一个分层的标签系统,用FGameplayTag结构体表示。你可以把它想象成给物品贴标签:一个敌人可以同时拥有Enemy.Boss.Fire、Status.Immune.Poison这些标签。引擎在内部使用GameplayTag做非常快速的正则匹配,大大方便了技能的触发条件、状态免疫、Buff叠加限制等逻辑。
在GAS语境里,GameplayTag有两类特殊角色:Ability Tags和Gameplay Effect Tags。
- Ability Tags标记能力本身的属性,比如
Ability.Type.Damage。 - Gameplay Effect Tags标记效果的来源或行为,如
Effect.Damage.Physical、Effect.Buff.SpeedUp。
以技能为例:你设定一个火球术技能,它的触发条件可以写成“当目标身上有State.Burning标签时,伤害翻倍”。你不需要写任何C++逻辑来检查“是否燃烧中”,只需要做一次Tag Query匹配。这就是标签系统的强大之处——把复杂的条件判断变成数据配置。
实操中有三个易错点:
- 不要滥用Tag种类,标签是层级结构,不要把所有信息塞进一个Tag里,例如用
State.Burning而不是BurningState,这样后续做TagQuery的匹配逻辑会自然得多。 - 每次修改Tag,虽然蓝图里立刻能看到,但C++编译之后需要重启编辑器,否则编辑器可能出现Tag列表找不到的情况。
- 网络同步场景中,Tag是客户端和服务器同步的核心依据之一,如果你自定义的Tag没有加在Default Engine.ini的Tag配置列表里,联网调试时经常会看到“Client Tag Missing”。
2.2 AttributeSet与Attribute:数值的容器与属性
AttributeSet在中文社区常被直译为“属性集”,它定义了一个角色有哪些数值,比如Health(生命)、Mana(法力)、AttackPower(攻击力)、MoveSpeed(移动速度)。这个类通常以C++类的形式存在,你可以创建子类并添加自己项目的自定义属性。
在很多老版本教程里,会看到大家在Character类里写:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Attributes") float Health;然后手动计算扣血、加血、护甲减伤。用GAS之后,这套逻辑应该放弃。正确做法是:
UCLASS() class MYGAME_API UMyAttributeSet : public UAttributeSet { GENERATED_BODY() public: ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health) UPROPERTY(BlueprintReadOnly, Category = "Attributes") FGameplayAttributeData Health; };之后再通过GameplayEffect去修改Health,而不是直接执行Health -= Damage。GAS这一套设计背后的核心思想是:所有属性的改变都应该是可追踪、可回调、可回滚的。如果你直接给属性赋值,就无法实现伤害结算的延迟判定(比如格挡、免疫、吸收),也无法利用服务器的权威同步逻辑来防止外挂篡改血量。
2.3 GameplayAbility:技能模块
GameplayAbility是所有“技能”“动作”“能力”的老家。它定义了技能激活时的逻辑、能否被打断、需要消耗什么资源、激活后可以执行哪些任务。一个标准的GA(GameplayAbility)通常包含:
- 输入处理(比如按下技能键)
- 激活条件(比如冷却结束、资源足够)
- 活动过程(移动、播放动画、发射投射物)
- 结束时机(命中、动画结束、被打断)
举个例子,你设计一个“旋风斩”技能,这个GA里会有:
UCLASS() class UGA_Whirlwind : public UGameplayAbility { GENERATED_BODY() public: virtual void ActivateAbility(...) override; virtual void EndAbility(...) override; };然后在ActivateAbility里启动一个PlayMontageAndWait任务、一个WaitGameplayEvent监听命中事件。这里要特别留意:GA本身尽量不要写“如何扣血、如何添加Buff”的代码,GA的职责是协调流程,具体数值改变交给GameplayEffect去处理。
初学者最容易犯的错误,是在GA里疯狂写Target->GetAttributeSet()->Health -= Damage这种代码。首先这是绕过了GAS的事件体系,其次它在单机模式下可能看不出问题,一旦上服务器和客户端双端,数值就完全不同步了,最后就是我在第6节会提到的“灵异回滚”事件。
2.4 GameplayEffect:不自己加血的“效果”
GameplayEffect是我认为GAS中最容易引起误会的名词。从字面上看,它像是“技能产生的视觉效果”,实际上它是一张“数据配置单”,用来定义属性如何变化。比如:
- 一个“伤害药水”效果,会让Health在3秒内减少50点。
- 一个“加速”效果,会让MoveSpeed增加150,持续5秒。
- 一个“免疫”效果,会在持续期间给角色添加一个
Status.Immune标签。
GameplayEffect本身并不执行任何逻辑,它只是把“谁、在何时、通过什么方式、改变什么属性、改变多少、持续多久”这些信息打包好了。真正让效果生效的是ASC调用ApplyGameplayEffectToSelf或ApplyGameplayEffectToTarget。
我把GameplayEffect比作一个胶囊:胶囊里装的是药品说明书(Modifier列表),而不是药品本身。角色的ASC是胃,把胶囊消化之后才产生实际数值变化。所以如果你没给角色挂ASC就直接Apply一个Effect,引擎只会警告“No AbilitySystemComponent found”,不会给你扣除任何血量。
在编辑GE时,最关键的是三个区域:
- Duration Policy:决定效果是Instant(瞬间)、Infinite(持续到被移除)还是Duration(定时)。
- Modifiers:决定改哪些属性、加成方式(加法、乘法、覆盖)。
- Stacking:决定同类效果是否可以叠加、叠加规则是什么。
Instant类型的GE非常特殊,它只执行一次,执行完就自动结束,常见于瞬发伤害和治疗。如果错误地把瞬发伤害设成了Duration类型,就会出现“伤害每秒跳一次”的问题,我在项目里遇到过三次这种情况,都是因为这个配置被复制错了。
2.5 AbilityTask:异步节点
AbilityTask可以理解为“技能中的协程”。它的作用是让一个技能按照时间轴或事件流分步执行。例如:
- 播放攻击动画并等待动画结束时回调
- 等待玩家再次按下攻击键以触发连招
- 追踪投射物,直到命中目标
- 读取服务器数据,直到数据不及
传统方法做这些需要用Timer、EventDispatcher、Delegate,而AbilityTask把这些都封装成了节点,你在蓝图里可以直接拉出一条线等一个异步事件。UE5里你创建自定义Task时,需要继承UAbilityTask:
UCLASS() class UAbilityTask_MyWaitEvent : public UAbilityTask { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable) FGenericGameplayTaskDelegate OnFinished; UFUNCTION(BlueprintCallable, Category = "Ability|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility")) static UAbilityTask_MyWaitEvent* MyWaitEvent(UGameplayAbility* OwningAbility); };这里有几个实际经验:
- 每个Task都要在蓝图节点封装中解决
OwningAbility的传递问题,否则蓝图节点找不到调用方。 - Task在设计时要明确“由谁触发结束时”——是被Datasmith调用结束,还是目标死亡自然结束?这类逻辑边界在命名上要非常清楚。
- 不要在Task里直接引用Actor,应该引用Actor的ASC或者持有的Tag,这样才能避免网络同步时引用混乱。
2.6 Cue与Execution Calculation:表现与逻辑
GameplayCue是负责“表现层”的,比如命中火花、中毒后的绿色持续效果、被击飞时飘出的伤害数字。它本身不改变属性,纯粹是为了让玩家觉得“我的攻击有效了”。通常你会通过GE配置一个Cue Tag,当该GE应用时,ASC会广播这个Tag对应的事件,所有挂载了该Cue的Actor或Widget就能响应。
Execution Calculation(执行计算)是GE中浑然一体的模块,它在效果应用时执行一次,可以根据攻击者的属性、目标属性、随机数、暴击判断等,动态计算最终的Modifier值。举个例子,普通GE只能配置固定伤害,而Execution Calculation可以写“最终伤害 = 攻击方攻击力 × 技能倍率 已减 目标护甲”。这种计算用C++写类继承UGameplayEffectExecutionCalculation,并实现Execute函数。它是GAS高级玩法的核心入口,术语一定要记住,后面看很多教程时它出现的频率极高。
3. 进阶术语:Prediction、Server与客户端同步
当你想从单机技能过渡到多人联机时,GAS里那套同步术语就成了绕不过去的关卡。很多新手在多人场景中看到函数名带Server、Client、Local、Predicted,立刻就被绕晕了。下面这件事我觉得很值得讲清楚。
3.1 Local Predicted、Server Only、Server Initiation
在GAS中,“预测”(Prediction)是让客户端先行执行技能逻辑,以减少网络延迟带来的“卡顿感”。它有三种常见类型:
- Local Predicted:本地预测。客户端不需要等服务器返回就能立即执行效果。比如连击动作,你按下攻击键,本地立刻播放动画和判定范围。
- Server Only:仅在服务器执行。服务器拥有最终权威,客户端只等服务器同步结果。比如金币扣除、掉落物生成这种涉及全局数值的改动,绝对不能本地预测。
- Server Initiation:由服务器发起执行。客户端不主动执行技能,而是等服务器发指令。比如Boss的全屏技能,玩家需要等服务器确认后再播放出招动画,才能保证所有客户端看到同一个起手式。
这个概念你可以理解为“谁拍板的问题”:本地预测就像“你家楼下的快递柜”,你输入取件码,它立刻开门;Server Only就像“银行转账”,必须等后台确认最终账目;Server Initiation则是“中央机房的广播系统”,大屏由中控室统一发布,各个分屏不能私自显示。
实际项目中,移动、翻滚这类高频操作通常用Local Predicted;伤害结算、掉宝、金币用Server Only;Boss大招必须用Server Initiation。如果顺序搞反,轻则手感发飘,重则出现严重的数据不同步。
3.2 预测失败回滚:也读“权杖”还是“回滚”?
Prediction和Rollback是GAS文档里一对“形影不离”的词。当客户端先行执行了本地预测后,服务器可能判定这次预测无效(比如原本本地以为放出了技能,但服务器认为你的蓝不够)。这种情况下,服务器会回传一个“失败”消息,客户端必须把这次预测执行的部分全部回滚。
在实操中,最常见的回滚场景是“本地闪现成功但服务器拒绝”。原因是客户端和服务器对触发条件判断不一致——比如说蓝量在客户端显示还有10点,但服务器在你按技能的一瞬间已经判定为只有9点。GAS通过PredictionKey机制来追踪每一次预测,确保回滚时不会影响其他已生效的效果。
从这里你会发现,真正的核心不是“记住术语”,而是建立“预测—确认—回滚”的心智模型。我在社区里看过太多人在问“为什么技能在服务器上放不出来”,其实大部分是把这个模型搞反了:他们把伤害结算写进了本地预测中,而伤害结算本该属于Server Authority的一部分。
4. 实操中那些劝退人的“术语墙”
不知道你有没有经历过这样的场景:照着教程敲完代码,编译器报了个Unable to find type 'FGameplayAttribute';或者去蓝图里搜索节点,搜GameplayEffect搜出来一大堆看似完全不相关的内容;再或者,你在网上搜“刀光材质”,结果搜到一篇关于“GAS伤害判定”的文章,点进去看发现完全不是一回事。这些情况我都遇到过,下面说说为什么。
4.1 命名乱象:GA_GE_前缀与编译器报错
GAS本身是一套框架,但每个人写项目的时候都会自己加前缀去做区分。于是你会在不同教程里看到:
- GA_MeleeAttack、GA_Fireball(GA指GameplayAbility蓝图)
- GE_Damage、GE_HealBuff(GE指GameplayEffect蓝图)
- AT_Health(AT指AttributeSet类)
这种命名约定方便人阅读,但对引擎来说没有任何意义。编辑器不会因为你给一个类起名GA_就自动把它当成GameplayAbility,真正决定类型的是父类。编译器报错最常见的原因是:你创建了一个叫“GE_Fireball”的蓝图,但它的父类其实是Actor,不是GameplayEffect。很多新手被前缀迷惑,以为自己已经创建了正确的资源类型,结果反复检查节点却发现怎么连都连不上。
我的建议是:项目里定一套命名规范,并且在面试或组队时明示这套规范的适用范围。团队里一个人按“带GE前缀的蓝图就是GE”来使用,另一个人按“带GE前缀的蓝图不一定就是GE”来理解,很容易出问题。
4.2 蓝图命名中的坑:BP_GA与BP_GE
UE5的蓝图系统里,新建蓝图时会让你选择父类。在GAS项目里常见的选择是父类为GameplayAbility的蓝图(通常是BP_GA_Fireball),以及父类为GameplayEffect的蓝图(通常是BP_GE_FireDamage)。这里有一个很隐蔽的坑:
- GameplayAbility的蓝图在创建后会有很多“事件节点”可以直接重载,比如OnActivate、OnEndAbility等。
- GameplayEffect的蓝图本质上是一个数据资产,它没有事件节点,只有配置界面。
- 如果你误把GameplayEffect蓝图当成GameplayAbility蓝图打开,界面里一大堆属性配置会让你莫名其妙。
项目交接时,如果老同事说“你去改一下BP_GA_Fireball”,但你把文件打开后看到了Modifier列表,那就是找错了文件。这种错误在多人协作中特别容易出现,尤其是在一些没有严格文件夹管理的项目里。
4.3 双指触摸、刀光材质那些关键词为什么总牵到GAS
最近有朋友问我:“我看别人做UE5刀光材质,教程里却一直在提GAS,这是什么情况?”其实这不奇怪。GAS作为一套“能力系统”,它天然适合和视觉效果配合。刀光这种技能特效,本质是GA激活后播放特效并执行命中判定;双指触摸蓝图则可能对应GA的输入处理。当博主讲解技能框架时,不可避免地要引用GAS术语,所以你会看到“刀光材质”的话题里藏着一大堆GA、GE、Tag。
这其实也说明GAS已经是目前UE5技能系统领域的事实标准。无论你是做ARPG、MOBA、FPS还是RTS,技能触发、属性变化、状态管理这些需求最终都会落到GAS的术语体系上。理解了这套术语,你在浏览大量UE5相关文章时,就像拿到了一把通用钥匙。
5. 常见问题与排查技巧实录(术语角度)
在真实项目中,术语理解不到位导致的bug往往比代码逻辑错误更难排查。我把自己遇到过的和帮朋友排查过的高频问题整理成一个速查表,希望能帮你少走几步弯路。
5.1 “TargetActor在客户端为空”怎么回事
这个问题的直接原因是:你只在自己的Ability里创建了TargetActor(目标收集器),但没注意它是本地预测还是服务器执行。当本地预测Actor在服务器上不产生,而服务器又需要它的数据来执行GE时,客户端就会“凭空消失”很多效果。
通常解决方案是:
- 确认TargetActor的Replicates属性设置为true。
- 网络的TargetActor要在Performance和同步需求之间做取舍,不要无脑Local Predicted。
- 如果目标数据是纯数值,建议直接用
FGameplayAbilityTargetDataHandle在预测数据里传递,而不是依赖TargetActor实体。
5.2 GameplayEffect Stacking 的 RemoveStack 参数
Stacking(堆叠)是GAS中一个高频术语。很多Buff设计成“最多叠加3层,每次命中+1层,持续5秒”。这里就有参数细节问题:GE的Stacking机制里有一个SetStackCount和RemoveStack字段,如果你不搞清楚StackingType是AggregateByTarget还是AggregateBySource,移除层数时经常会出现“一次性全清”的情况。
- AggregateBySource:每层来源和来源相关,来自同一个施法者的效果可以叠加。
- AggregateByTarget:只看目标身上的同一GE,来自不同施法者也共享层数。
实战中,比如3层中毒debuff,如果3个不同骷髅弓手同时射箭,AggregateByTarget会让中毒层数叠加到3;AggregateBySource则一定会明确是3个不同来源。制作团队必须根据游戏设计意图选择,否则“反甲的毒伤”这类机制就会错乱。
5.3 编辑器里搜不到 GAS 相关的类
启动的UE5项目里如果没开启GAS插件,你是无法在类搜索里找到GameplayAbility这些类的。很多人第一次的时候都会卡在这里。
开启方式:
- 打开Edit → Plugins。
- 搜索Gameplay Abilities。
- 确保该插件已启用。
- 重启编辑器。
另外还要确认项目里有没有启用GameplayTags和GameplayTasks插件。GAS依赖这两者运行。如果你在一个纯蓝图项目里想用GAS的代码书写逻辑,还需要在Build.cs文件里添加依赖模块:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GameplayAbilities", "GameplayTags", "GameplayTasks" });5.4 一张完整的去伪存真对照表
最后我又整理了一张“从英文到中文、从概念到实操”的完整术语表,方便你贴在项目文档或备忘录里。
| 英文术语 | 常见翻译 | 真正的角色 | 我踩过的坑 |
|---|---|---|---|
| Gameplay Ability System | 游戏性技能系统 | 整套框架,不是单一组件 | 有人用来指ASC,指代混乱 |
| AbilitySystemComponent (ASC) | 能力系统组件 | 整套框架的“心脏” | 以为它是单例,实际是组件 |
| GameplayAttributeData | 游戏性属性数据 | 属性的数值包装 | 还是习惯直接float,绕过了回调 |
| GameplayEffect (GE) | 游戏性效果 | 固定方案的数据配置 | 没做执行计算前只能配死数值 |
| Modifier | 修饰器/修正器 | 修改属性的一个算子 | 叠加顺序和Multiply操作符搞反 |
| Duration Policy | 持续时间策略 | 决定GE是瞬发还是持续 | Instant和Duration混乱导致每秒掉血 |
| Attribute Set | 属性集 | 承载属性的容器 | 多人共用同一套时会串数据 |
| AbilityTag | 技能标签 | 表示能力元数据 | 没定义父级分类匹配频繁出错 |
| GameplayCue | 技能提示/表现提示 | 表现层触发器 | 在客户端和服务器都执行导致重复触发 |
| AbilityTask | 技能任务 | 协程/异步节点 | 在蓝图里手动管理生命周期容易泄漏 |
| TargetActor | 目标收集者 | 辅助识别敌人/位置 | 没开Replicates导致目标丢失 |
| PredictionKey | 预测键 | 标记本地预测的凭证 | 没有独立键导致重叠预测互相覆盖 |
| Execution Calculation | 执行计算 | 根据双方属性做动态计算 | 一些教程也简写Exec Calc,容易看懵 |
6. 个人经验与建议
到了这一步,我想分享一段比较个人的心得。GAS术语这件事,最大的难点在于“你已经在用自己习惯的术语体系思考问题,而GAS有自己的体系”。当你试图用旧的“Skill、Buff、Damage”去理解新的“Ability、Effect、GE”时,虽然它们看似对应,但实际上存在微妙差异。Skill在传统游戏里往往既包含效果又包含表现,而GAS把这两者彻底拆开,并通过Tag和ASC重新连接起来。所以你会发现“用GAS写技能”和“用普通角色蓝图写技能”是完全不同的心智模式。
我建议团队在接GAS之前,一定要先做一次术语对齐的培训。不要直接让程序去写代码,而是把策划、客户端、服务器端的同学叫到一起,花半天时间把GameplayEffect、GameplayAbility、AttributeSet、GameplayTag、AbilityTask这五个核心概念讲清楚。曾经我们项目因为策划不理解GameplayEffect和GameplayAbility的区别,误把“技能是否触发”配置在GE的Modifier里,结果每次技能触发都异常,排查了整整一天。术语对齐之后,这类问题基本不会再出现。
最后再分享一个小技巧:如果你在阅读GAS源码或文章时遇到不认识的术语,不要急着去引擎文档里搜,先从“数据还是行为”这两个维度对它做一次归类。凡是“数据、状态、配置”方向的,多半和AttributeSet、GameplayEffect、Tag有关;凡是“流程、触发、行为”方向的,多半和GameplayAbility、AbilityTask、Cue有关。用这个二分法看,很多术语的定位就瞬间清晰了。GAS的学习曲线比较陡,但术语这关过了之后,真正的设计和架构乐趣才会显现出来。