简介
记录GAS中各个模块的使用经验
引用
【gameplay-ability-system-for-unity】
【Unreal】虚幻GAS系统快速入门 - LunarMaxim的文章 - 知乎
UE-GAS架构分析(二)(GameplayAttribute) - 李嘉的文章 - 知乎
UE GAS进阶-深入理解GE - 高贵纯合子的文章 - 知乎
GameplayTag
GameplayTags是一种层级标签,如Parent.Child.GrandChild。替代了原来的Bool,或Enum的结构,可以在玩法设计中更高效 的标记对象的行为或状态。
角色本身的枚举(敌我阵营、职业)、状态(无敌、控制)都可以用Tag存储
设计上:Ability(Atk、Heal、Fire、Bomb)、Effect(Atk、Heal)、GameplayCue(DieVFX、CameraShake、UpdeCD)、Event、Character(Agent、Monster、Neutrality)、Cooldown(Atk、Skill)等基础标签。对应的模块只检测对应的tag。避免重复和模糊定义,反面例子(比如将Attack的GA标签和GE标签、角色处于攻击状态的标签都设置为Character.Attack,不利于追述Tag的来源。)
Gameplay Ability System
主要包含的内容
- ASC(AbilitySystemComponent)
- GA(GameplayAbilites)
- 角色的技能,攻击、疾跑、自爆、死亡等
- AS(AttributeSet)
- 角色身上可以用float表示的属性(hp、atk、speed)
- GE(GameplayEffect)
- 修改属性(hp、atk、speed)
- GC(GameplayCues)
- 播放特效
- 播放音效
AbilitySystemComponent
Ability System Component(ASC)是整个GAS的基础组件。
ASC本质上是一个UActorComponent,用于处理整个框架下的交互逻辑,包括使用技能 (GameplayAbility)、包含属性(AttributeSet)、处理各种效果(GameplayEffect)。
所有需要应用GAS的对象(Actor),都必须拥有GAS组件。
拥有ASC的Actor被称为ASC的OwnerActor,ASC实际作用的Actor叫做AvatarActor。ASC可以被赋予某个角色ASC,也可以被赋予PlayerState(可以保存死亡角色的一些数据
如果Character需要销毁再重新生成,如MOBA游戏角色死亡后泉水复活,那么ASC可以放在PlayerState上避免随着角色一同销毁。此时的OwnerActor是PlayerState,AvatarActor则是Character。
GameplayAbility
Gameplay Ability(GA)标识了游戏中一个对象(Actor)可以做的行为或技能。 能力(Ability)可以是普通攻击或者吟唱技能,可以是角色被击飞倒地,还可以是使用 某种道具,交互某个物件,甚至跳跃、飞行等角色行为也可以是Ability。
Ability可以被赋予对象或从对象的ASC中移除,对象同时可以激活多个GameplayAbility。 *基本的移动输入、UI交互行为则不能或不建议通过GA来实现
需要拥有GA后,才能使用GA(通过给对象身上挂在AbilitySystemComponent,数据中保存拥有多少的Abilitity)
使用分为实例化和释放两个过程,前者主要是生成一个 GameplayAbilitySpec 对象(抽象类,定义 AbilitySpec CreateSpec 的方法),并作为一部份非共有(非静态)属性赋值。
GameplayAbility的主要功能
- 设置 GameplayAbility 的 Tag、CD、Cost等属性
- 获取必要信息
- 编写逻辑,比如播放动画、应用 GameplayEffect、应用冲量?等
- 需要执行 EndAbility 结束
添加Ability的手段
- 配置时,在 AbilitySystemComponentPreset 的配置中添加对应的 Ability
- 代码动态添加,asc.GrantAbility(ability);
- 使用 GameplayEffect 添加 GameplayAbility,对于非即时的 GameplayEffect,在 GrantAbility 项添加对应的 GameplayAbility。
调用Ability的方式
- 主动释放(释放技能)
- 通过 ByClass 的方式:一次只能 Activate 一个 GA
- 通过 ByTag 的方式:可以 Activate 任意多个 GA,配合 Tag 容器使用
- 被动释放(受击)
Ability的触发条件
标签
可以限制各种技能的相互关系,比如收击时不能翻滚(设计时需要把受击时不能释放的技能都放在同一父层级下)。Tag建议以 Ability 开头。
- AbilityTags:GA的标签
- CancelAbilityWithTags:激活该GA时,打断其他拥有所有所选标签的GA
- BlockAbilityWithTags:激活该GA时,阻止激活拥有所选标签的GA
- ActivationOwnedTags:激活该GA时,赋予ASC所选的GA
- ActivationRequiredTags:激活GA时,ASC需要的标签
- ActivationBlockedTags:激活GA时,ASC不能有的标签
(SourceRequiredTags\SourceBlockedTags\TargetRequiredTags\TargetBlockedTags未实现)
冷却和消耗
需要添加冷却和消耗,则需要写好对应的GE,在配置时,配置好对应的数据。在 Cooldown GE 持续期间,玩家的ASC组件会携带对应技能的 CooldownTag,本质是通过 Tag 来限制的。对于每一个 GA 都需要写一遍 Cost 和 CD 的 GE。
【优化方式】,原理为在实例化生成 GE Spec时,修改其 Cost 和 Cooldown 属性后再将其应用。
- 使用MMC。这是最简单的方法。创建一个MMC,从实例中读取成本值,GameplayAbility该实例可以从 中获取GameplayEffectSpec。(EX-GAS已实现)
1 | float UPGMMC_HeroAbilityCost::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec & Spec) const |
- 覆盖UGameplayAbility::GetCostGameplayEffect()。覆盖此函数并在运行时创建一个GameplayEffect,用于读取上的成本值GameplayAbility。(AbstractAbility 的 SetCooldown、SetCost)
冷却GE的要求
- Duration 类型
- GrantedTags 为技能的冷却 Tag,如 Cooldown.Skill1
消耗GE的要求
- Instant 类型
- 有一个或多个 Modifier 来修改对应的属性
AbilityTask
GA 是一帧内完成的,如果想要实现类似的 Wait 的异步逻辑需要使用 Task。EX-GAS 中有Timeline 配置项。
AttributeSet
AttributeSet 负责定义和持有属性,并且管理属性的变化,包括网络同步。 需要在Actor中被添加为成员变量,并注册到ASC(C++)。
一个ASC可以拥有一个或多个(不同的)AttributeSet,因此可以角色共享一个很大的 Attribute Set,也可以每个角色按需添加Attribute Set。可以在属性变化前(PreAttributeChange)后(PostGameplayEffectExecute)处理相关 逻辑,可以通过委托的方式绑定属性变化。
BaseValue跟CurrentValue
AttributeValue 由两个float(BaseValue、CurrentValue)构成,BaseValue是属性的基础值,而CurrentValue是由于某些Buff加成变动后的值。当Buff褪去后,就可以回到BaseValue。所以分了俩个属性。比如玩家当前HP的值是100,加了一个提升20%血量的Buff。则HP的BaseValue为100,CurrentValue为120。Buff褪去后,CurrentValue变回BaseValue的值:100
分別由哪种类型的GE改变?
BaseValue由Instant(立即)的GE改变.更新会导致 CurrentValue的重新计算(根据 CalculateMode(叠加、最大值、最小值) 计算)。
1
2
3
4
5
6
7
8
9
10
11
public void ApplyModFromInstantGameplayEffect(GameplayEffectSpec spec)
{
foreach (var modifier in spec.Modifiers)
{
...
AttributeSetContainer.Sets[modifier.AttributeSetName]
.ChangeAttributeBase(modifier.AttributeShortName, baseValue);
}
}CurrentValue由Duration(持续)、Infinite(永恒) 的GE改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// <summary>
/// it's triggered only when the owner's gameplay effect is added or removed.
/// </summary>
void RefreshModifierCache()
{
// 注销属性变化监听回调
UnregisterAttributeChangedListen();
_modifierCache.Clear();
var gameplayEffects = _owner.GameplayEffectContainer.GameplayEffects();
foreach (var geSpec in gameplayEffects)
{
if (geSpec.IsActive)
{
foreach (var modifier in geSpec.Modifiers)
{
if (modifier.AttributeName == _processedAttribute.Name)
{
_modifierCache.Add(new Tuple<GameplayEffectSpec, GameplayEffectModifier>(geSpec, modifier));
TryRegisterAttributeChangedListen(geSpec, modifier);
}
}
}
}
UpdateCurrentValueWhenModifierIsDirty();
}
GameplayEffect
Gameplay Effect(GE)是Ability对自己或他人产生影响的途径。 GE通常可以被理解为我们游戏中的buff。比如增益/减益效果(修改属性)。
但是GAS中的GE也更加广义,释放技能时候的伤害结算,施加特殊效果的控制、霸体效果(修改GameplayTag)都是通过GE来实现的。
GE相当于一个可配置的数据表,不可以添加逻辑。开发者创建一个UGameplayEffect的派生蓝图,就可以根据需求制作想要的效果。
GE是修改Attribute的唯一渠道!
Stacking
用于叠加多个GE的效果,仅能用于 Infinite 和 Duration 的GE。
- StackingType:叠加栈在目标身上or施法者身上(ByTarget时,3个敌人最多对我释放3层、BySource时,3个敌人可以对我叠加9层)
- 持续时间刷新策略:
- 不刷新
- 每次apply成功后刷新持续时间
- 周期重制策略:
- 不重制
- 每次apply成功后重置Effect的周期计时
- 持续时间结束策略:
- 持续时间结束时,清除所有层数
- 持续时间结束时减少一层,然后重新经历一个Duration
- 持续时间结束时,再次刷新Duration,相当于无限Duration
Overflow
设置Stack溢出会Apply的GE。
应用
从GA或者ASC去Apply一个GE
1 |
|
GameplayEffect的施加(Apply)和 激活(Activate)
- GameplayEffect的施加(Apply)和激活(Activate)是两个概念,施加是指GameplayEffect被添加到目标身上,激活是指GameplayEffect实际生效。
- 为什么做区分?
- 举个例子:固有被动技能(Ability)是持续回血,被动技能的逻辑显然是永久激活的状态,而持续回血的效果(GameplayEffect) 来源于被动技能,那如果单位受到了外部的debuff禁止所有的回血效果,那么是不是被动技能被禁止?显然不是,被动技能还是会持续激活的。 那应该是移除回血效果吗?显然也不是,被动技能整个过程是不做任何变化,如果移除回血效果,那debuff一旦消失,谁再把回血效果加回来? 所以,这里需要区分施加和激活,被动技能的持续回血效果被施加到单位身上,而debuff做的是让回血效果失活,而不是移除回血效果,一旦debuff结束, 回血效果又被激活,而这个激活的操作可以理解为回血效果自己激活的(依赖于Tag系统)。
GameplayCue
GameplayCues (GC) 执行非游戏性相关的事情,比如音效,粒子特效,震屏等。GameplayCues通常会被复制和预测(除非设置Executed, Added或Removed是本地的)。分为 Instant 和 Duration 两种(GameplayCueInstant、GameplayCueDurational)。
调用
一般通过GE配置,也可以在GA里配置
ModifierMagnitudeCalculation
数值计算公式,唯一的使用场景是在GameplayEffect中。 GAS中,体系内运作的情况下,只有GameplayEffect才能修改Attribute的数值。而GameplayEffect就是通过MMC修改Attribute的数值。
ScalableFloatModCalculation:可缩放浮点数计算
- 该类型是根据Magnitude计算Modifier模值的,计算公式为:ModifierMagnitude * k + b 实际上就是一个线性函数,k和b为可编辑参数,可以在编辑器中设置。
AttributeBasedModCalculation:基于属性的计算
- 该类型是根据属性值计算Modifier模值的,计算公式为:AttributeValue * k + b 计算逻辑与ScalableFloatModCalculation一致。
- 重点在于属性值的来源,确定属性值来源的参数有3个:
- attributeFromType:属性值从谁身上取?是从游戏效果的来源(创建者),还是目标(拥有者)。
- attributeName:属性值的名称,比如战斗属性集里的生命值:AS_Fight.Health
- captureType:属性值的捕获类型
- Track: 追踪,在Modifier被执行时,当场去取属性值
- SnapShot: 快照,在游戏效果被创建时会对来源和目标的属性进行快照。在Modifier被执行时,去取快照的属性值。
SetByCallerModCalculation:由调用者设置的计算
不使用任何值计算模值,而是在执行时由调用者给出Modifier模值。
通过对GameplayEffectSpec注册数值来实现设置值。
设置数值映射有2种:
- 自定义键值:通过GameplayEffectSpec的RegisterValue(string key, float value)
- GameplayTag:通过GameplayEffectSpec的RegisterValue(GameplayTag tag, float value)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[private string valueName; ]
public override float CalculateMagnitude(GameplayEffectSpec spec,float input)
{
var value = spec.GetMapValue(valueName);
return value ?? 0;
}
[ ]
[ ]
private GameplayTag _tag;
public override float CalculateMagnitude(GameplayEffectSpec spec, float input)
{
var value = spec.GetMapValue(_tag);
return value ?? 0;
}
CustomCalculation:自定义计算(必须继承自抽象基类ModifierMagnitudeCalculation)
- 上述3种类型显然不够方便且全面的满足游戏开发者的所有需求,所以提供了自定义计算类的功能。
- 允许开发者自由发挥给出各种各样的计算逻辑。
总结
- ASC管理GA、GE、Attribute。
- GE可以用来给予ASC一个GA,也可以修改Attribute。(甚至还能Apply其他的GE,图中没有提到)
- GA可以发送Event给其他ASC,调用对应的GA;也可以对目标Apply一个GE,修改其属性。
- GE和GA都可以用来触发GC。