げーむ開発徒然日記~怠惰のために勤勉~

Unreal Engineや3DCG制作について学んだことを記事にしていきます

対象Actorの速度を監視するAbilityTask

概要

GASにてダッシュアビリティ(GA_Run)を実装した際、ダッシュのアクション入力をすると止まっているにも関わらずGA_Runの持つState.RunningのようなGameplayTagが付与されてしまうのが気になっていました。

AbilityTaskですが、どうやらTick動作させることができるみたいなので、それを利用してActorの速度を監視するAbilityTaskを作りました。

AbilityTaskの作り方については、あいす氏の記事でわかりやすく解説されていますのでそちらをご覧ください。 また、公式の詳しい作成手順はAbilityTask.h内に示されています。

[UE] AbilityTask のつくりかた - Qiita

使用UEバージョン:5.0.3

作成するAbilityTask

今回作ったものは以下のようなものです。

AbilityTaskを実行するとTargetに接続したActorの速度(高さ方向は無視)を監視し続け、Threshold(閾値)を超えるとOnStartMovingデリゲートが発行され、再度Threshold以下となるとOnStopMovingデリゲートが発行されるといった非常にシンプルな構成になっています。

実装

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWaitGroundMovingDelegate);

UCLASS()
class YOURPROJECT_API UAbilityTask_WaitGroundMoving : public UAbilityTask
{
    GENERATED_BODY()

    UPROPERTY(BlueprintAssignable)
    FWaitGroundMovingDelegate OnStartMoving;
    UPROPERTY(BlueprintAssignable)
    FWaitGroundMovingDelegate OnStopMoving;

    virtual void TickTask(float DeltaTime) override;

    /** TargetActorのXY速度が閾値Thresholdを上回る、又は、Threshold以下となるのを待つ */
    UFUNCTION(BlueprintCallable, Category = "Ability|Custom|Tasks", meta = (DisplayName= "WaitGroundMoving", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
    static  UAbilityTask_WaitGroundMoving* CreateWaitGroundMoving(UGameplayAbility* OwningAbility, AActor* Target, float Threshold);

    virtual void Activate() override;

protected:
    UPROPERTY()
    AActor* TargetActor;
    
    float Threshold;

private:
    bool bIsMoving;
};
#include "Kismet/KismetMathLibrary.h"

void UAbilityTask_WaitGroundMoving::TickTask(float DeltaTime)
{
    if (TargetActor)
    {
        float GroundSpeed = UKismetMathLibrary::VSizeXY(TargetActor->GetVelocity());

        if (GroundSpeed > Threshold)
        {
            if (!bIsMoving)
            {
                if (ShouldBroadcastAbilityTaskDelegates())
                {
                    OnStartMoving.Broadcast();
                }
                bIsMoving = true;
            }
        }
        else
        {
            if (bIsMoving)
            {
                if (ShouldBroadcastAbilityTaskDelegates())
                {
                    OnStopMoving.Broadcast();
                }
                bIsMoving = false;
            }
        }
    }
    else
    {
        ABILITY_LOG(Warning, TEXT("UAbilityTask_WaitGroundMoving ticked without a valid Actor. ending."));
        EndTask();
    }
}

UAbilityTask_WaitGroundMoving* UAbilityTask_WaitGroundMoving::CreateWaitGroundMoving(
    UGameplayAbility* OwningAbility, AActor* InTarget, float InThreshold)
{
    auto* MyObj = NewAbilityTask<ThisClass>(OwningAbility);

    MyObj->bTickingTask = true;  //Tick動作に必要
    MyObj->TargetActor = InTarget;
    MyObj->Threshold = InThreshold;

    return MyObj;
}

void UAbilityTask_WaitGroundMoving::Activate()
{
    SetWaitingOnRemotePlayerData();
}