虚幻四Gameplay Ability System入门(1)

虚幻四Gameplay Ability System入门(2)

我最近在学习虚幻四的Gameplay Ability System,这个名字可以被理解为技能系统框架(大概),接下来我就简称为GAS或技能系统。在网上找了很久,发现相关的中文教程比较少,所以打算把自己的学习过程和对技能系统的理解写成文章,既帮助我理解,也希望可以帮助到其它想要学习GAS的朋友。之前写过一篇教程,但感觉很不满意,于是打算重写一遍。接下来进入正题。

什么是Gameplay Ability System?

在很多的游戏中,角色会拥有很多的技能,比如火球术,治疗术等等。这些技能会消耗法力值,存在冷却时间,可以造成伤害。同时一个角色可以拥有多个技能,技能之间也会相互关联,比如火球术无法对使用冰霜护盾的敌人造成伤害。

实现以上的种种效果需要一个较为复杂的框架,而虚幻四的GAS系统就为我们提供了一个管理技能的系统,可以很方便的实现技能需要,但GAS目前离不开C++,而且官方目前还没有提供方便入门的教程与文档,因此学习起来还是比较痛苦的,以下是我认为比较好的入门教程:

Bilibili

UE4官方的视频教程,中文

https://www.bilibili.com/video/BV1X5411V7jh

Youtube

有条件的可以翻墙去看

UE4官方视频教程,英文,相较于中文的视频,我认为这个教程讲解的更深入一点。

https://www.youtube.com/watch?v=YvXvWa6vbAA

Github

这一篇是我认为最好的文档了,包含一个项目和较为完整的说明,但仍然较为复杂,建议看完上面两个视频对GAS有了一定了解再看。

https://github.com/tranek/GASDocumentation

GAS系统的基本构成

  1. Ability System Component,GAS系统的大脑,拥有Abilities(技能)和Attributes(属性),可以把它理解为技能系统的中枢。
  2. Ability,技能。可以把它理解为某项能力,比如火球术,跳跃等等,它应该包含较为完整的逻辑,可以添加给角色的技能系统,也可以从技能系统中移除。
  3. Attribute和AttibuteSet,属性和属性集。Attribute代表了角色的某种属性,比如Health,Mana等等。
  4. Tags,层次化的标签。它代表了某种状态或者属性。比如角色处于燃烧状态,那么这个状态的标签就为Character.State.Burning,我们可以自定义状态并在技能和效果中设置tag之间的关系,比如角色处于燃烧标签时会受到伤害,但如果被带有Water标签的效果影响,就可以移除燃烧标签。我认为Tag应该是技能系统的核心和魅力所在了。
  5. Gameplay Effect(GE),技能效果,它本身只是一个数据集,代表了对某些Attribute和Tag的修改。比如GE_Damage中应该设置为对Attribute:Health修改Add -20,表示为伤害效果为扣血20点。它还可以添加修改Tag,或者被Tag所影响,和第四点的例子一样,火焰的伤害效果本质是一个GE,如果另外有一个带有Water标签的GE作用于角色,那么它会移除燃烧效果GE。理论上GAS中对于Tag和Attribute的修改尽可能都要使用GE
  6. Ability Task,表示为Ability中的一个任务。比如接下来几乎每一个技能都会用到的一个Task是PlayMontageAndWait,可以看到它创建并返回了一个Async Task.
1.png
  1. Gameplay cue,GC执行的是非游戏逻辑的效果比如声音特效,粒子特效,摄像机抖动等等,GC通过关联的Tag触发,还是举角色燃烧的粒子,当角色身上有Burning的标签时,就可以设置Gameplay Cue Tag, 然后就会触发GC_Burning,让角色身上有一个燃烧的粒子特效。

下图是我对GAS各个组件关系之间的简单理解,并不完整且正确,只是帮助大致理解各个组件之间的关系。

2.png

GAS基础设置

这一部分涉及到虚幻四C++和蓝图的知识,需要拥有一定的基础。

首先打开虚幻四,创建一个C++的空白项目,如果觉得配置麻烦也可以创建一个第三人称项目。

这里我直接使用了epic商城中的素材,将素材直接添加到工程

3.png

1.角色基础配置

因为这篇文章不是介绍虚幻四C++入门的,因此我就不具体介绍角色基础配置的说明了。

首先新建Character C++类,命名为CharacterBase

4.png

在Project Setting中的Input添加Axis Mappings

5.png

打开CharacterBase.h和Character.cpp

添加Camera和SprintArm,函数MoveForward和MoveRight

GENERATED_BODY()
    
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera", meta=(AllowPrivateAccess = "true"))
    class USpringArmComponent* CameraBoom;

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

protected:
   // Character Movement
   void MoveForward(float Value);

   void MoveRight(float Value);

cpp实现

// Sets default values
ACharacterTesting::ACharacterTesting()
{
   // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
   PrimaryActorTick.bCanEverTick = true;

   bUseControllerRotationPitch = false;
   bUseControllerRotationRoll = false;
   bUseControllerRotationYaw = false;

   GetCharacterMovement()->bOrientRotationToMovement = true;

   CameraBoom = CreateDefaultSubobject(TEXT("Camera Boom"));
   CameraBoom->SetupAttachment(RootComponent);
   CameraBoom->TargetArmLength = 300.0f;
   CameraBoom->bUsePawnControlRotation = true;

   FollowCamera = CreateDefaultSubobject(TEXT("Follow Camera"));
   FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
   FollowCamera->bUsePawnControlRotation = false;
}

// Called to bind functionality to input
void ACharacterTesting::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    PlayerInputComponent->BindAxis("MoveForward", this, &ACharacterTesting::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &ACharacterTesting::MoveRight);

    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
}

void ACharacterTesting::MoveForward(float Value)
{
    if((Controller != nullptr) && (Value != 0.0f))
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void ACharacterTesting::MoveRight(float Value)
{
    if((Controller != nullptr) && (Value != 0.0f))
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
}

在UE4 Editor中创建CharacterBase子类BP_Character,给角色选择mesh

6.png

创建AnimBlueprint,命名为AnimBP_Character,设置角色相应的动画。

7.png

这里只需要locomotion就够了,把locomotion连接到output pose上即可

8.png
9.png
10.png

角色基本设置完成。

2.GAS系统基础配置

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

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

PrivateDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks" });

然后Build Solution

2. 添加ASC

打开CharacterBase.h

创建Ability System Component,这里需要继承一个接口,然后实现接口中的纯虚函数GetAbilitySystemComponent(),它的作用是返回AbilitySystem,这个函数的作用是在不知道当前角色是否具有AbilitySystem的时候时,我们就可以调用这个函数,而不需要使用Cast_To

UCLASS()
class GAS__API ACharacterBase : public ACharacter, public IAbilitySystemInterface
{
    //.......
public:
    // ......
    // Ability System Component
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="CharacterBase")
    UAbilitySystemComponent* AbilitySystem;

    virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;
}

GetAbilitySystemComponent()实现

ACharacterBase::ACharacterBase()
{
    //......
    AbilitySystem = CreateDefaultSubobject("AbilitySystem");
}

// override AbilityInterface virtual function
UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent() const
{
   return AbilitySystem;
}

这样子Character就拥有了Ability System Component了。

3.接下来实现一个方法可以给ASC添加Ability,这个功能可以在BP中调用。

// Add Ability to Character
    UFUNCTION(BlueprintCallable, Category="Ability System")
    void GiveAbility(TSubclassOf Ability);

实现

void ACharacterBase::GiveAbility(TSubclassOf Ability)
{
    if(AbilitySystem)
    {
        if(HasAuthority() && Ability)
        {
            AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability, 1));
        }
        AbilitySystem->InitAbilityActorInfo(this, this);
    }
}

蓝图中调用的例子

12.png

4.创建和实现基础的AttributeSet

在第一步我们实际上还用不到Attribute,但方便起见还是先创建一下。命名为AttributeSetBase

13.png

打开。这里我只展现了创建Attribute:Health和MaxHealth的方法。

最前面的define是一种mecro方法,它在AttributeSet中实现,它可以自动地帮你实现Attribute的getter, setter等方法。

attribute需要一个ReplicatedUsing方法。

   #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
 *
 */
UCLASS()
class GAS__API UAttributeSetBase : public UAttributeSet
{
   GENERATED_BODY()

public:
   UAttributeSetBase();

   virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;

   // Health and MaxHealth
   UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_Health)
   FGameplayAttributeData Health;
   ATTRIBUTE_ACCESSORS(UAttributeSetBase, Health);

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

   UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_MaxHealth)
   FGameplayAttributeData MaxHealth;
   ATTRIBUTE_ACCESSORS(UAttributeSetBase, MaxHealth);

   UFUNCTION()
   virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
};
void UAttributeSetBase::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
{
   Super::GetLifetimeReplicatedProps(OutLifetimeProps);

   DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
   DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, MaxHealth, COND_None, REPNOTIFY_Always);
}

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

void UAttributeSetBase::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
   GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MaxHealth, OldMaxHealth);
}

最后一步是把AttributeSet添加给角色。

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities")
    UAttributeSetBase* AttributeSet;
AttributeSet = CreateDefaultSubobject("Attribute Set");

OK,到这一步角色的基础GAS配置就完成了

5.测试

创建一个GameplayAbility类的蓝图,命名为BP_Test

14.png

打开后,这里完成的是启动ability,打印一个hello在屏幕上,然后结束Ability

15.png

然后打开角色的蓝图,在beginplay中调用GiveAbility函数

16.png

点击鼠标左键,启用ability。

17.png

运行游戏,点击鼠标左键应该就可以看到Hello了。

18.png

你可能感兴趣的:(虚幻四Gameplay Ability System入门(1))