Gameplay Ability System コードリーディング ② SpawnActor

Gameplay Ability System コードリーディング ① WaitOverlap - You are done!

①では簡単な WaitOverlap を見ましたが、今回はそれよりも複雑(らしい)WaitTargetData を見ていきたいと思いました、が。

WaitTargetData に行く前に

前回確認した AbilityTask の基本要項にあったものに以下がありました。

Implement a Activate() function (defined here in base class). This function should actually start/execute your task logic. It is safe to invoke callback delegates here.

今回は、AbilityTask.h 上記の記述のさらに下にある部分も見る必要が出てきます。

We have additional support for AbilityTasks that want to spawn actors. Though this could be accomplished in an Activate() function, it would not be possible to pass in dynamic "ExposeOnSpawn" actor properties. This is a powerful feature of blueprints, in order to support this, you need to implement a different step 3:

Instead of an Activate() function, you should implement a BeginSpawningActor() and FinishSpawningActor() function.

BeginSpawningActor() must take in a TSubclassOf parameters named 'Class'. It must also have a out reference parameters of type YourActorClassToSpawn*& named SpawnedActor. This function is allowed to decide whether it wants to spawn the actor or not (useful if wishing to predicate actor spawning on network authority).

BeginSpawningActor() can instantiate an actor with SpawnActorDeferred. This is important, otherwise the UCS will run before spawn parameters are set. BeginSpawningActor() should also set the SpawnedActor parameter to the actor it spawned.

[Next, the generated byte code will set the expose on spawn parameters to whatever the user has set]

If you spawned something, FinishSpawningActor() will be called and pass in the same actor that was just spawned. You MUST call ExecuteConstruction + PostActorConstruction on this actor!

This is a lot of steps but in general, AbilityTask_SpawnActor() gives a clear, minimal example.

WaitTargetData はこの AbilityTasks that want to spawn actors に該当するため、上記にあるようにまずは AbilityTask_SpawnActor を確認したいと思います。

SpawnActor

UAbilityTask_SpawnActor | Unreal Engine Documentation

ヘッダファイル

小さいので以下が全体です。

UCLASS()
class GAMEPLAYABILITIES_API UAbilityTask_SpawnActor: public UAbilityTask
{
    GENERATED_UCLASS_BODY()

    UPROPERTY(BlueprintAssignable)
    FSpawnActorDelegate Success;

    /** Called when we can't spawn: on clients or potentially on server if they fail to spawn (rare) */
    UPROPERTY(BlueprintAssignable)
    FSpawnActorDelegate DidNotSpawn;
    
    /** Spawn new Actor on the network authority (server) */
    UFUNCTION(BlueprintCallable, meta=(HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category="Ability|Tasks")
    static UAbilityTask_SpawnActor* SpawnActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> Class);

    UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Abilities")
    bool BeginSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> Class, AActor*& SpawnedActor);

    UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Abilities")
    void FinishSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, AActor* SpawnedActor);

protected:
    FGameplayAbilityTargetDataHandle CachedTargetDataHandle;
};

ざっと眺めると、BlueprintAssignabledelegate として SuccessDidNotSpawn があるのが確認できます。

続いて static ファクトリも確認できます。

そして、、Activate が存在しません。最初に貼った AbilityTask.h の説明にあるように Instead of an Activate() function, you should implement a BeginSpawningActor() and FinishSpawningActor() function. の状態です。

ソースファイル

ファクトリ

UAbilityTask_SpawnActor* UAbilityTask_SpawnActor::SpawnActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> InClass)
{
    UAbilityTask_SpawnActor* MyObj = NewAbilityTask<UAbilityTask_SpawnActor>(OwningAbility);
    MyObj->CachedTargetDataHandle = MoveTemp(TargetData);
    return MyObj;
}

引数で受け取った TargetData を Cache している以外は通常の初期化のようです。

BeginSpawningActor

bool UAbilityTask_SpawnActor::BeginSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, TSubclassOf<AActor> InClass, AActor*& SpawnedActor)
{
    if (Ability && Ability->GetCurrentActorInfo()->IsNetAuthority() && ShouldBroadcastAbilityTaskDelegates())
    {
        UWorld* const World = GEngine->GetWorldFromContextObject(OwningAbility, EGetWorldErrorMode::LogAndReturnNull);
        if (World)
        {
            SpawnedActor = World->SpawnActorDeferred<AActor>(InClass, FTransform::Identity, NULL, NULL, ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
        }
    }
    
    if (SpawnedActor == nullptr)
    {
        if (ShouldBroadcastAbilityTaskDelegates())
        {
            DidNotSpawn.Broadcast(nullptr);
        }
        return false;
    }

    return true;
}

改めてドキュメント貼ります。

BeginSpawningActor() must take in a TSubclassOf parameters named 'Class'. It must also have a out reference parameters of type YourActorClassToSpawn*& named SpawnedActor. This function is allowed to decide whether it wants to spawn the actor or not (useful if wishing to predicate actor spawning on network authority).

BeginSpawningActor() can instantiate an actor with SpawnActorDeferred. This is important, otherwise the UCS will run before spawn parameters are set. BeginSpawningActor() should also set the SpawnedActor parameter to the actor it spawned.

引数として TSubclassOf<AActor> InClass を引き受けており、AActor*& SpawnedActor として out reference parameters も持っています。

また SpawnActorDeferredインスタンス化も行っています。ここでは spawn された Actor に追加のパラメータの設定はありません。

失敗時には DidNotSpawn delegate を通知しています。

FinishSpawningActor

void UAbilityTask_SpawnActor::FinishSpawningActor(UGameplayAbility* OwningAbility, FGameplayAbilityTargetDataHandle TargetData, AActor* SpawnedActor)
{
    if (SpawnedActor)
    {
        bool bTransformSet = false;
        FTransform SpawnTransform;
        if (FGameplayAbilityTargetData* LocationData = CachedTargetDataHandle.Get(0))     //Hardcode to use data 0. It's OK if data isn't useful/valid.
        {
            //Set location. Rotation is unaffected.
            if (LocationData->HasHitResult())
            {
                SpawnTransform.SetLocation(LocationData->GetHitResult()->Location);
                bTransformSet = true;
            }
            else if (LocationData->HasEndPoint())
            {
                SpawnTransform = LocationData->GetEndPointTransform();
                bTransformSet = true;
            }
            }
        if (!bTransformSet)
        {
            SpawnTransform = AbilitySystemComponent->GetOwner()->GetTransform();
        }

        SpawnedActor->FinishSpawning(SpawnTransform);

        if (ShouldBroadcastAbilityTaskDelegates())
        {
            Success.Broadcast(SpawnedActor);
        }
    }

    EndTask();
}

こちらもドキュメントを貼ります。

If you spawned something, FinishSpawningActor() will be called and pass in the same actor that was just spawned. You MUST call ExecuteConstruction + PostActorConstruction on this actor!

Spawn された Actor を受け取って、ExecuteConstruction + PostActorConstruction を Actor に対して実行しているのが確認できます。ここでは FinishSpawning で行われています。
AActor::FinishSpawning | Unreal Engine Documentation

ここでは、最後に Success delegate を通知し、 EndTask しています。

FGameplayAbilityTargetData

ようやく気づいたのですが、これは前回の WaitOverlap で取得されたデータと同じ型です。

なので、Overlap した HitResult に対して Spawn のようなことができるということですね。(あくまで、ここまでの知識の話なので他にもやり方はいくらでもあるのだと思います)

次回

おそらくこの2つで主要なパターンの簡単なものを確認できたので、次回こそは WaitTargetData を見ていきたいと思います。