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

やあ

今回は、AbilityTasksについて勉強するよ。
間違いなどあれば教えてくださると助かります!

前回

pto8913.hatenablog.com

AbilityTasksについて

すでに何回もでてきてるよ。
こんなのや
f:id:pto8913:20200602151320p:plain
こんなの。
f:id:pto8913:20200602151312p:plain

GameplayAbilityは1フレームでしか実行されないので、AblilityTaskを使って、時間の経過で発生するアクションを実行したりして、いろんな状況に対処するよ。

たくさんのAbilityTaskがあるので自分で見てみてね。

注意 : AbilityTaskはコンストラクタで、UAbilityTaskのコンストラクタで、ゲーム全体で同時に1000個までとハードコードされているので注意。

AbilityTaskを自分で作る

よく使う関数

AbilityTaskの新しいインスタンスを返す静的関数
これだね。
f:id:pto8913:20200602151320p:plain
・Activate
タスクを実行する。
・OnDestroy
クリーンアップのための関数。
・コールバック関数
f:id:pto8913:20200602163611j:plain

注意 : コールバック関数に使うデリゲートは1つしか宣言できないです。

デリゲートって?

docs.unrealengine.com

タスクの作成

f:id:pto8913:20200602201513p:plain

MyAT_Test.hに追加

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

#pragma once

#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "ptoAT_Test.generated.h"

UCLASS()
class ABILITYTEST_API UptoAT_Test : public UAbilityTask
{
    GENERATED_BODY()
public:
    UptoAT_Test();

    /*
   @ OwningAbilit : このタスクを呼び出しているGameplayAbility
   @ TaskInstanceName : この関数によって返されるインスタンスの名前。なくてもいい。
   */
    UFUNCTION(BlueprintCallable, Category = "Ability|pto|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
        static UptoAT_Test* ptoTest(
            UGameplayAbility* OwningAbility,
            FName TaskInstanceName
        );
};

MyAT_Test.cppに追加

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

#include "Abilities/Tasks/ptoAT_Test.h"

UptoAT_Test::UptoAT_Test()
{

}

UptoAT_Test* UptoAT_Test::ptoTest(
    UGameplayAbility* OwningAbility,
    FName TaskInstanceName
)
{
    UptoAT_Test* MyObj = NewAbilityTask<UptoAT_Test>(
        OwningAbility, TaskInstanceName
    );

    return MyObj;
}

これで、タスクをGameplayAbilityのイベントグラフで呼び出せるようになったよ。
えぇ!?こんなに簡単に!?

GameplayAbilityについてはPart.4を見てね
適当なGameplayAbilityから確認してみてね。
f:id:pto8913:20200602203042p:plain

アニメーションモンタージュを再生できるようにする

タスクを実行できるようにする

MyAT_Test.hに追加

public:
    /* タスクを実行する */
    virtual void Activate() override;

MyAT_Test.cppに追加

#include "Engine.h"

void UptoAT_Test::Activate()
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, "Activate in ptoAT_Test");
}

タスクが実行できているかテスト

アビリティを登録して、実行できるようにする。
f:id:pto8913:20200602213617p:plain
f:id:pto8913:20200602214102g:plain

タスクの実行はできてるね。

再生するアニメーションモンタージュを登録できるようにする

MyAT_Test.hに追加

public:
    /*
   @ OwningAbility : A GameplayAbility that calls this task
   @ TaskInstanceName : The Instance name return by this func. need not be
   @ MontageToPlay : Animation Montage
   */
    UFUNCTION(BlueprintCallable, Category = "Ability|pto|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
        static UptoAT_Test* ptoTest(
            UGameplayAbility* OwningAbility,
            FName TaskInstanceName,
            UAnimMontage* MontageToPlay,
            float Rate = 1.f,
            FName StartSectionName = NAME_None
        );
private:
    UPROPERTY()
        UAnimMontage* MontageToPlay;
    UPROPERTY()
        float Rate;
    UPROPERTY()
        FName StartSectionName;

MyAT_Test.cppに追加

UptoAT_Test::UptoAT_Test()
{
    Rate = 1.f;
}

UptoAT_Test* UptoAT_Test::ptoTest(
    UGameplayAbility* OwningAbility,
    FName TaskInstanceName,
    UAnimMontage* MontageToPlay,
    float Rate,
    FName StartSectionName
)
{
    UptoAT_Test* MyObj = NewAbilityTask<UptoAT_Test>(
        OwningAbility, TaskInstanceName
    );

    MyObj->MontageToPlay = MontageToPlay;
    MyObj->Rate = Rate;
    MyObj->StartSectionName = StartSectionName;

    return MyObj;
}

アニメーションモンタージュを再生する

MyAT_Test.cppに追加

void UptoAT_Test::Activate()
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, "Activate in ptoAT_Test");
    if (!Ability) return;
    if (!AbilitySystemComponent) return;

    bool bPlayedMontage = false;

    const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
    UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
    if (AnimInstance)
    {
        AbilitySystemComponent->PlayMontage(
            Ability,
            Ability->GetCurrentActivationInfo(),
            MontageToPlay,
            Rate,
            StartSectionName
        );
    }
}

テストする

使用するモンタージュは、ThirdPersonCharacterのジャンプの一連の動作をまとめたものだよ。
f:id:pto8913:20200602221408p:plain
f:id:pto8913:20200602221554p:plain

f:id:pto8913:20200602221924g:plain
おーけー

コールバック関数を作ってみる

モンタージュが通常終了したときの処理

MyAT_Test.hに追加

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FptoTestDelegate);

class HOGE_API UMy_AT_Test : public UAbilityTest
{
public:
    UPROPERTY(BlueprintAssignable)
        FptoTestDelegate OnCompleted;
private:
    void OnMontageEnd(UAnimMontage* Montage, bool bInterrupted);
};

MyAT_Test.cppに追加

void UptoAT_Test::OnMontageEnd(UAnimMontage* Montage, bool bInterrupted)
{
    if (!bInterrupted)
    {
        if (ShouldBroadcastAbilityTaskDelegates())
        {
            OnCompleted.Broadcast();
        }
    }
    
    EndTask();
}

とりあえずうまくいっているか確認するよ。
f:id:pto8913:20200602224759p:plain
OnCompletedが追加されてるね。

ただ、いまのところ追加されただけで、バインドもしていないし、呼び出しもしてないから、何も起こらないよ。

モンタージュの終わりにバインドする

MyAT_Test.cppに追加

void UptoAT_Test::Activate()
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, "Activate in ptoAT_Test");
    if (!Ability) return;
    if (!AbilitySystemComponent) return;

    const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
    UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
    if (AnimInstance)
    {
        AbilitySystemComponent->PlayMontage(
            Ability,
            Ability->GetCurrentActivationInfo(),
            MontageToPlay,
            Rate,
            StartSectionName
        );

        MontageEndDelegate.BindUObject(
            this,
            &UptoAT_Test::OnMontageEnd
        );
        AnimInstance->Montage_SetEndDelegate(MontageEndDelegate, MontageToPlay);
    }
}

テスト

f:id:pto8913:20200602234000p:plain

f:id:pto8913:20200602234222g:plain
いぇい

アビリティがキャンセルされたときの処理

MyAT_Test.hに追加

public:
    virtual void ExternalCancel() override;

    UPROPERTY(BlueprintAssignable)
        FptoTestDelegate OnCancelled;
private:
    void OnAbilityCancelled();
    bool StopPlayingMontage();
    FDelegateHandle CancelHandle;

MyAT_Test.cppに追加

void UptoAT_Test::Activate()
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, "Activate in ptoAT_Test");
    if (!Ability) return;
    if (!AbilitySystemComponent) return;

    const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
    UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
    if (AnimInstance)
    {
        AbilitySystemComponent->PlayMontage(
            Ability,
            Ability->GetCurrentActivationInfo(),
            MontageToPlay,
            Rate,
            StartSectionName
        );

        CancelHandle = Ability->OnGameplayAbilityCancelled.AddUObject(
            this,
            &UptoAT_Test::OnAbilityCancelled
        );

        MontageEndDelegate.BindUObject(
            this,
            &UptoAT_Test::OnMontageEnd
        );
        AnimInstance->Montage_SetEndDelegate(MontageEndDelegate, MontageToPlay);
    }
}

void UptoAT_Test::ExternalCancel()
{
    check(AbilitySystemComponent);

    OnAbilityCancelled();

    Super::ExternalCancel();
}

void UptoAT_Test::OnAbilityCancelled()
{
    if (StopPlayingMontage())
    {
        if (ShouldBroadcastAbilityTaskDelegates())
        {
            OnCancelled.Broadcast();
        }
    }
}

bool UptoAT_Test::StopPlayingMontage()
{
    const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
    if (!ActorInfo) return false;

    UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
    if (!AnimInstance) return false;

    if (AbilitySystemComponent && Ability)
    {
        if ((AbilitySystemComponent->GetAnimatingAbility() == Ability)
            && (AbilitySystemComponent->GetCurrentMontage() == MontageToPlay)
            )
        {
            FAnimMontageInstance* MontageInst = AnimInstance->GetActiveInstanceForMontage(MontageToPlay);
            if (MontageInst)
            {
                MontageInst->OnMontageEnded.Unbind();
            }

            AbilitySystemComponent->CurrentMontageStop();
            return true;
        }
    }

    return false;
}

テスト

めんどうだったからタスクを呼び出した後すぐアビリティをキャンセルするようにしたよ。
f:id:pto8913:20200603102938p:plain

f:id:pto8913:20200603103215g:plain
おーけー

こんなかんじでどんどんいい感じのタスクをつくってこー
今回はここまで、ありがとうございました。

次回