Gameplay Ability System学习(1)

因为本人正在学习虚幻四当中的Gameplay Ability System系统,发现在网上的中文教程较少,于是希望将自己学习的过程和方法记录下来,也希望可以帮助到一些有需要的人。当然,因为我也是刚刚学习,所以这篇教程会有很多问题和缺陷,也请读者理解。
注意使用游戏能力系统必须用到C++,所以我这里默认看这篇文章的老哥具备一定的虚幻四基础知识了。废话不多说,开始!
这一篇文章参考了Youtube的视频视频链接

1. 添加GAS系统

使用GAS系统首先需要在Plugins中Enable插件Gameplay Abilities

添加插件

然后在ProjectName.Build.cs中,添加三个依赖,分别是GameplayAbilities, GameplayTasks, GameplayTags

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", 
"HeadMountedDisplay", "GameplayAbilities", "GameplayTasks", "GameplayTags" });

然后重新build solution

2. 然后理解一下GAS系统中几个基本类型的作用:

(1)AbilitySystemComponent(ASC)是一个component,负责处理技能系统的所有交互。可以把它理解为游戏技能系统的心脏。
(2)Gameplay Tags是一系列层次化的标签,如果是武器的攻击,可以定义为Weapon.Hit。可以通过这些标签设置各种关系,比如拥有魔法抗性标签的不会被魔法攻击标签的造成伤害。也可以用Gameplay Cue来响应对应Tage的GE。
(3)Attributes和Attribute Set中负责定义技能系统中的属性,包括Health, Stamina等。通常只能被Gameplay Effect修改,包括角色属性值的初始化。
(4)Gameplay Effects(GE)是Abilities改变自己或别人的Attributes和Gameplay Tags的方法,比如伤害,生命值恢复等几乎一切修改数据的方法,它本身也是一个数据类型,不应该有逻辑,只表示如何修改,修改多少等数据信息。
(5)Gameplay Abilities表示游戏中Actor所能做得行动,比如射击,近战攻击等。它的执行次序如下。

Ability

(6)Ability Tasks,技能任务
(7)Gameplay Cues,当Gameplay Effect被成功应用后,配置在Gameplay Tags的所有Gameplay Cues都会被触发。

下图是我理解的GAS系统的部分关系图,只是大致的关系与个人理解,并不准确,参考一下即可。


GAS系统关系图
3. 接下来开始添加所需要的类,在虚幻四中新建Ability System Component, Gameplay Ability和一个Attribute Set。我这里命名都是GAS。
新建classes
4. 然后打开visual studio(Rider)。在ProjectName.h中,添加enum类
UENUM(BlueprintType)
enum class EGASAbilityInputID: uint8
{
   None,
   Confirm,
   Cancel,
   Punch
};
5.然后定义角色的Attribute Set,在这里我希望角色拥有生命值。

首先定义一个macro方法,这个方法方便于修改和初始化属性值,可以直接生成某个Attribute属性的getter, setter等方法。这个方法的实现在AttributeSet.h中,可以进入查看,在最底部的位置,直接复制过来即可。

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

然后声明构造函数和GetLifeTimeReplicatiedProps方法。

UGASAttributeSet();

/** Returns properties that are replicated for the lifetime of the actor channel */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

接下来定义生命值Attribute。ReplicatedUsing表示该函数在属性值发生修改后就会被调用。FGameplayAttributeData 用于创建Attribute,它作为一个Struct,在技能系统中被建议使用。具体实现也在AttributeSet.h中,可自行查看。

UPROPERTY(BlueprintReadOnly, Category="Attributes", ReplicatedUsing=OnRep_Health)
FGameplayAttributeData Health;
//这里使用了之前定义的macro方法
ATTRIBUTE_ACCESSORS(UGASAttributeSet, Health);

UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

然后在cpp中生成这些函数的实现。
OnRep_Health直接使用了Attribute.h中,用于处理被modify,修改了的attribute的方法。

void UGASAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
   GAMEPLAYATTRIBUTE_REPNOTIFY(UGASAttributeSet, Health, OldHealth);
}

GetLifetimeReplicatedProps 的实现(这个函数的作用我还不太理解,但教程里都做了相应的要求实现)

UGASAttributeSet::UGASAttributeSet()
{
}
/** Allows gamecode to specify RepNotify condition*/
void UGASAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
   Super::GetLifetimeReplicatedProps(OutLifetimeProps);

   DOREPLIFETIME_CONDITION_NOTIFY(UGASAttributeSet, Health, COND_None, REPNOTIFY_Always);
}

Attribute Set的代码就完成了。

6.然后在GASGameplayAbility.h中。声明一个能力输入的enum变量,该enum类我们之前在第四步实现了。
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="Ability")
EGASAbilityInputID AbilityInputID = EGASAbilityInputID::None;
7.还需要写代码的最后一步啦,也是最长的一步,打开自己的character.h。先继承AbilitySystem接口
#include "AbilitySystemInterface.h"
class AGASCharacter : public ACharacter, public IAbilitySystemInterface

在character中需要一个AbilitySystemComponent(GAS系统的心脏)和AttributeSet(attributes集合)

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera, meta = (AllowPrivateAccess = "true"))
class UGASAbilitySystemComponent* AbilitySystemComponent;

UPROPERTY()
class UGASAttributeSet* Attributes;

还需要重写一个方法,返回character的AbilitySystemComponent

virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
UAbilitySystemComponent* AGASCharacter::GetAbilitySystemComponent() const
{
   return AbilitySystemComponent;
}

在角色的构造函数中,创建AbilitySystemComponent和Attribute Set

AbilitySystemComponent = CreateDefaultSubobject<UGASAbilitySystemComponent>("AbilitySystem");
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);

Attributes = CreateDefaultSubobject<UGASAttributeSet>("Attributes");

现在如果Build Solution,就可以在角色的Movement component下看到AbilitySystemComponent

但是现在动作系统没有Ability可以被激活,也没有和输入绑定。

首先我们需要实现初始化动作系统可以使用的abilities。需要一个DefaultAbilities将这些能力“给予”动作系统。
然后对角色的attributes赋予初值,由于动作系统对attributes的修改都需要借助gameplay effect,即使是初始化,所以我们需要一个DefaultAttributeEffect,初始化Attribute
在public中定义两个对象。之后会在蓝图里赋值。

UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="GAS")
TSubclassOf<class UGameplayEffect> DefaultAttributeEffect;

UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category="GAS")
TArray<TSubclassOf<class UGASGameplayAbility>> DefaultAbilities;

有了DefaultAttributeEffect后。我们需要使用默认的gameplay effect初始化角色的属性值。

virtual void InitializeAttributes();

实现

// 初始化Attributes
void AGASCharacter::InitializeAttributes()
{
   if(AbilitySystemComponent && DefaultAttributeEffect)
   {
      FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
      EffectContext.AddSourceObject(this);

      FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(DefaultAttributeEffect, 1, EffectContext);

      if(SpecHandle.IsValid())
      {
         FActiveGameplayEffectHandle GEHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
      }
   }
}

我觉得要理解功能看蓝图应该更加容易一点,以上的功能在蓝图中的实现如下。

FGameplayEffectContextHandle描述:

/** Handle that wraps a FGameplayEffectContext or subclass, to allow it to be polymorphic and replicate properly*/

FGameplayEffectSpsecHandle在虚幻四的中的描述如下:

/** Allows blueprints to generate a GameplayEffectSpec once and then reference it by handle, to apply it multiple times/multiple targets. */

最后一个函数ApplyGameplayEffectSpecToSelf,将创建的Gameplay effect作用于目标,在这里通过作用于自己来进行初始化属性。

接下来还需要将DefaultAbilities赋予给技能系统。

virtual void GiveAbilities();

实现

void AGASCharacter::GiveAbilities()
{
   if(HasAuthority() && AbilitySystemComponent)
   {
      for(TSubclassOf<UGASGameplayAbility>& StartupAbility: DefaultAbilities)
      {
         AbilitySystemComponent->GiveAbility(
            FGameplayAbilitySpec(StartupAbility, 1,static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID), this));
         AbilitySystemComponent->InitAbilityActorInfo(this, this);
      }
   }
}

FGameplayAbilitySpec需要一个GameplayAbility作为输入,其余输入:

通过InitAbilityActorInfo告诉动作系统,谁才是他的主人!要听主人的命令。
然后Ability和Attribute的初始化就完成了。

最后需要将动作系统和输入绑定。
绑定函数需要两个参数,一个是角色Input Component的指针,一个是FGameplayAbilityInputBinds, FGameplayAbilityInputBinds的构造函数至少需要3个参数:前两个参数是字符串,用于定义”Confirm”和”Cancel”的输入命令,第三个参数为所有输入的UEnum,就是之前我们在头文件中定义的那个。我们在OnRep_PlayerState()中完成绑定。

virtual void OnRep_PlayerState() override;

实现

void AGASCharacter::OnRep_PlayerState()
{
   Super::OnRep_PlayerState();

   AbilitySystemComponent->InitAbilityActorInfo(this, this);

   InitializeAttributes();

   if(AbilitySystemComponent && InputComponent)
   {
      const FGameplayAbilityInputBinds Binds("Confirm", "Cancel", "EGASAbilityInputID",
         static_cast<int32>(EGASAbilityInputID::Confirm), static_cast<int32>(EGASAbilityInputID::Cancel));
      AbilitySystemComponent->BindAbilityActivationToInputComponent(InputComponent, Binds);
   }
}

最后重写PossessedBy函数,因为Character的主人实际上也是一个Character Controller,所以需要让controller来当技能系统的“主人”

virtual void PossessedBy(AController* NewController) override;

函数实现

void AGASCharacter::PossessedBy(AController* NewController)
{
   Super::PossessedBy(NewController);
   // Server Gas Init
   AbilitySystemComponent->InitAbilityActorInfo(this, this);

   InitializeAttributes();
   GiveAbilities();
}
8. OK,到这一步,代码部分就全部完成了。接下来进入虚幻四Editor。

新建蓝图类GameplayEffect,命名为GE_CharacterDefault,作用为初始化角色的Attributes


image.png

在该Gameplay Effect中,只需要在Modifiers中添加一个元素。


将GE_CharacterDefault赋值给角色蓝图中的Default Attribute Effect


运行游戏,输入命令(这一步应该在添加Ability后,即第九步,否则会报错)



可以看到属性被初始化了

9.接下来实现角色攻击的能力。

首先在Input Setting中添加Action Mapping


绑定鼠标左键

同样的,新建蓝图类


GameplayAbility

命名为GA_Punch



这里我们实现最简单的,使用Punch能力后,会播放一次动画,然后结束能力。


攻击后播放Montage

添加Ability

完成后,将鼠标左键和能力绑定。

激活能力

接下来运行,点击鼠标左键,我们就可以看到角色可以播放攻击动画了。


10. 实现攻击伤害

角色具有攻击能力后,还不能造成伤害,为了实现伤害能力。首先创建造成伤害的Gameplay effect
和第八步一样,命名为GE_Damage


GE_Damage

注意,这里要将Gameplay Cue Tags设置为GameplayCue.TakeDamage,这个名字可以自己创建。


Tag

我们不希望角色在攻击的全过程都能形成伤害判定,所以需要在关键帧后启动碰撞判定。
新建蓝图类AnimNotify,命名为AN_Punch



打开AN_Punch,让播放动画的对象在接收到Notify后,会调用角色的Punch事件。这里的事件还没有实现。



在攻击动画中添加Notify。

实现Punch Event



这里大致实现的过程是,在启动punch后,会在右手命名为Weapon的Socket位置启动一个球状碰撞检测器,如果检测器检测到碰撞的pawn(valid),就会向该actor发送一个Gameplay Event,注意,这里的Event Tag需要为Weapon.Hit,可以通过添加Tag创建。


11. 完善Punch Ability

回到GA_Punch中,我们需要在结束activity前,等待因为碰撞出发引发的event,收到event后,向目标施加一个Gameplay Effect,就是我们之前实现的GE_Damage




Apply Gameplay Effect放大

现在虽然可以造成伤害,但是并没有反馈,我们希望敌人被伤害后会播放一个被攻击动画。新建一个Gameplay Cue, 命名为GC_TakaDamage


Gameplay Cue

注意,GC_TakaDamage中的Class Setting需要将这里的tag设置为Gameplay.TakeDamage,和我们在GE_Damage中的一样,这意味着该Gameplay Cue就会相应所有tag为这个的Effect。


12. 完成

开始游戏



可以看到被攻击的单位会播放Hit动画。完成!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容