- 什么是GameplayAbilitySystem(GAS)
- AbilitySystemComponent(ASC)
- GameplayTag
- GameplayAttribute
- AttributeSet
- GameplayEffect(GE)
- GameplayAbility(GA)
- GameplayCue
- GameplayTask
之前有关GAS的笔记统一移到这里,主要记录GAS中重要的类型和它们的关系。
(2022.3.16)ui方面的事情总算告一段落,有时间继续去看技能有关的文档了。
(2025.2.28)没学会的知识不会自己跑回来找你。
什么是GameplayAbilitySystem(GAS)
GAS是虚幻引擎下的一套技能框架插件,它的优势是易于扩展,实现复杂的技能流程,而且支持网络复制,它在EPIC公司内部已经被Fornite登大型的商业联机游戏所使用,开发联机游戏的坑很多都帮你踩过了,避免重复造轮子的麻烦。其中分离的GA、GE模块实现了技能核心逻辑和技能效果的解耦,易于修改和复用,开发时能够专注在某个特定模块,而不是在一个技能中考虑所有事情。
主要参考的资料是GASDocument及其中文翻译,还有虚幻官方的视频介绍和GAS入门的直播录像。
AbilitySystemComponent(ASC)
要使用GAS的功能就必须为某个Actor挂上ASC,主要包含以下信息:
- ActiveGameplayEffects:目前激活的GE
- ActivatableAbilities:可激活的GA
- AbilityScopeLockCount:用来判断是否在当前作用域被锁定,以延迟一些行为的发生,GA中也有类似的处理
- AbilityPendingAdds:GASpec数组,用来延迟处理增加GA
- AbilityPendingRemoves:GASpecHandle数组,用来延迟处理移除GA
在对他们进行遍历时,要加上宏ABILITYLIST_SCOPE_LOCK
以确保遍历的元素在当前作用域不会被删除,它利用的是本地声明一个FScopedAbilityListLock
类型的变量,它在构造和析构中控制了ASC锁定的计数。GiveAbility
和ClearAbility
会检查这个计数,只有当计数归零的时候才会去真正执行,下面会详细说明。
GiveAbility
:传入GASpec,必须在有Authority的Owner上调用- 检查锁定计数是否大于0
- 是:将传入的spec放进
AbilityPendingAdds
并返回 - 否:加作用域锁,将传入的spec加入
ActivatableAbilities
数组中
- 是:将传入的spec放进
- 根据实例化policy,决定是否需要实例化一个GA,然后根据同步policy决定加入同步列表
NonReplicatedInstances
或ReplicatedInstances
,掉用OnGiveAbility
,返回handle
- 检查锁定计数是否大于0
OnGiveAbility
:处理GA上的FAbilityTriggerData
数据,增加spec信息,调用PrimaryInstance或CDO的同名方法ClearAbility
:传入GASpecHandle,必须在Authority的Owner上调用- 遍历
AbilityPendingAdds
数组看看其中有没有传入的handle,如果有将其删除,然后直接返回 - 遍历
ActivatableAbilities
数组,通过handle找到spec - 检查
AbilityScopeLockCount
是否大于0- 是:将传入的handle放进
AbilityPendingRemoves
并返回 - 否:加作用域锁,调用
OnRemoveAbility
,从ActivatableAbilities
移除这个handle,更新关联的信息
- 是:将传入的handle放进
- 遍历
OnRemoveAbility
:清除技能上触发事件的有关信息,删除所有生成的GA实例(如果技能处于激活状态,则先调用EndAbility
并注册删除事件),调用PrimaryInstance或CDO的同名方法
在作用域锁被析构时会调用ASC的DecrementAbilityListLock
方法,除了会减少AbilityScopeLockCount
外,还会检测其是否到了0,如果到0了则将两个pending列表中的数据拿出来处理。
TryActivateAbility
:调用GA的CanActivateAbility
,以及各种检查,最后CommitAbility
GameplayTag
这是UE自带的功能,GAS大量使用了tag来实现互斥、加状态、条件判断等。它的特点是用字符串表示的多层级的结构,这样的好处是查找起来可以按层级索引,而不用每个具体的标签都去遍历,而且字符串处理起来也非常简单方便。
- 通常角色身上会有不只一个标签,因此会用
FGameplayTagContainer
来储存各种FGameplayTag
。 RegisterGameplayTagEvent
是ASC中监听某个Tag的方法,它接收一个FGameplayTag
和一个枚举,判断是否只在Tag新增或删除的时候才通知。在ASC中使用FGameplayTagCountContainer
结构储存了现有的Tag信息,以及更新的接口。UAsyncTaskGameplayTagAddedRemoved
中又封装了一层蓝图接口,实现了Tag增加和删除可以注册的操作。-
FGameplayTag
中提供了比较各种情况tag的接口,如完全匹配、部分匹配、container中匹配等 RequestGameplayTag
:根据名字得到Tag
GameplayAttribute
角色的各种属性,血量、体力、魔法之类,一般只通过GE去控制,其中维护两个值BaseValue
和CurrentValue
,主要是为了方便在上buff之后恢复原先的值。Instant类型的GE改变BaseValue
,Duration和Infinite类型改变的都是CurrentValue
。
多个属性可以组成属性集AttributeSet,它提供了一些宏可以直接为每个属性生成Setter、Getter以及Init函数,一般来说属性集会放在OwnerActor中。
AttributeSet
属性集从基类UAttributeSet
上继承,基类中提供了获取ASC的接口
GameplayEffect(GE)
GE是纯配置蓝图,所有相关逻辑都通过配置来完成
技能产生的效果,如伤害、buff、增益、状态。通常用来修改属性。比如连击时,NPC被击杀后给玩家上一个加属性的GE,这个GE将玩家身上的“连击数”这一属性增加若干值,然后使用了ListenForAttributeChange
的地方就能监听到该属性的改变。
常用的类:
FGameplayEffectContext
:包含激活该GE的GA,释放该GE的Actor(Instigator)等FGameplayEffectContextHandle
:包含Context的结构,用来支持多态和网络复制,用MakeEffectContext
生成FGameplayEffectSpec
:包含GE实例(Def),Context,等级,持续时间等,真正上GE的时候用的是这个-
FGameplayEffectSpecHandle
:包含Spec,可以用MakeOutgoingSpec
生成,它会生成一个Context(或由外部传入),然后和GE类型的CDO生成一个Spec,最后用Spec生成handle返回 FActiveGameplayEffect
:上GE成功后生成的类,包含handle、GESpec,以及相同类型的Next指针FActiveGameplayEffectHandle
:储存了一个int32成员Handle
作为uid,用于在一个全局Map中索引对应的ASCFActiveGameplayEffectsContainer
:包含FActiveGameplayEffect
指针作为链表头节点
常用接口:
SetByCaller
:一种设置值的方式,代表该值由生成此GE的地方决定ExecutionCalculation
:可以在GE执行后做一些复杂的属性运算MakeOutgoingGameplayEffectSpec
:根据GE类型创建SpecHandle
要给角色上GE,可以用下面的代码:
// Can run on Server and Client
FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
EffectContext.AddSourceObject(this);
FGameplayEffectSpecHandle NewHandle = AbilitySystemComponent->MakeOutgoingSpec(DefaultAttributes, GetCharacterLevel(), EffectContext);
if (NewHandle.IsValid())
{
FActiveGameplayEffectHandle ActiveGEHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToTarget(*NewHandle.Data.Get(), AbilitySystemComponent.Get());
}
可以看到在上GE前需要准备如下数据:
- 创建一个EffectContext的handle
- 用GE的类创建一个EffectSpec
最后使用ASC的方法
ApplyGameplayEffectSpecToTarget
,它最终会调用ApplyGameplayEffectSpecToSelf
上面是通过ASC中的接口去上,还可以通过技能去上:
GE激活流程
ASC上GE和技能上GE有什么区别 GE在哪里改的属性 属性属于谁 怎么取消GE
MMC
ExecutionCalculation
GameplayAbility(GA)
GA是技能的核心逻辑,任何行为都可以是GA,除了常见的人物主动释放的技能,还可以是交互、格挡甚至受击,但要注意基础的移动和UI上的交互不宜使用GA,GA通常用来触发某种特定条件下的行为,而不是平时一直会做的事情。
以下列举会使用到的类:
FGameplayAbilityActorInfo
:包含该GA的OwnerActor、表现上的AvatarActor等和使用该GA的Actor有关的信息FGameplayAbilityActivationInfo
:激活状态,是否是Authority,以及预测情况FGameplayAbilitySpec
:除了等级和handle还包含以下信息- GA的实例,如果是从类初始化则获取其CDO
- SourceObject:谁创建的这个GA
- 赋予这个GA的GE的handle(
FActiveGameplayEffectHandle
) - InputId:用来识别输入
- ActivationInfo:包含激活模式和一个
FPredictionKey
,做预测的时候会用到
FGameplayAbilitySpecHandle
:spec在构造时做的第一件事就是生成一个handle,区分ASC中特定的GA,全局唯一FGameplayAbilitySpecDef
:包含被GE赋予时所需要的信息,可以用来生成Spec,其中包含GA的类
常用的接口:
CanActivateAbility
:检查技能释放条件,流程如下- 检查Avatar、网络情况(排除Role为Simulated的Avatar)和ASC
- 检查CostGE中的Modifier是否在Apply之后属性值仍都大于0
- 检查cd,看看ASC上有没有该技能的cd标签
- 检查标签是否满足条件
- 检查inputid是否被屏蔽
ActivateAbility
:CommitAbility
:技能真正释放,调用CommitExecute
,上冷却GE和消耗GEEndAbility
:CancelAbility
:GetPrimaryInstance
:只有当实例化策略为InstancedPerActor时才会尝试从同步列表中获取GA的指针SendGameplayEvent
:调用ASC的HandleGameplayEvent
处理响应事件,先用tag找到ASC上绑定的GASpec,然后根据GA实现的ShouldAbilityRespondToEvent
接口返回值,决定是否激活GA
FGameplayAbilityTargetDataHandle FGameplayAbilityTargetData
技能释放流程
要释放一个技能,先得有这个技能,调用GiveAbility
赋予玩家(或其他角色)
- 接收输入或事件(有时候释放技能是被动的)
- 检测释放条件
- 创建释放所需的类,拿到数据
- 激活技能逻辑
- 根据需要结束技能
如果要释放一个技能,首先要Give,这样GA(的Spec)才会被加入到ActivatableAbilities
的数组中,释放的时候一般传入GA类型,然后根据CDO在数组中找到对应的GA,调用ActivateAbility
。
怎么上的GE,如果技能结束了,之前上的GE怎么处理? 根据RemovalPolicy的配置决定
GameplayCue
技能的视觉特效,控制技能特效的播放和停止
GameplayTask
执行异步任务的框架
UAbilityTask_WaitGameplayTag