在UE里如何激活一个Ability

发布时间 2023-07-22 15:48:28作者: Osteichthyes

前言

Ability是GAS系统里的技能,本文主要学习UE是如何激活一个技能,主要包括以下几个知识点

  1. 怎么激活一个技能?
  2. 在客户端和服务器都怎么激活?
  3. 激活的条件是什么?
  4. 如果激活失败了怎么处理?
  5. 如果激活需要带有数据怎么办?

赋予GA

要激活一个GameplayAbility,需要首先GiveAbility并且只能在服务器调用,如果Give失败是无法激活的,因为激活需要一个FGameplayAbilitySpec类型变量,这个变量是Give函数中创建。

第一种激活方式

使用Try的激活方式。激活GA可以调用以下三个函数:

  1. TryActivateAbilitiesByTag
  2. TryActivateAbilityByClass
  3. TryActivateAbility。
    前两个是第三个函数的包装,主要负责从ActivatableAbilities找到FGameplayAbilitySpec,再由TryActivateAbility来激活。
    在函数TryActivateAbilitiesByTag中是通过Tag容器来激活技能,查找技能时,需要满足条件:Ability->AbilityTags是否拥有所有(HasAll)Tag容器所有Tag。查找函数还有一个参数bOnlyAbilitiesThatSatisfyTagRequirements,默认是True,如果是False的情况会进一步检查一下是否Block掉,如果没有Block(或不检查情况下),把所有满足条件的技能都返回。
    在函数TryActivateAbilityByClass中,通过Ability的CDO对象来查找,每一个FGameplayAbilitySpec都拥有一个Ability的CDO对象指针,查找时直接判断CDO是否相同,这个函数只能激活一个技能

第二种激活方式

使用SendGameplayEventToActor,内部是调用ASC组件的HandleGameplayEvent,Send函数可以带有参数。函数声明如下:

HandleGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload)

这个函数可能也会激活多个,在匹配EventTag时,使用的向上递归的方法,示例:当EventTag是A.B.C.D时,会同时尝试激活

  1. A.B.C.D
  2. A.B.C
  3. A.B
  4. A
    激活的查找是ASC组件的GameplayEventTriggeredAbilities,这个属性又是在Give时通过AbilityTriggers配置上去的

激活的过程

使用Try和Send的激活过程并不一样:

使用Try的方式激活前提条件:

  1. 确保FGameplayAbilitySpec存在并有效
  2. 如果标记了PendingRemove,激活失败
  3. 如果标记了RemoveAfterActivation,激活失败
  4. Ability的CDO对象需要存在并有效
  5. ASC组件的FGameplayAbilityActorInfo存在,并且OwnerActor和AvatarActor都存在并有效
  6. 如果是模拟端调用(判断LocalRole),激活失败

根据NetExecutionPolicy和实际调用地方分情况处理,

如果服务器,需要满足下面两个条件:

  1. NetExecutionPolicy是LocalOnly或LocalPredicted的情况下
  2. bAllowRemoteActivation是True的情况下

会调用RPC函数ClientTryActivateAbility进行激活

如果是在客户端,需要满足三个条件:

  1. NetExecutionPolicy需要是ServerOnly或ServerInitiated
  2. bAllowRemoteActivation是True的情况下
  3. CanActivateAbility调用成功的情况下

会调用RPC函数CallServerTryActivateAbility,让服务器去激活。

其它情况下,都会调用InternalTryActivateAbility函数激活。总结表格如下所示:

NetExecutionPolicy LocalOnly LocalPredicted ServerOnly ServerInitiated
客户端 继续激活 继续激活 检查客户端条件 检查客户端条件
服务器 检查服务器条件 检查服务器条件 继续激活 继续激活

为什么客户端检查是否能激活服务器的执行时,会多检查一个条件呢?

bAllowRemoteActivation是在TryActivateAbility的参数,默认是True,它的意义在于:?

而Send的激活过程

Send的激活过程如下:

  1. 确保FGameplayAbilitySpec存在并有效
  2. 确保Ability存在
  3. 确保Payload有效
  4. 检查HasNetworkAuthorityToActivateTriggeredAbility函数返回
  5. 调用ShouldAbilityRespondToEvent检查

在第2步,判断Ability是否有效时,优先使用Spec->GetPrimaryInstance,再使用Spec->Ability。这里又和Give的流程联系在一起,如果一个Ability的InstancingPolicy是InstancedPerActor,则在Give已经就已经创建好了,所以此时可以直接使用实例,其它情况下都是使用Spec->Ability,为什么这样做,目前原因可能是蓝图里可以重写ShouldAbilityRespondToEvent,如果使用实例可以动态修改返回结果,如果使用CDO对象,结果就只有一种情况了。

函数HasNetworkAuthorityToActivateTriggeredAbility和Try的激活判断类似,但是少了一个bAllowRemoteActivation的判断。而ShouldAbilityRespondToEvent就是调用蓝图的ShouldAbilityRespondToEvent函数

两者的区别:

  1. Send类型可以带有参数,而Try函数没有参数接口
  2. Send不能远程激活,而Try可以在服务器激活客户端的技能,也可以在客户端激活服务器的技能
  3. 配置Tag的地方不同
  4. Send可以在蓝图里重载ShouldAbilityRespondToEvent
  5. Send激活技能时使用递归迭代,而Try可以根据Class来指定具体一个,也可以使用Tag容器指定多个(不相关Tag)

InternalTryActivateAbility函数的流程

不管是Send还是Try,都是在调用成功后,还再次需要调用:InternalTryActivateAbility函数,函数的流程如下:

  1. 确保FGameplayAbilitySpec存在并有效
  2. ASC组件的FGameplayAbilityActorInfo存在并有效,并且OwnerActor和AvatarActor都存在并有效
  3. 如果是模拟端调用(判断LocalRole),激活失败
  4. 检查NetExecutionPolicy的配置是否正确,如配置了LocalOnly,就不应该在服务器调用InternalTryActivateAbility
  5. 调用Ability的CanActivateAbility判断,也是优先使用GetPrimaryInstance,再使用Spec->Ability
  6. 检查InstancingPolicy为InstancedPerActor的情况是否可以激活:
    1. 如果已经激活了,根据bRetriggerInstancedAbility判断是否可以重新激活
    2. 判断Ability的实例是否存在

至此,激活的判断条件执行完成