跳转到正文

[UE4/5经验分享·第二篇] AbilitySystem救命

用 GameplayAbilitySystem 重构角色行为与状态的实战笔记,承接插件化系列第二篇。

2021年12月12日更新时间: 2026年10月28日

目录

GameplayAbility救命

这是我重构的第二个阶段。

在上一篇提到了之前项目代码几乎很多逻辑都赛到了Character类里面,然后玩家的各种操作、行为也都是通过枚举来表达的。然而,我打算用UE的GameplayAbility系统来对游戏中的个体角色进行重构。

GameplayABility系统网络上已经有很多大佬阐述过它是什么,怎么用的,以及有什么好处。这里,我也想分享一下,我们是如何使用它的。

一个游戏,里面会有各种各样的世界,世界里面包含各种各样的关卡,关卡里面包含了各种各样的Actor,Actor又由各种各样的组件构成,各种各样的组件又提供了各种各样的功能,最后构成了一个完整的游戏体验。

而游戏中的各种角色、其行为、功能非常多样也繁杂,有的角色可能会飞,有的角色可能会砍人,有的角色可能会爬楼梯。越复杂的角色,其功能行为越复杂,还要响应各种复杂的输入、进行各种复杂的判定,比如我在战斗的时候不能跳、我在下落的时候不能使用技能。我在交互中的时候不能爬楼梯。

而得益于AbilitySystem设计上的卓越性,我才有可能完成我如下的部分重构内容。

用Ability拆分角色复杂度

一个正常的人(角色),假设它没穿衣服,没拿武器,什么都没有。那么它具备一个人的一些基本能力,比如走、跑、跳跃、疾跑。挥拳。

那么我就让一个角色身上可以配置一堆角色能力(CharacterAbilities)。这样的角色、还会拥有它的基础属性(BaseAttributeSet),比如血量、体力,濒死血量等。

我们游戏需要玩家能够使用不同的武器,从而可以使用武器进行战斗。那么一个角色就可以挂载一个武器系统组件,只要一个角色有了这个组件,就说明它“习武了”,一个习武之人,就具备了武器拾取、切换、装备/卸下武器的能力。(这个组件会给角色这些能力),但是它还并不能用武器攻击。

一个习武之人,在切换了不同的武器过后,就可以使用不同的武器做出不同的事情,比如当角色抽出一把短刀,它就具备了左键轻攻击和右键重攻击的能力,当角色收起短刀,它就不具备这些能力了,反之如果一个角色抽出一把弓,那么它就具备了右键瞄准左键射击的能力。


尽管我不能完整描述出我的重构细节,但大致就是这样的思路,它让我的角色不再臃肿。这些一堆一堆的能力(GameplayAbilities)就是各种不同的UE4资产,通过不同形式、方式的组装,去构成不同的游戏功能,而它是可拔插的。从而让一个角色的复杂与否,是配出来的,而具体的Ability由具体的人写好即可。

比如一个角色它能受到攻击、或者被人打倒在地,或者能被人扶起,这些全都是Ability(被动)。


用AbilitySystem将代码层架空

游戏中的很多其他功能、也大量用到或者依赖到了GameplayAbilities系统。

先举例,一开始,我们在一个角色受到伤害后,会通过GameplayEvent让受伤的人触发一个受击被动(通过Context根据进攻者武器、进攻方向等播放不同方位Montage),这些逻辑都是写在C++的,要发事件、必然要通过EventTag,且根据期望发生的结果不同、传递的EventData也可能会变。而这些易变的逻辑是写在C++里的,不太符合我们整个项目就是一个插件的风格。万一我要受击的人触发一个跳舞的被动、又或者根据伤害强度、方位不同、其受击表现有天壤之别呢?

最后,做了一个被动技能叫InjuredAbility,一开始就在角色身上配置好,这个技能监听角色受到伤害的事件,并根据不同情况触发各种其他的受击被动技能。比如受到轻攻击,就触发一个轻受击(退1步);受到重攻击,就触发一个重受击(退两步);受到远程箭头伤害,就抖一下Mesh骨骼。如果我不给一个角色配置InjuredAbility,那么这个角色就失去了“被打有反应”的能力。

再举例,策划冒出一个新想法,需要做伤害克制功能,即某A对某B造成某类伤害的时候,会在已经打出的伤害上额外追加某系数/常数加成。而我们的实现方式依然是一个被动技能叫DamageCorrection,这里从角色管理器监听任意角色A对任意角色B造成任意类型伤害的事件,根据角色的唯一ID去克制表里查询一个伤害克制关系,如果A对B确实能从表里查出克制关系,就从克制关系表里取出额外伤害值,然后追加伤害到角色B上。同样的,我不配置这个技能给角色,角色就不会有存在克制关系这个功能。

简单来说,我们将易变的逻辑留给蓝图来做,但通过Ability来组织这些功能,让C++代码成了纯粹的抽象,暴露各种各样的事件。我们有一种将Ability当成组件来用的感觉,但相比组件,我们能更动态地去控制功能的可拔插性,我们不希望一个角色挂很多组件、拥有很多冗余功能。

总结

由于GameplayAbilities的灵活性之高,我们可能发展出了一些比较奇葩的用法,但它确实在Gameplay这一块,为项目的重构提供了很大的帮助,至少不会再有“水面上建房子”的感觉,也让我更有信心去应对后续的难题。

同时,越发觉得编程语言真的只是剑,设计模式才是剑法,不会剑法,挥出来的就是啦啦舞。

下一篇,会讲到AI制作上的一些迷途,以及我为什么抛弃了虚幻自带的行为树,转而选择了分层状态机(LogicDriver)。


相关阅读