前言 在第一篇初始化 的时候简单讲解了技能的添加和使用, 但是没有对技能GameplayAbility(GA)做详细介绍,本片开始对GA中的主要功能注意剖析
注册/移除技能 之前我们通过配置的方式自动注册起始技能, 那么我们肯定需要动态的增加或者删除技能, 为了方便使用, 封装蓝图库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UFUNCTION (BlueprintCallable, Server, Reliable, WithValidation, Category = "SR|ASC" ) void RPC_Ser_AddAbility (TSubclassOf<USRGameplayAbilityBase> AbilityClass, int32 level = 1 ) ; UFUNCTION (BlueprintCallable, Server, Reliable, WithValidation, Category = "SR|ASC" ) void RPC_Ser_RemoveAbilityByClass (TSubclassOf<USRGameplayAbilityBase> AbilityClass) ; UFUNCTION (BlueprintCallable, Server, Reliable, WithValidation, Category = "SR|ASC" ) void RPC_Ser_RemoveAbilityByName (const FString& name) ; UFUNCTION (BlueprintCallable, Category = "SR|ASC" ) bool AddNewAbility (TSubclassOf<USRGameplayAbilityBase> AbilityClass, int32 level = 1 ) ; UFUNCTION (BlueprintCallable, Category = "SR|ASC" ) bool RemoveAbilityByClass (TSubclassOf<USRGameplayAbilityBase> AbilityClass) ; UFUNCTION (BlueprintCallable, Category = "SR|ASC" ) bool RemoveAbilityByName (const FString& name) ;
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 28 29 30 31 32 33 34 35 36 37 bool USRAbilitySystemComponent::AddNewAbility (TSubclassOf<USRGameplayAbilityBase> AbilityClass, int32 level) { if (AbilitySpecMap.Num ()>0 ) { for (TPair<FString, FGameplayAbilitySpec*> p : AbilitySpecMap) { if (p.Value->Ability->GetClass () == AbilityClass) { UE_LOG (SRLog, Warning, TEXT ("USRAbilitySystemComponent::AddNewAbility Failed!! Has Repeat Ability" )); return false ; } } } FGameplayAbilitySpec spec = FGameplayAbilitySpec (AbilityClass, level, static_cast <int32>(AbilityClass.GetDefaultObject ()->InputID), GetOwnerActor ()); GiveAbility (spec); AbilitySpecMap.Add (AbilityClass.GetDefaultObject ()->AbilityName, this ->FindAbilitySpecFromClass (AbilityClass)); return true ; } bool USRAbilitySystemComponent::RemoveAbilityByClass (TSubclassOf<USRGameplayAbilityBase> AbilityClass) { FGameplayAbilitySpec* spec = FindAbilitySpecFromClass (AbilityClass); if (spec) { this ->ClearAbility (spec->Handle); for (TPair<FString, FGameplayAbilitySpec*> p : AbilitySpecMap) { if (p.Value->Ability->GetClass () == AbilityClass) { AbilitySpecMap.Remove (p.Key); } } return true ; } UE_LOG (SRLog, Warning, TEXT ("USRAbilitySystemComponent::RemoveAbilityByClass Failed!! Do not has this ability" )); return false ; }
这个Name版本是我们在GA类中自定义的, 通过TMap<FString, FGameplayAbilitySpec*> AbilitySpecMap保存在我们ASC中
触发技能 如果是已经用之前按键绑定的技能, 那么按键就会直接触发技能
还有其他几个方法来手动触发技能
1 2 3 4 5 6 7 8 9 10 11 UFUNCTION (BlueprintCallable, Category = "Abilities" )bool TryActivateAbilitiesByTag (const FGameplayTagContainer& GameplayTagContainer, bool bAllowRemoteActivation = true ) ;UFUNCTION (BlueprintCallable, Category = "Abilities" )bool TryActivateAbilityByClass (TSubclassOf<UGameplayAbility> InAbilityToActivate, bool bAllowRemoteActivation = true ) ;bool TryActivateAbility (FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation = true ) ;bool TriggerAbilityFromGameplayEvent (FGameplayAbilitySpecHandle AbilityToTrigger, FGameplayAbilityActorInfo* ActorInfo, FGameplayTag Tag, const FGameplayEventData* Payload, UAbilitySystemComponent& Component) ;FGameplayAbilitySpecHandle GiveAbilityAndActivateOnce (const FGameplayAbilitySpec& AbilitySpec) ;
到蓝图中就是如下
激活技能的不同方法决定GA中的函数调用, 一般都是执行到ActiveAbility,
如果通过事件的方式触发技能, 即上述中的TriggerAbilityFromGameplayEvent或者蓝图中的SendGameplayEventToActor,那么可以提供一个Payload参数作为扩展参数,这个非常有用
那么在GA中可以重写函数ActivateAbilityFromEvent,前提是不要重写默认的ActivateAbility函数;
通过事件触发的方式还会与后续的tag有关系, 这个后续再讲
技能逻辑没有固定规则, 完全可以自己脑洞, 不过千万不要忘记在技能完成以后调用EndAbility来结束技能,否则技能一直结束不了而类似一个被动技能一直存在
被动技能 被动技能如果是默认就存在(触发),可以在GA中的OnAvatarSet事件中最判断处理,如果有必要就直接调用TryActivateAbility(我们之前已经申明了一个布尔变量bAutoActive), 然后不要调用EndAbility就可以了
1 2 3 4 5 6 7 8 void UVGGameplayAbility::OnAvatarSet (const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) { Super::OnAvatarSet (ActorInfo, Spec); if (bAutoActivate) { ActorInfo->AbilitySystemComponent->TryActivateAbility (Spec.Handle, false ); } }
关闭/中断技能 目前在ASC中有如下几个方法关闭技能,都未暴露给蓝图;
1 2 3 4 5 6 7 void CancelAbility (UGameplayAbility* Ability) ; void CancelAbilityHandle (const FGameplayAbilitySpecHandle& AbilityHandle) ;void CancelAbilities (const FGameplayTagContainer* WithTags=nullptr , const FGameplayTagContainer* WithoutTags=nullptr , UGameplayAbility* Ignore=nullptr ) ;void CancelAllAbilities (UGameplayAbility* Ignore=nullptr ) ;
在蓝图中只能在GA中自己调用CancelAbility关闭技能
不过我们可以自己封装蓝图函数库,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void UFlib_GAS::CancelAbilityWithTag (UAbilitySystemComponent* AbilityComponent, const FGameplayTagContainer& WithTags, const FGameplayTagContainer& WithoutTags, UGameplayAbility* Ignore) { if (!AbilityComponent) { return ; } AbilityComponent->CancelAbilities (&WithTags, &WithoutTags, Ignore); } void UFlib_GAS::CancelAllAbilities (UAbilitySystemComponent* AbilityComponent, UGameplayAbility* Ignore ) { if (!AbilityComponent) { return ; } AbilityComponent->CancelAllAbilities (Ignore); }
GASDocumentation 项目文档说 CancelAllAbilities有时候无法正常生效,但是我这边实测可以正常关闭技能
获取激活的技能 可以通过方法 void GetActivatableGameplayAbilitySpecsByAllMatchingTags(const FGameplayTagContainer& GameplayTagContainer, TArray < struct FGameplayAbilitySpec* >& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements = true) const;根据tag获取正在运行的技能
照样可以封装一个蓝图函数库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 UFUNCTION (BlueprintCallable, BlueprintPure, Category = "VGAS | FunctionLib" , meta = (AutoCreateRefTerm = "GameplayTagContainer, WithTags" )) static void GetActivatableGameplayAbilitySpecsByAllMatchingTags (UAbilitySystemComponent* AbilityComponent, const FGameplayTagContainer& GameplayTagContainer, TArray < struct FGameplayAbilitySpec >& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements) ; void UFlib_VGAS::GetActivatableGameplayAbilitySpecsByAllMatchingTags (UAbilitySystemComponent* AbilityComponent, const FGameplayTagContainer& GameplayTagContainer, TArray < struct FGameplayAbilitySpec >& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements) { if (!AbilityComponent) { return ; } TArray <FGameplayAbilitySpec* > returnAbilities; AbilityComponent->GetActivatableGameplayAbilitySpecsByAllMatchingTags (GameplayTagContainer, returnAbilities, bOnlyAbilitiesThatSatisfyTagRequirements); for (FGameplayAbilitySpec* i : returnAbilities) { MatchingGameplayAbilities.Add (*i); } }
这里还是因为蓝图的原因,传参不能按照原版本传参,需要一个临时变量returnAbilities小小的加工一下
实例化模式 技能的实例化模式分为3种,见下表
Instancing Policy
Description
Example of when to use
Instanced Per Actor
每个ASC只会生成一个实例,每次触发GA时服用实例对象
这个是最常用的方式
Instanced Per Execution
每次GA激活都会产生一个实例
这个方式不太常用,但是可以每个技能都独立作用
Non-Instanced
GA自己默认Obj来维护自身,不会生成实例
这是三种方法中性能最好的,但是在使用的时候是最受限制的。非实例化的GameplayAbilities不能存储状态,这意味着没有动态变量,也没有绑定到AbilityTask委托。在MOBA或RTS中,最适合使用它们的是那些经常使用的简单技能,比如仆从基本攻击或者角色的Jump
网络方案
Net Execution Policy
Description
Local Only
只运行在本地客户端,这个比较适合在只表现本地效果的技能,单人游戏中需要使用Server Only
Local Predicted
先本地运行, 服务端会校准本地客户端错误的内容
Server Only
只在服务端运行, 本地技能适合运行在服务端,单人游戏也适合用此
Server Initiated
服务端先运行然后再运行在客户端,不常用
标签 GA的标签非常重要而且使用,包含如下标签
GameplayTag Container
Description
Ability Tags
当前技能拥有的标签, 也是通过tag来激活技能的凭证
Cancel Abilities with Tag
当前技能会关闭的拥有此类tag的正在运行技能
Block Abilities with Tag
运行技能的会阻挡拥有此类tag的技能(并不会返回失败)
Activation Owned Tags
此技能会激活的tag
Activation Required Tags
激活技能所需要包含的tag
Activation Blocked Tags
当拥有此类tag的技能正在运行时,该技能会被阻挡(并不会返回失败)
Source Required Tags
此标签包括如下共4个标签内,都是通过ByEvent的方式作用才有效; 此标签在输入的时候必须包含才能运行
Source Blocked Tags
同理会被阻挡的标签
Target Required Tags
同理目标必须包含的tag
Target Blocked Tags
同理目标如果包含会被阻挡的tag
Gameplay Ability Spec Gameplay Ability Spec在GA正确激活后会创建, 内部提供了诸多GA中的数据如 class,level,input bindings等
如果GA在服务端创建, 会同步到客户端
Gameplay Ability Spec创建实例规则请参考实例化模式 ↑
GA的传递数据 GA传递外部数据有如下几种方式
Method
Description
Activate GameplayAbility by Event
通过此方法激活技能会有一个Payload参数, 可以用来传递诸多参数;如两个UObject参数更加方便扩展自定义数据
Use WaitGameplayEvent AbilityTask
用这个方法可以监听其他tag技能的状态,以此类得到payload数据
Use TargetData
用结构体 TargetData 传递数据是一个办法,具体以后补充
Store Data on the OwnerActor or AvatarActor
把数据存到GA的OwnerActor或者角色中,但是你需要确保网络同步
技能消耗/冷却 技能的消耗和冷却通过GameplayEffect实现, 详情可以查看GameplayEffect篇
技能升级
Level Up Method
Description
Ungrant and Regrant at the New Level
移除GA,重新注册一个等级不一样的GA,此方式会终止GA
Increase the GameplayAbilitySpec's Level
对spec中的level进行升级处理,这种方式不会终止GA
蓝图中无法得到GA中的Level变量, 所以无论是对GA的Level进行处理还是GE都需要在cpp中
1 2 3 4 5 6 7 8 9 void USRAbilitySystemComponent::UpgradeAbilityByName (const FString& name, int32 upLevel) { if (AbilitySpecMap.Contains (name)) { FGameplayAbilitySpec* spec = *AbilitySpecMap.Find (name); spec->Level += upLevel; this ->MarkAbilitySpecDirty (*spec); } }
因为添加的时候已经存放了TMap<FString, FGameplayAbilitySpec*> AbilitySpecMap, 直接对其进行操作即可, 按照GAS注释说明需要调用MarkAbilitySpecDirty()
网络规则
NetSecurityPolicy
Description
ClientOrServer
无要求,客户端和服务器都可以自由触发
ServerOnlyExecution
客户端请求执行将被服务器忽略。客户端仍然可以请求服务器取消或终止此功能。
ServerOnlyTermination
客户端可以请求执行, 但是不能请求终止或者取消
ServerOnly
只有服务端有权执行,客户端无法执行任何操作