Mapz's Blog

可以递归的函数指针

UE4:UGameplayAbility 类型简析

作为 GAS 系统中的核心元素之一 GameplayAbility 代表一个可大可小的 “能力”

这个能力能干啥,基本全靠你自己定义的边界,大到一个复杂的技能,小到一个动作,都可以作为一个“能力”

可以说 GA 的目的是基于一系列的行为控制来生成 GameplayEffect 改变 GameplayAttribue, 创建 GameplayCue 来处理显示和声音效果

这次我们基于 UE 版本 4.26 来简析一下 UGameplayAbility 类

基本信息

类型继承 UObject,并实现 IGameplayTaskOwnerInterface 接口

实现 IGameplayTaskOwnerInterface 的目的是为了可以在其中执行异步的 UGameplayTask

而 GA 使用的 GameplayTasksComponent 是 GA 的当前释放者的 AbilitySystemComponent (UAbilitySystemComponent 也是继承自 UGameplayTasksComponent 的)

重要的函数

如源码中的注释所写的,GA 里面有几个重要的函数

CanActivateAbility

函数定义

1
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const;

函数功能是检查是否可以激活此 Ability

GAS 中在 UAbilitySystemComponent::TryActivateAbility 中调用

  • Handle 参数:AbilitySpcHandle,这个代表一个 Ability 的实例和数据载体的 Handle
  • ActorInfo 参数:当前技能的释放者信息,类型 FGameplayAbilityActorInfo 包括技能所有者,技能 Avatar Actor (代表物理上释放技能的 Actor,可以没有)和 PC ,骨架 Mesh ,是否本地 Actor 等信息
  • SourceTags:表示释放者拥有的 Tags
  • TargetTags:表示目标拥有的 Tags
  • OptionalRelevantTags:用来存放一些额外的信息,本体里面用来放技能释放失败的原因 Tags

函数默认流程

函数是 virtual 的,你可以自己重写

默认流程如下

  1. 判断是否符合执行策略(只能在服务器执行,只能在客户端发起之类的)
  2. 判断 ActorInfo 中是否含有 AbilitySystemComponent ,没有则不可以释放
  3. 判断用户是否禁止释放技能(AbilitySystemComponent.UserAbilityActivationInhibited,例如菜单状态下不能释放之类的)
  4. 判断技能冷却是否完成

    冷却是由 TSubclassOf CooldownGameplayEffectClass 来控制的,在技能成功被 commit 的时候,会触发生成这个 GE,判断是否冷却了就是判断是否有这个 GE

  5. 判断技能消费是否够

    同样的消费是由 TSubclassOf CostGameplayEffectClass 来控制的,这个 GE 是懒加载的,这里会调用 AbilitySystemComponent->CanApplyAttributeModifiers 来检查是否有资源释放技能

  6. 检查当前 Tags 是否满足释放条件,包括需要的 Tag,Block 的 Tag 等
  7. 获得技能实例的技能信息(FGameplayAbilitySpec)
  8. 如果绑定了输入,检查是否为不允许输入的技能(AbilitySystemComponent->IsAbilityInputBlocked)
  9. 检查蓝图覆盖的 K2_CanActivateAbility 是否可以释放

CallActivateAbility

函数定义

1
void CallActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate = nullptr, const FGameplayEventData* TriggerEventData = nullptr);

函数用来激活 Ability

参数除了上面的以外还有

  • ActivationInfo:FGameplayAbilityActivationInfo 类型,存储一些技能的激活信息,包括 PK 和 ActivationMode (激活的模式, Authority 或者 Client 等)
  • OnGameplayAbilityEndedDelegate: Ability Ended 回调
  • TriggerEventData:FGameplayEventData 类型,包括技能发起者,目标,目标数据,增幅,Tag容器等,还可以传入两个 Custom 的 UObject 用来传递数据

函数主要流程

  1. 执行 PreActivate(Virtual 可覆盖)
    a. 刷新对齐 ServerMovement
    b. 如果是实例化的 Activity,设置 bIsActive 为激活状态,设置为 bIsBlockingOtherAbilities 为 True
    c. 设置 Activity 的当前 ActorInfo 和 ActivationInfo
    d. 加入需要激活的 GameplayTags
    e. 加入 AbilityEnded 回调
    f. 发送 AbilityActivatedCallbacks 事件
    g. 更新 Ability 的 Blocking 和 Cancel Tags
  2. 执行 ActivateAbility(Virtual 可覆盖)
    a. 如果有蓝图实现,则使用蓝图实现 K2_ActivateAbility 激活任务
    b. 如果有蓝图的 TriggerEvent 实现,则执行 K2_ActivateAbilityFromEvent(*TriggerEventData) 激活任务,灭有 TriggerData 则 End Ability
    c. 如果没有蓝图实现,则直接 Commit Ability

CommitAbility

函数定义

1
virtual bool CommitAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr);

函数是确认 Ability

默认流程

  1. 调用 CommitCheck 检查是否可以 Commit (和 CanActivateAbility 差不多,也可以做额外的检查)
  2. 设置冷却,执行消耗资源 (ApplyCooldown 和 ApplyCost)
  3. 执行蓝图 K2_CommitExecute
  4. 发送 AbilityCommittedCallbacks 广播

CancelAbility

函数定义

1
virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility);

函数用来取消 Ability

默认流程

  1. 检查 Ability 是否可以被 Cancel (如果是非实例化的,则可以被 Cancel,如果是实例化的,检查是否可以被 Cancel 的设置)
  2. 检查当前的 Ability 是否正在 ApplyGameplayEffectSpecToTarget (ScopeLockCount),如果正在被操作,加入 WaitingToExecute 中,并会在执行完成后,执行本次 Cancel 操作
  3. 如果需要,执行 ReplicateEndOrCancelAbility 执行客户端或者服务器的 CancelAbility 或者 EndAbility 的 RPC
  4. 广播 OnGameplayAbilityCancelled 事件
  5. 执行 EndAbility

EndAbility

函数定义

1
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled);

函数用来结束 Ability

默认流程

  1. 检查 Ability 是否可以被 End
    a. 不是 Active 的实例化 Acitivity 不可以被 End
    b. 检查 Handle 的 ActivitySpec 是否 Active
  2. 查当前的 Ability 是否正在 ApplyGameplayEffectSpecToTarget (ScopeLockCount),如果正在被操作,加入 WaitingToExecute 中,并会在执行完成后,执行本次 EndAbility 操作
  3. 执行蓝图 K2_OnEndAbility
  4. 清除这个 Ability 在 World 中的所有 timer 和 latent actions
  5. 广播 OnGameplayAbilityEnded 事件,并移除回调
  6. 广播 OnGameplayAbilityEndedWithData 事件,并移除回调
  7. 对于实例化 Ability 设置 bIsActive 为非激活
  8. 结束所有挂在 Ability 上的 GameplayTask,并 Reset 列表来移除内存占用
  9. 如果需要执行 ReplicateEndOrCancelAbility
  10. 移除 ActivationTags
  11. 移除技能添加的 GameplayCue
  12. 移除 AbilityComponent 上的 Blocking 和 Cancel Tags
  13. 移除 PK 缓存
  14. 移除 AbilityComponent 上的 AnimatingAbility
  15. 播出 AbilityComponent 上的 AbilityEndedCallbacks , OnAbilityEnded 事件
  16. 需要同步的 Ability ,移除 Spec 的 ReplicatedInstance(服务器)
  17. 不需要同步的 Ability ,移除 Spec 的 NonReplicatedInstances
  18. 服务器上如果没有活动的 Spec 则,且设置为在 Activation 后就从 Component 中移除 Ability 则 ClearAbility,否则 MarkAbilitySpecDirty

这里的 ClearAbility,和 GiveAbility 对应,是从 AbilityComponent 中移除这个 Ability 的 Handle 而 MarkAbilitySpecDirty 是如字面意思表示 AbilitySpec 已经被修改过了

配置项

  • ReplicationPolicy:同步策略
  • InstancingPolicy:实例化策略,可以设置为非实例化,每个 Actor 实例化,或者每次执行实例化,非实例化的 Ability 只是用其 CDO 来做逻辑
  • bServerRespectsRemoteAbilityCancellation : 是否可以从客户端来 Cancel 服务器上的 Ability
  • bRetriggerInstancedAbility:是否在激活一个实例化的 Ability 的时候, 先 End 再重新激活
  • NetExecutionPolicy:网络同步策略,包括本地预测,服务器 only 等
  • NetSecurityPolicy:网络安全政策,设置 Ability 的执行权限
  • CostGameplayEffectClass:消费的 GE
  • AbilityTriggers:可以激活 Ability 的 Tags
  • CooldownGameplayEffectClass:冷却的 GE
  • CancelAbilitiesWithTag:执行的时候,会 Cancel 含有这些 Tag 的其他 Ability
  • BlockAbilitiesWithTag:执行的时候,会 Block 含有这些 Tag 的其他 Ability
  • ActivationOwnedTags:激活的时候附加到 Owner 的 Tags
  • ActivationRequiredTags:激活的时候需要的 Tags
  • ActivationBlockedTags:激活的时候,如果有这些 Tags 会阻止激活
  • SourceRequiredTags:Source Actor 有这些 Tags 的时候才能激活
  • SourceBlockedTags:Source Actor 有这些 Tags 的时候不能激活
  • TargetRequiredTags:Target Actor 有这些 Tags 的时候才能激活
  • TargetBlockedTags:Target Actor 有这些 Tags 的时候不能激活
  • bReplicateInputDirectly: 是否直接把输入事件传到服务器
  • AbilityTags:拥有的 Tags,会同其他的 Ability 的 CancelAbilitiesWithTag 和 CancelAbilitiesWithTag 协同作用

小结

  1. 非实例化的 Ability ,总是可以被 Cancel,也总是会 Block 其他的 Ability,而不需要设置
  2. 对于每个实例会不同的 Ability,需要设置为实例化,否则不要设置为实例化,以节省资源,Ability 所需的实例数据事实上存在 AbilitySpec 中