UE4 C++ GameplayAbilitiesを勉強していくPart.1

やあ

GameplayAbilitiesを使おうと思い立って、ActionRPGサンプルのコードを見てたら頭が痛くなったので、小さいところから徐々に勉強していくことにしたよ
完全に理解して、ActionRPGを解説できるようになるぞ!()
f:id:pto8913:20200526223340p:plain

間違ってることはコメントで教えてください!

セットアップ

f:id:pto8913:20200526210343p:plain
f:id:pto8913:20200526210125p:plain
GameplayAbilitiesモジュールがビルドされるようにする。
xx.build.csに追加

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class AbilityTest : ModuleRules
{
    public AbilityTest(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

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

追加したら、(必要ないかもだけど).uprojectを右クリックして、Generated Visual Studio Project Files
f:id:pto8913:20200526211610p:plain

キャラクターを作る

f:id:pto8913:20200526211923p:plain

AbilitySystemComponentについて

GameplayAbilitySystemを使う上で、なくてはならないコンポーネント!
GameplayAbilitiesを使用したり、Attributesを持っていたり、GameplayEffectを受け取ったりしたいアクターは必ず、AbilitySystemComponentを持っていないといけないよ。
・これを使うアクターはAbilitySystemInteracterを継承する必要があるよ。

CharacterBase.hに追加

// Copyright(C)write by pto8913. 2020. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "CharacterBase.generated.h"

class UAbilitySystemComponent;

UCLASS()
class ABILITYTEST_API ACharacterBase 
    : public ACharacter, public IAbilitySystemInterface
{
    GENERATED_UCLASS_BODY()
public:
    // Begin Init
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ability")
        UAbilitySystemComponent* AbilitySystemComp;
    /* Implement IAbilitySystemInterface */
    UAbilitySystemComponent* GetAbilitySystemComponent() const override;
    // End Init
};

CharacterBase.cppに追加

// Copyright(C)write by pto8913. 2020. All Rights Reserved.

#include "Abilities/Characters/Bases/CharacterBase.h"

#include "AbilitySystemComponent.h"

/////////////////////////////////////////
// Init

ACharacterBase::ACharacterBase(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    AbilitySystemComp = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilityComp"));
    AbilitySystemComp->ReplicationMode = EGameplayEffectReplicationMode::Full;
}

UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent() const
{
    return AbilitySystemComp;
}

AbilitySystemComp->ReplicationMode = EGameplayEffectReplicationMode::Full;
これは、(多分)シングルプレイヤー用で、すべてのGameplayEffectをクライアントに複製するってこと。

初期化

AbilitySystemComponentPawnがコントローラに所有された後に、初期化します。
PossessedByを使います。これはコントローラーが所有されたよ!って教えてくれるものです。

CharacterBase.hに追加

public:
    virtual void PossessedBy(AController* NewController) override;

CharacterBase.cppに追加

void ACharacterBase::PossessedBy(AController* NewController)
{
    Super::PossessedBy(NewController);

    if (IsValid(AbilitySystemComp) == true)
    {
        AbilitySystemComp->InitAbilityActorInfo(this, this);
    }
}

上のように書いたけど、環境次第でPawnの生成後にコントローラーの所有処理が起こることもあるようなので、OnRep_ControllerActorInfoを更新するよ。
OnRep_ControllerについてはPawn及びControllerクラスを参照してください。

CharacterBase.hに追加

public:
    virtual void OnRep_Controller() override;

CharacterBase.cppに追加

void ACharacterBase::OnRep_Controller()
{
    Super::OnRep_Controller();

    if (IsValid(AbilitySystemComp) == true)
    {
        AbilitySystemComp->RefreshAbilityActorInfo();
    }
}

アビリティをもたせる

GameplayAbilityについて

・アクターがゲーム内でできるアクションやスキルのこと!
例えば、銃を撃つ、全力疾走など、複数のGameplayAbilityを一度に使用できる!

CharacterBase.hに追加

class UGameplayAbility;

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Ability")
        TArray<TSubclassOf<UGameplayAbility>> GameplayAbilities;

初期化

AbilitySystemComponentと同じタイミングでやるよ。
特に深い意味はないんだけど、処理の流れとしてActorInfoの登録->初期化のほうが自然な気がするからね。

CharacterBase.hに追加

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Ability")
        TArray<TSubclassOf<UGameplayAbility>> GameplayAbilities;
    void AddStartupGameplayAbilities();
protected:
    UPROPERTY(EditAnywhere, Replicated, Category = "Ability")
        int32 ExistsLevel = 1;

    UPROPERTY()
        int32 bAbilitiesInitialzied;

CharacterBase.cppに追加

void ACharacterBase::PossessedBy(AController* NewController)
{
    Super::PossessedBy(NewController);

    if (IsValid(AbilitySystemComp) == true)
    {
        AbilitySystemComp->InitAbilityActorInfo(this, this);
        AddStartupGameplayAbilities();
    }
}

void ACharacterBase::AddStartupGameplayAbilities()
{
    if (IsValid(AbilitySystemComp) == true)
    {
        for (TSubclassOf<UGameplayAbility>& StartupAbility : GameplayAbilities)
        {
            AbilitySystemComp->GiveAbility(FGameplayAbilitySpec(StartupAbility, ExistsLevel, INDEX_NONE, this));
        }

        bAbilitiesInitialized = true;
    }
}

ここまでの処理を試してみる

TPテンプレートで自動的に作られるキャラクターの親クラスをCharacterBaseに変更。

AbilityTestCharacter.h

#include "Abilities/Characters/Bases/CharacterBase.h"

class AAbilityTestCharacter : public ACharacterBase
{
};

ビルドしてエディタに移動。
※もしかしたらここで、GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps)のリンカエラーが出るかもしれません。一度クリーンして、再ビルドしてみてください。それでもエラーが出る場合はコードを追加してください。

適当にアビリティをつくる

f:id:pto8913:20200527110330p:plain
f:id:pto8913:20200527111800p:plain
f:id:pto8913:20200527111723p:plain

キャラクターにアビリティを設定する

f:id:pto8913:20200527111920p:plain
f:id:pto8913:20200527112026p:plain

とりあえずプレイ。想定通りの動作をしているか確認。
今のところ1を押したらBegin Jumpが表示され、3秒後にEnd Jumpが表示されます。

f:id:pto8913:20200527112555p:plain
f:id:pto8913:20200527112605p:plain

表示された!

次に、アニメを再生するよー。
Jump_Startからアニムモンタージュを作成。
f:id:pto8913:20200527112913p:plain
これだけだと短すぎてわからないので、Jump_LoopJump_Endを追加
f:id:pto8913:20200527120602p:plain

ThirdPerson_AnimBPのアニムグラフに追加。
f:id:pto8913:20200527113328p:plain

GA_Jumpからモンタージュを実行する。
f:id:pto8913:20200527113510p:plain

いざプレイ。
f:id:pto8913:20200527120800g:plain

うまくいってるけど、キーを連打すると、最初から再生され、してほしい動作と違った動作になるね。

ここで役に立つのがGameplayTags!

GameplayTagsについて

GameplayTagsを使うことで、動作を制限できます。

GameplayTagsの設定

GameplayTagsの設定は、
GameplayAbilityクラスの子クラス
f:id:pto8913:20200527122657p:plain
プロジェクト設定
f:id:pto8913:20200527122844p:plain
DefaultGameplayTags.iniから直接設定
f:id:pto8913:20200527132429p:plain

主に使用されるGameplayTagsの詳細

Ability Tags : このアビリティがもつタグ
Block Abilities with Tag : このアビリティがアクティブな間、これらのタグを持つアビリティがアクティブにならないようブロックする。

実際に設定する

f:id:pto8913:20200527132628p:plain
.でつなぐことで自動的に階層ができるよ。
f:id:pto8913:20200527132722p:plain

これでGA_JumpAbility TagsAction.Movement.Jumpタグが設定されたね。

このままでは、キーを連打した際の挙動は改善しないよ。
だから、Block Abilities with TagAction.Movementを追加。
f:id:pto8913:20200527133035p:plain
これで、ダイジョーブ!

GameplayTags設定の際の注意

Block Abilities with TagAction.Movementを追加するのは、Action.Movement階層下にある、どのタグを持ったアビリティがアクティブでも、アビリティをブロックするようになるからだよ。

例えば、以下のようなGameplayTagsを設定しているとします。

f:id:pto8913:20200527133813p:plain

全部ブロックするのに、一つずつGameplayTagsを設定するのはとんでもない労力が掛かるね。

f:id:pto8913:20200527133851p:plain

後からAction.Movement下に、新しいGameplayTagsを追加したときも、さらに追加して...みたいなことになるね。
そのため、Action.MovementBlock Abilities with Tagに追加するよ。
もちろんActionを追加してもいいよ。
これは各々の実装次第だね。

f:id:pto8913:20200527134132p:plain

確認する

f:id:pto8913:20200527134522g:plain
gifだとわかりにくいね。
実際に手元で動かしてみてー。
キーを連打しても、アビリティが終わるまで、次のアビリティがアクティブになることはないはずだよー。

今回はここまで、ありがとうございました。

聞きたいこと

わかる人がいたら教えていただきたいのですが、下記のような認識であっているのでしょうか?
サーバー上でダメージの計算や、エフェクトの発生、サウンドエフェクトを行い、それらの結果をクライアントに複製して実行する。
その過程で、キャンセルやブロック処理が働くことで、複製を阻止することもできる。
(と思っているだけで実際は全然違うのかも)

次回

AttributeSetについて調べてみます。

pto8913.hatenablog.com