【Unreal】虚幻GAS系统快速入门-CSDN博客
GameplayTags
FGameplayTags是一种层级标签,如Parent.Child.GrandChild。
通过GameplayTagManager进行注册。替代了原来的Bool,或Enum的结构,可以在玩法设计中更高效地标记对象的行为或状态。
GameplayTags是一个内置的插件,不属于GAS。
但是GAS会大量使用Tag
Gameplay Ability System
GAS主要包含以下内容:
- ASC(Ability System Component)主要组件,由C++编写,代码里有很多方法是蓝图未实现的。
- GA(Gameplay Abilities)角色的技能,包括攻击、疾跑、施法、翻滚、使用道具等,但不包括基础移动和UI。
- AS(Attribute Set)角色身上可以用float表示的属性,如生命值、体力值、魔力值等。
- GE(Gameplay Effects)用于修改属性,如增加50移动速度10s;还能配合GA实现更多玩法。
- GC(Gameplay Cues)播放特效、音效等。
Ability System Component
Ability System Component(ASC)是整个GAS的基础组件。
ASC本质上是一个UActorComponent,用于处理整个框架下的交互逻辑,包括使用技能 (GameplayAbility)、包含属性(AttributeSet)、处理各种效果(GameplayEffect)。
所有需要应用GAS的对象(Actor),都必须拥有GAS组件。
拥有ASC的Actor被称为ASC的OwnerActor,ASC实际作用的Actor叫做AvatarActor。ASC可以被赋予某个角色ASC,也可以被赋予PlayerState(可以保存死亡角色的一些数据)
简单来说,ASC是一种角色组件,负责和GA、GE、AS打交道。
如果Character需要销毁再重新生成,如MOBA游戏角色死亡后泉水复活,那么ASC可以放在PlayerState上避免随着角色一同销毁。此时的OwnerActor是PlayerState,AvatarActor则是Character。
注意事项
项目Build.cs文件的PrivateDependencyModuleNames里加上“GameplayAbilities”,“GameplayTags”,“GameplayTasks”三个模块。
组件声明
#include "AbilitySystemComponent.h"
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Abilities")
class UAbilitySystemComponent* AbilitySystemComponent;
组件实例化
//实例化ASC
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));
通过 IAbilitySystemInterface接口,并实现GetASC函数
#include "AbilitySystemInterface.h"
class ARPG_UNREAL_API ACharacterBase : public ACharacter, public IAbilitySystemInterface
public:
UAbilitySystemComponent* GetAbilitySystemComponent()const override;
UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent()const{return AbilitySystemComponent;}
Gameplay Ability
Gameplay Ability(GA)标识了游戏中一个对象(Actor)可以做的行为或技能。能力(Ability)可以是普通攻击或者吟唱技能,可以是角色被击飞倒地,还可以是使用某种道具,交互某个物件,甚至跳跃、飞行等角色行为也可以是Ability。
Ability可以被赋予对象或从对象的ASC中移除,对象同时可以激活多个GameplayAbility。*基本的移动输入、UI交互行为则不能或不建议通过GA来实现
注意事项
角色需要拥有GA后,才能使用GA。
GA的使用分为实例化和释放两个过程,前者主要是生成一个FGameplayAbilitySpec对象,并为一部分非公有(非静态)属性赋值,如当前GA的等级。后者操作的实际对象则为Spec。
可以把Spec理解为GA的实例,GE等其他类也有相似的概念。
通常来说,使用GA时不用去考虑两个过程的区别,除非你需要在实例化Spec后,手动修改一些在GA类上定义好的属性再去手动释放。在GE篇会详细介绍,用于实现技能的冷却、消耗
添加GA
1.在角色类中创建一个数组,游戏启动时自动添加数组里的GA
(注意:使用这一种方法不易控制每个GA的初始等级。)
1、在角色头文件声明数组:
public:
// 将在游戏启动时被赋予角色的Abilities数组
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Abilities")
TArray<TSubclassOf<class UGameplayAbility>> PreloadedAbilities;
2.在角色的BeginPlay()里遍历数组,使用AbilitySystemComponent->GiveAbility()添加Ability:
Super::BeginPlay();
if (AbilitySystemComponent != nullptr)
{
//初始化技能
if (PreloadedAbilities.Num() > 0)
{
for (auto i = 0; i < PreloadedAbilities.Num(); i++)
{
if (PreloadedAbilities[i] != nullptr)
{
// FGameplayAbilitySpec是GA的实例,其构造函数的第二个参数代表GA的等级,这里暂令其全部为1
AbilitySystemComponent->GiveAbility(
FGameplayAbilitySpec(PreloadedAbilities[i].GetDefaultObject(), 1));
}
}
}
//初始化ASC
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
3.在角色蓝图的Details面板找到数组,填好GA。
注意事项:
AbilitySystemComponent->GiveAbility()方法在蓝图中无法使用
蓝图中动态添加Ability,我们需要在蓝图中实现自己的GiveAbility()
.h
public:
//添加Ability
UFUNCTION(BlueprintCallable, Category = "Ability System")
void GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level = 1);
.cpp
void ACharacterBase::GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level)
{
if (AbilitySystemComponent)
{
if (HasAuthority() && Ability)
{
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, Level));
}
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
使用GE添加GA
新建一个GE,在Granted Abilities条目里添加的GA都会在GE被Apply到角色身上时赋予(Grant)。
然后在蓝图里调用Apply GameplayEffect to Self节点即可(这里的Level是GE的等级,不是GA)。
制作GA
在内容浏览器右键,创建Gameplay→Gameplay技能蓝图:
继承自GameplayAbility:
最下面两个是内置的GA,上面的是自制的GA
一般GA要做的事有:
- 设置GA的Tag、CD、Cost等属性。
- 获取必要信息,主要通过Get Actor Info。如果是通过Event调用的GA(使用Activate Ability From Event节点作为输入),还可以通过Gameplay Event Data获取。
- 编写逻辑,如播放动画、应用GE、应用冲量等。
- 一定不要忘了EndAbility。
调用GA
主动调用
在蓝图中主要有by Class和by Tag两种调用方法。
byClass一次只能Activate一个GA,byTag可以Activate任意多个GA,配合Tag容器使用。
如果使用EnhancedInputAction插件来管理输入,要注意在某些设置下Trigger会每帧都进行输出
只要能获取ASC,就可以在任何地方调用GA,比如行为树Task蓝图,甚至在GA蓝图中调用其他GA。
被动调用
Trigger可以理解为一个Tag,当ASC组件收到一个Trigger时,就会自动调用所有拥有该Trigger的GA。
Trigger的Tag在GA的Details面板中设置。
Trigger的触发方式有三种,分别是:
- Gameplay Event:当Owner收到一个带有Tag的Gameplay Event(不是Gameplay Effect的GE!)时调用一次GA,此时Owner不会拥有对应的Tag。
- Owner Tag Added:当Owner获取对应Tag的时候调用一次GA。
- Owner Tag Present:当Owner拥有Tag时调用GA,失去Tag时移除。
一般使用第一种方法,并配合SendGameplayEventToActor节点使用,如下图所示。(这张图是很久以前截的,Tag建议以Event开头。)
受击效果的例子,发送一个Tag为Hit的Event给碰撞检测到的Actor
使用Gameplay Event调用的好处是,可以传入数据(Payload),是除了Get Actor Info外的另一种信息传递方法。
此时应该删除ActiveAbility节点,转而使用ActivateAbilityFromEvent事件。(不要通过在左上角重载函数的方式,右键空白处搜索才是对的。)
设置GA触发条件
GA的标签
可以限制各种技能的相互关系,比如受击时候不能翻滚。
这时候Tag的父子层级关系设计就尤为重要,可以把受击时不能释放的技能都放在同一个父层级下。
Tag建议以Ability开头。
Ability Tags:该GA的标签。
Cancel Abilities with Tag:激活该GA时,打断其他拥有所选标签的GA。
Block Abilities with Tag:激活该GA时,阻止激活拥有所选标签的GA(已经激活的不会被中断)。
Activation Owned Tags:激活该GA时,赋予ASC所选GA。
Activation Required Tags:激活GA时,ASC需要的标签。
Activation Blocked Tags:激活GA时,ASC不能有的标签。
Source Required Tags:激活GA时,Source需要的标签。
Source Blocked Tags:激活GA时,Source不能有的标签。
Target Required Tags:激活GA时,Target需要的标签。
Target Blocked Tags:激活GA时,Target不能有的标签。
冷却与消耗
在GA的Details面板的Cost和Cooldown条目中选择对应的GE即可。
一个Cooldown GE仅需满足以下要求:
- 为Has Duration类型,Duration Magnitude计算方式为Set By Caller或Custom Calculation Class。
- Granted Tags为技能的冷却Tag,如Cooldown.skill1。
在Cooldown GE持续期间,玩家的ASC组件就会携带对应技能的Cooldown Tag,本质是通过Tag来限制的。
*冷却Tag建议以Cooldown开头统一管理。
一个Cost GE仅需满足以下要求:
- 为Instant类型。
- 有一个或多个Modifier去修改对应的属性,计算方式为Custom Calculation Class。
每个GA都要写一遍Cost和CD的GE,非常麻烦
优化
在实例化生成GE Spec时,修改其Cost和Cooldown属性后再将其应用。
首先创建一个GA基类,添加CD时长、Cost数值(包括生命值和法力值两种类型的Cost)、以及Cooldown Tag等属性,并重载GetCooldownTags、ApplyCooldown、GetCostGameplayEffect三个方法。
GameplayAbilityBase.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "GameplayAbilityBase.generated.h"
/**
*
*/
UCLASS()
class ARPG_UNREAL_API UGameplayAbilityBase : public UGameplayAbility
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldowns")
FScalableFloat CooldownDuration;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldowns")
FGameplayTagContainer CooldownTags;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Costs")
FScalableFloat HealthCost;
// 根据需要可以设置多种类型Cost
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Costs")
FScalableFloat ManaCost;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Costs")
FScalableFloat StaminaCost;
// Temp container that we will return the pointer to in GetCooldownTags().
// This will be a union of our CooldownTags and the Cooldown GE's cooldown tags.
UPROPERTY(Transient)
FGameplayTagContainer TempCooldownTags;
// Return the union of our Cooldown Tags and any existing Cooldown GE's tags.
virtual const FGameplayTagContainer* GetCooldownTags() const override;
// Inject our Cooldown Tags and to add the SetByCaller to the cooldown GameplayEffectSpec.
virtual void ApplyCooldown(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const override;
virtual UGameplayEffect* GetCostGameplayEffect() const override;
};
GameplayAbilityBase.cpp
#include "ARPG_Unreal/Public/GameplayAbilitySystem/GameplayAbilityBase.h"
const FGameplayTagContainer* UGameplayAbilityBase::GetCooldownTags() const
{
FGameplayTagContainer* MutableTags = const_cast<FGameplayTagContainer*>(&TempCooldownTags);
MutableTags->Reset();
// MutableTags writes to the TempCooldownTags on the CDO so clear it in case the ability cooldown tags change (moved to a different slot)
const FGameplayTagContainer* ParentTags = Super::GetCooldownTags();
if (ParentTags)
{
MutableTags->AppendTags(*ParentTags);
}
MutableTags->AppendTags(CooldownTags);
return MutableTags;
}
void UGameplayAbilityBase::ApplyCooldown(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo) const
{
UGameplayEffect* CooldownGE = GetCooldownGameplayEffect();
if (CooldownGE)
{
FGameplayEffectSpecHandle SpecHandle =
MakeOutgoingGameplayEffectSpec(CooldownGE->GetClass(), GetAbilityLevel());
SpecHandle.Data.Get()->DynamicGrantedTags.AppendTags(CooldownTags);
SpecHandle.Data.Get()->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Cooldown")),
CooldownDuration.GetValueAtLevel(GetAbilityLevel()));
ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
}
UGameplayEffect* UGameplayAbilityBase::GetCostGameplayEffect() const
{
return Super::GetCostGameplayEffect();
}
然后创建继承UGameplayModMagnitudeCalculation,创建对应属性的Cost MMC,这里仅展示法力值消耗,Stamina消耗同理。
ManaMMC.h
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "ManaMMC.generated.h"
/**
*
*/
UCLASS()
class ARPG_UNREAL_API UManaMMC : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
};
ManaMMC.cpp
#include "GameplayAbilitySystem/ManaMMC.h"
#include "GameplayAbilitySystem/GameplayAbilityBase.h"
float UManaMMC::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
const UGameplayAbilityBase* Ability = Cast<UGameplayAbilityBase>(Spec.GetContext().GetAbilityInstance_NotReplicated());
if (!Ability)
{
return 0.0f;
}
return Ability->ManaCost.GetValueAtLevel(Ability->GetAbilityLevel());
}
最后写一个通用的Cost和CD GE,所有的GA都使用这两个GE创建Spec。
Cost GE
Cooldown GE
注意上面的Data Tag并不等同于Cooldown Tag,只是用于告诉GE的修改器(Modifier)需要修改(Modify)的Data是什么。Cooldown Tag才是CD期间拥有的Tag,以Cooldown开头。
之后创建一个GA蓝图基类,之后所有的GA都继承自这个基类,配置好CD、Tag和Cost,然后调用Commit Ability节点就好了。如果不需要Cost或CD,最好取消选择Cooldown GE Class和Cost GE Class,以避免当魔力值归零时无法释放0消耗技能的问题。
基类设置
具体GA配置
Ability Task
GA是在一帧内完成的,如果想要实现类似Wait的异步逻辑需要使用Task。
图中所示就是Ability Task,是基于原生的Gameplay Task实现的。
可以看见,GAS内置了许多Task,图中用的是一个播放蒙太奇的Task(注意与UE原生的播放蒙太奇节点不同,在GAS系统中最好使用PlayMontageAndWait)。
AttributeSet
AttributeSet负责定义和持有属性,并且管理属性的变化,包括网络同步。需要在Actor中被添加为成员变量,并注册到ASC(C++)。
一个ASC可以拥有一个或多个(不同的)AttributeSet,因此可以角色共享一个很大的 AttributeSet,也可以每个角色按需添加AttributeSet。
可以在属性变化前(PreAttributeChange)后(PostGameplayEffectExecute)处理相关逻辑,可以通过委托的方式绑定属性变化。
正如字面意思,AS是Attribute的集合。
Attribute就是HP、MP、Speed、ATK等可以用float表示的属性。
因为Attribute是包含了两个float变量的结构体,分别是Base Value和Current Value。
Base Value表示基础值,Current Value表示临时值。
如临时增加100生命值10s,改变的就是Current Value,10s后自动变回Base Value。
注意事项
AS只能使用C++创建。
AS添加
创建AttributeSetBase类,这里需要使用AbilitySystemComponent.h的宏ATTRIBUTE_ACCESSORS()。
对每一个FGameplayAttributeData都应用一遍宏。
这里创建Health和MaxHealth作为示范。
AttributeSetBase.h
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "AttributeSetBase.generated.h"
// Uses macros from AttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
*
*/
UCLASS()
class ARPG_UNREAL_API UAttributeSetBase : public UAttributeSet
{
GENERATED_BODY()
public:
// Attributes
UPROPERTY(VisibleAnywhere, BlueprintReadWrite);
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, Health);
UPROPERTY(VisibleAnywhere, BlueprintReadWrite);
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, MaxHealth);
};
最后在ASC组件里指定好就可以使用了。
Default Starting Table是用于属性初始化用的
AS初始化
AS可以通过GE和DataTable两种不同方式初始化,Epic推荐使用GE。
通过GE初始化
创建一个GE,命名为GE_InitAttributes,找到Gameplay Effect条目。
一个Modifiers对应一个Attributes。
添加新的Modifier,选择要修改的属性,Modifier Op(修改方式)选择Override。
Modifier Magnitude(修改值)选择Scalable Float,填入想设置的默认值。
然后在蓝图中Apply该GE即可。
通过DataTable初始化
创建一个DataTable,行结构选择AttributeMetaData,其格式如下。
可以使用Excel做好后保存为csv文件再快速导入。
注意这里的最大最小值没有任何作用,为未完成功能,实现方法见4.5。
因此最好单独创建一个MaxHealth属性。
属性的名称需要带上完整类名。
然后找到Character的ASC组件,在Attribute Test条目填上表格即可。
AS获取
搜索Get Attribute即可,Current Value和Base Value都可以获得。
可以通过ASC组件调用,也可以使用GAS的蓝图函数库里的函数。
监听Attribute修改事件
PreAttributesChange和PostGameplayEffectExecute
AttributeSet提供了两个方法用于监听Value的改变:
- PreAttributeChange:用于Attribute的Current Value被改变前调用,对应Infinite和Has Duration的GE。
- PostGameplayEffectExecute:用于Base Value改变后调用,对应InstantGE。
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
这两个事件适用于Clamp属性,确保其不超出临界值。
void UAttributeSetBase::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
}
// 这个方法也行,但是需要"GameplayEffectExtension.h"
void UAttributeSetBase::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if(Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
}
}
GetGameplayAttributeValueChangeDelegate
如果想要监听Attribute的变化以更新UI,则不适合用上面的方法,应该在角色类中创建一个回调,以及蓝图事件:
CharacterBase.h
// Attribute Change Callbacks
void OnHealthChanged(const FOnAttributeChangeData& Data);
// Attribute Change Event in Blueprint
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangeEvent, float, NewHealth);
UPROPERTY(BlueprintAssignable, Category="Ability")
FOnHealthChangeEvent HealthChangeEvent;
CharacterBase.cpp
void ACharacterBase::OnHealthChanged(const FOnAttributeChangeData& Data)
{
HealthChangeEvent.Broadcast(Data.NewValue);
}
然后在BeginPlay()里将其注册到ASC:
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
if (AbilitySystemComponent != nullptr)
{
//初始化技能...
//初始化ASC...
//注册Attribute变化事件
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UAttributeSetBase::GetHealthAttribute()).AddUObject(this, &ACharacterBase::OnHealthChanged);
}
}
之后就可以从蓝图调用生命值变化事件了。
Gameplay Effect
Gameplay Effect(GE)是Ability对自己或他人产生影响的途径。GE通常可以被理解为我们游戏中的Buff。比如增益/减益效果(修改属性)。
但是GAS中的GE也更加广义,释放技能时候的伤害结算,施加特殊效果的控制、霸体效果 (修改GameplayTag)都是通过GE来实现的。
GE相当于一个可配置的数据表,不可以添加逻辑。开发者创建一个UGameplayEffect的派 生蓝图,就可以根据需求制作想要的效果。
GE就是一张数据表,不负责逻辑处理,定义Attribute修改的值。
GE是修改Attribute的唯一渠道!
其工作流程可以简单分为以下几步:
- 创建一个实例Spec。
- (可选)修改Spec的一些值。
- 如果允许,应用(Apply)GE,但是Attribute仍未被修改。
- 如果允许,使Modifier生效,修改Attribute。
- 满足条件后,移除该Spec。
其功能非常多且强大,提供了非常多的可配置项。来一张图感受一下:
GE的配置项可以满足绝大多数游戏的需求,尤其是MOBA游戏和RPG游戏。
比较重要的功能有嵌套调用GE、赋予GA、调用GC。
此外也能根据等级计算数据、实现多层GE叠加、设置GE应用的条件、几率等。
GE配置项讲解
Gameplay Effect
最核心的配置项。
Duration Policy:GE的持续类型,有三种。
- Instand:立即改变Base Value(扣血)。
- Infinite:永久改变Current Value(按下疾跑修改速度)只能通过GA或ASC取消。
- Has Duration:临时修改Current Value(临时Buff)。
Modifiers:选择你要修改的Attribute,支持数值等级曲线和Tag,会单独讲。
Executions:同样也是修改属性,支持更复杂的运算。
Conditional Gameplay Effects:当GE成功应用时,可以应用其他GE,嵌套调用GE的方法之一。
如果是Has Duration的GE,那么我们需要设定Duration的时长,称为Duration Magnitude。
Period
设置GE的触发周期,仅有Infinite和Has Duration的GE才显示前两项设置。
Period:
如果是Infinite模式,加上Period后等价于周期执行的Instant;
如果是Has Duration模式,就是普通的周期重复。(也有说法是周期执行的Instance,待证实。)
Execute Periodic Effect on Application:t=0的时候是否触发。(如LOL中点燃技能就是使用后立即造成伤害,之后每秒应用一次)。
Periodic Inhibition Policy:GE中断并恢复后的处理方式。
- Never Reset:从被打断时的位置开始计算周期,相当于暂停再播放。
- Reset Period:从0开始计算周期。
- Execute and Reset Period:打断时立即执行一次,下次从0开始计算周期。
Application
设置GE的应用概率和条件。
概率支持曲线图表。
条件可以简单地用Tag去限制,也可以用Application Requirement进行更复杂的逻辑判断。
需要添加一个自定义的Custom Application Requirement(CAR)蓝图类,重载里面的唯一方法,如下图所示。
官方文档推荐在以下情况使用CAR蓝图类:
- Target需要有一定数量的属性时;
- Target需要GE堆叠到一定数量时;
除此之外CARs还能够做更多事情,比如检查Target是否应用了一个GameplayEffect 的实例,在应用一个新实例时如果同类型的实例已存在则只改变其持续时间(CanApplyGameplayEffect()要返回false)。
Stacking
用于叠加多个GE的效果,仅能用于Infinite和Has Duration的GE。
Stack Limit Count:最大层数。
Stacking Type:叠加栈在目标身上or施法者身上。
举个例子,假设层数为3,如果是by Target模式,那么3个敌人对我释放的Debuff只能叠三层。
如果是by Source模式,那么3个敌人可以对我叠加9层Debuff。
每层Effect如果是Modifiers来计算,则为直接叠加的效果,比如用Modifiers来增加3攻击力,则第一层为增加3攻击力,第二层为增加6攻击力,第三层为增加9攻击力,而如果需要根据层数不同而改变增加的值,则需要使用Executions。
Stack Duration Refresh Policy:Apply新GE时是否刷新持续时间,注意溢出的Apply也会刷新,想关闭可以在下面的Overflow条目关闭。
Stack Period Reset Policy:同上,是否刷新周期。
Stack Expiration Policy:当一层GE的Duration到期后的处理方式。
- Clear Entire Stack:清空全部层数,如LOL征服者。
- Remove Single Stack and Refresh Duration:清空一层,如LOL致命节奏。
- Refresh Duration:不清空,相当于无限长的Duration,但可以通过调用FActiveGameplayEffectsContainer::OnStackCountChange(FActiveGameplayEffect& ActiveEffect, int32 OldStackCount, int32 NewStackCount)方法来自己处理细节,如一次掉两层。
Overflow
可以设置Stack溢出会Apply的GE。通过GE应用GE的方法之一,需要配合Stacking来使用。
Deny Overflow Application:如果为True,则溢出的Apply不会刷新Duration。
Clear Stack On Overflow:字面意思,需要勾选上一个选项后才能选中。
Expiration
当GE的Duration被打断或结束时的行为。通过GE应用GE的方法之一,仅能用于Has Duration的GE。
Premature Expiration Effect Classes:打断时Apply的GE。
Routine Expiration Effect Classes:正常结束时Apply的GE。
Immunity
Immunity和Tag类似,也可以用来限制GE。
通过Tag匹配来实现,匹配的目标是Target的ASC组件以及拥有的GA。
如果拥有Require Tags的所有Tag,并且没有Ignore Tags的所有Tag,则认为匹配成功,该GE不会被Apply。
和Tags相比,Immunity提供了一个回调UAbilitySystemComponent::OnImmunityBlockGameplayEffectDelegate。
下面的Granted Application Immunity Query是更高级的匹配,但是更消耗性能。
比较特殊的是最后三个选项,分别是根据GE修改的Attribute匹配、根据GE来源匹配以及根据GE的类匹配。
Tags
和GA的Tag条目类似,设置各种限制条件。
GameplayEffectAssetTag:GE的Tag。Combined Tag为计算结果,不可编辑,计算方式是继承的Tag+Added-Removed。
GrantedTags:GE会赋予目标ASC的Tag,仅适用于Infinite和Has Duration的GE。
Application Tag Requirements:GE满足Tag条件才能应用(Apply)。
Ongoing Tag Requirements:GE满足Tag条件才能修改值(Modifier or Execution)。通过这项设置GE可以仅Apply而不修改值,仅适用于Infinite和Has Duration的GE。
Removal Tag Requirements:GE满足Tag条件就会被移除。
Remove Gameplay Effects with Tags:Apply后移除指定Tag的GE。
Remove Gameplay Effect Query:上面一条的高级版,可以匹配GE的类(Effect Definition),匹配来源(Effect Source)以及匹配GE修改的属性(Modifying Attribute),移除成功匹配的GE。
Display
与特效相关的设置,调用Gameplay Cue的方式之一
Granted Abilities
使用GE添加GA的方式。仅支持Infinite和Has Duration的GE。
Level:GA的等级。
Input ID:如果使用旧版输入,每一个操作映射都对应着一个枚举值,输入对应的枚举值就可以将这个新GA绑定到输入上。
Removal Policy:设置当GE被移除时,GA是否要移除。
- Cancel Ability Immediately:移除,并触发事件EndAbility。
- Remove Ability on End:移除,但是不触发EndAbility。
- Do Nothing:GA不会被移除。
Modifier与Execution
Modifier
Modifier在Gameplay Effect目录下,作用是修改Attribute,一个Modifier对应一个Attribute。
Attribute:要修改的Attribute,AttributeSetBase是自己写的C++ AS类。
Modifier Op:运算符(Operator),有加、乘、除和覆盖四种。
Modifier Magnitude:运算值,与Attribute进行Modifier Op选择的运算。
Tags:是否能修改该Attribute的限制条件,这里指ASC组件上的Tag。
我们制作GE,主要任务就是确定Attribute、Op与Magnitude,而Magnitude是最灵活的一部分,可以通过四种方式得到,下面即将介绍。
Magnitude的计算
Magnitude的计算方式有四种,对应四种Magnitude Calculation Type:
- Scalable Float:不计算,直接给定一个浮点数作为Magnitude的值,也可以从等级曲线中获得。要注意的是如果使用了曲线图表,图表里获得的值会和输入的数相乘。
- Attribute Based:读取玩家或目标属性作为一个值,可以进行简单线性计算。
主要分为三个部分,上面的部分是运算系数,公式为:
要修改的Attribute和用来计算Magnitude的Attribute是不一样的,为了区分这里称后者为Attr。Coe,Pre,Post都可以通过等级图表获得。中间的部分为Attr的来源,图例为目标的生命值。可以实现如偷取敌方最大生命值20%的效果。AttributeCurve的存在意义不是很清楚。
根据《GameplayEffect(一)功能》的说明,正确的公式应该如下,有待测试:
关于Snapshot,官方文档是这么说明的:
快照(Snapshot)意味着取GameplayEffectSpec被创建时属性的值,否则取GameplayEffectSpec被应用时属性的值。
正常情况下,我们为角色应用GE需要调用ApplyGEToOwner节点(详见5.4.1),此时系统会自动帮我们创建一个Spec实例并将其Apply。
但我们也可以手动地调用MakeOutgoingGESpec节点来实例化一个GE,修改其值后,再使用ApplyGESpecToOwner节点应用该Spec(该方法在3.5.2有使用)。
如果想要获取修改前的值,就可以勾选Snapshot。
下面的Attribute Calculation Type有三种,代表Attr使用的值:
- Attribute Magnitude:使用Current Value。
- Attribute Base Value:字面意思。
- Attribute Bonus Magnitude:使用Current Value - Base Value。
Tag也是计算限制条件,不过是对Attr的限制,而不是上文的Attribute。
- Custom Calculation Class:自定义的更复杂的运算规则,与AttributeBase相比好处是可以获取任意数量的Attr。
点击Calculation Class旁边的加号,将创建一个GameplayModMagnitudeCalculation蓝图类。
里面唯一的重载函数就是写计算逻辑的地方,返回的float就是Magnitude值。
此外还有一个继承的变量RelevantAttributeToCapture,可以在类默认值设置要Capture的Attribute及其来源。
但这个蓝图应该只是半成品,Spec和GE Attribute Capture Definition结构体都没法拆分,想要在蓝图使用还需要去C++部分自己实现一些函数给蓝图。
如果使用C++的方式编写MMC,可以参考这篇文章:
《虚幻四Gameplay Ability System入门(7)-Gameplay Effect详解(2)自定义Calculation Class》
对照着上图理解会更直观一些。同时在3.5.2处设置Cost时也有一个使用MMC的例子。
- Set By Caller:通过蓝图获得Magnitude。
一般情况下,我们Apply一个GE后,系统会自动帮我们生成一个GE的Spec并添加到目标的ASC上。5.4会说明GE的一般使用方法。
这里的思路不太一样,我们先是创建了一个GE的实例Spec,用Caller修改指定Modifier的Magnitude之后,再将Spec Apply到目标上。
而Data Tag则用于区分多个Modifier,告诉蓝图修改哪个Modifier的Magnitude,建议用Data开头。
比较简单,也非常好用。配置完之后可以从GA或是ASC按照下图所示方法使用该GE。
图例为创建一个Cooldown GE的实例后,再将Cooldown值赋给对应Data的Magnitude。Cooldown的实现并不是这样,这里只是一个演示,可以自行换成其他属性。
Execution
更高级的Modifier,一个Execution就能设置多个Attribute。
和上面计算Modifier的Magnitude用的CalculationClass类似,区别在于上文用到的MagnitudeCalculation是获取多个Attr以计算Magnitude,再通过Magnitude修改Attribute。
而这个ExecutionCalculation是直接获取多个Attribute进行修改。
此外,Conditional GE可以设置Execution成功执行时候Apply的GE,这也是非主动应用GE的方法之一。
图的最下面也有一个Conditional GE,注意二者是不一样的。鼠标悬停也能查看二者的差别。
GE的应用
GE的应用我们称为Apply,可以从GA或者ASC去Apply一个GE。
GE可以通过蓝图手动应用,也可以通过GE的配置项,使GE在特定条件下嵌套应用其他GE。
GE的主动应用
应用GE的时候,我们可以设置GE的等级。
Stacks表示应用多少层的GE,仅在GA里Apply GE时才能设置此项。
注意重复调用该节点也算多层GE叠加,Stack详见5.2.4。
GA中Apply GE的例子
ASC中Apply GE的例子
Apply的对象有Owner也有Target,Owner比较省事。
如果想让敌人扣血,可以在GA里先Send一个Gameplay Event,通过Event调用Target的播放受击动画的GA,再在GA里Apply一个扣血GE To Self。
GE的嵌套应用
涉及3个配置项,对应不同的条件:
- Gameplay Effect:当前GE成功应用后,应用配置好的GE。
- Overflow:GE层数溢出时,应用配置好的GE,可以做满层爆炸的效果。
- Expiration:GE中断或结束时,应用配置好的GE。
Gameplay Cue
GameplayCues(GC)执行非游戏性相关的事情,比如音效,粒子特效,震屏等。
GameplayCues通常会被复制和预测(除非设置Executed, Added或Removed是本地的)。
主要有Static和Actor两类GC。
Static适用于单次播放的特效。由于其是静态的,不会产生实例,因此在其蓝图里创建的变量都是只读的。对应Instant和Periodic的GE。
Actor适用于持久的,不定时长的特效。其继承自一个场景Actor,每次使用会产生一个对应实例。对应Infinity和Has Duration的GE。
GC的制作
打开窗口-GameplayCue编辑器,可以看到如下页面:
每一个GC(处理器/Handler)需要一个对应的Tag,点击新增会显示GC蓝图创建页面。
根据需求选择即可。
不用GameplayCue编辑器,直接创建GC蓝图也是可以的,但是记得在类默认值中设置Tag。
Static类型GC设置
对于Static的GC,仅需重载OnExecute函数即可。
通过获取传入参数Target的根组件,就可以附加粒子系统发射器了。
Parameter用于传入一些参数,如伤害飘字的数值等。
Actor类型GC设置
Actor类型GC继承自场景Actor,因此有Tick、BeginPlay、Overlap等其他函数。
因此其类默认值也有很多设置,最主要的是Gameplay Cue和CleanUp两个目录。
这里我们重载OnActive和OnRemove两个函数即可。
和Static类型的GC不同的是,如果我们勾选了Gameplay Cue的Auto Attach GC To Owner,我们可以用GC自身的根组件作为发射器要附加的组件(在不需要绑定到指定骨骼的情况下)。
此外,由于Actor类型的GC是非静态的,可以产生实例,因此是可以创建变量并写入的。
GC的调用
一般通过GE配置,也可以在GA里调用Execute/Add触发。
从GE配置GC
选择GC对应的Tag即可,可以同时选择多个Tag,触发多个GC。
Require Modifier Success to Trigger Cues:需要GE成功修改Attribute后才调用GC,而不仅仅是Apply该GE。
Suppress Stacking Cues:多个GE存在Stack中时是否实例化多个GC(如果使用了Stack,对应的一定是可以实例化的Actor类GC)。
Min、Max Level和Magnitude Attribute则与传入参数有关。
Raw Magnitude即为Magnitude Attribute的值,而Normalized Magnitude的计算方式如下:
$Normalized = (Raw - Min) / (Max - Min)$
如上图所示,当Min=0且Max=100时,Normalized = Raw / 100,即百分比。
从GA调用GC
共有五个相关函数,Add&Remove与Execute分别对应Actor类型和Static类型的GC,具体用法见图。
总结
整个GAS系统的工作流程如图所示。
ASC管理GA、GE、Attribute。
GE可以用来给予ASC一个GA,也可以修改Attribute。(甚至还能Apply其他的GE,图中没有提到。)
GA可以发送Event给其他ASC,调用对应的GA;也可以对目标Apply一个GE,修改其属性。
GE和GA都可以用来触发GC。