GameplayAbilityTargetingLocationInfo をよくわかっていなかった

環境

UE 5.0.3

GameplayAbilityTargetingLocationInfo

FGameplayAbilityTargetingLocationInfo | Unreal Engine Documentation

以前書いた WaitTargetData と Radius の記事 で上記のように GASDocumentation の GA_Meteor_BP から持ってきたノードをそのまま使っている部分がありました。

なんとなく分かった気になっていたのですが、いざ自分で組んでみると期待と違う動きをしたので理解できていなかったことがわかりました。

定義の確認

GameplayAbilityTargetType.h に定義されています。

/** Structure that stores a location in one of several different formats */
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayAbilityTargetingLocationInfo
{
    GENERATED_USTRUCT_BODY()

    FGameplayAbilityTargetingLocationInfo()
    : LocationType(EGameplayAbilityTargetingLocationType::LiteralTransform)
    , SourceActor(nullptr)
    , SourceComponent(nullptr)
    , SourceAbility(nullptr)
    { }

    virtual ~FGameplayAbilityTargetingLocationInfo() {}

    void operator=(const FGameplayAbilityTargetingLocationInfo& Other)
    {
        LocationType = Other.LocationType;
        LiteralTransform = Other.LiteralTransform;
        SourceActor = Other.SourceActor;
        SourceComponent = Other.SourceComponent;
        SourceAbility = Other.SourceAbility;
        SourceSocketName = Other.SourceSocketName;
    }

    /** Converts internal format into a literal world space transform */
    FTransform GetTargetingTransform() const
    {
        //Return or calculate based on LocationType.
        switch (LocationType)
        {
        case EGameplayAbilityTargetingLocationType::ActorTransform:
            if (SourceActor)
            {
                return SourceActor->GetTransform();
            }
            break;
        case EGameplayAbilityTargetingLocationType::SocketTransform:
            if (SourceComponent)
            {
                // Bad socket name will just return component transform anyway, so we're safe
                return SourceComponent->GetSocketTransform(SourceSocketName);
            }
            break;
        case EGameplayAbilityTargetingLocationType::LiteralTransform:
            return LiteralTransform;
        default:
            check(false);
            break;
        }

        // It cannot get here
        return FTransform::Identity;
    }

    /** Initializes new target data and fills in with hit results */
    FGameplayAbilityTargetDataHandle MakeTargetDataHandleFromHitResult(TWeakObjectPtr<UGameplayAbility> Ability, const FHitResult& HitResult) const;
    FGameplayAbilityTargetDataHandle MakeTargetDataHandleFromHitResults(TWeakObjectPtr<UGameplayAbility> Ability, const TArray<FHitResult>& HitResults) const;

    /** Initializes new actor list target data, and sets this as the origin */
    FGameplayAbilityTargetDataHandle MakeTargetDataHandleFromActors(const TArray<TWeakObjectPtr<AActor>>& TargetActors, bool OneActorPerHandle = false) const;

    /** Type of location used - will determine what data is transmitted over the network and what fields are used when calculating position. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    TEnumAsByte<EGameplayAbilityTargetingLocationType::Type> LocationType;

    /** A literal world transform can be used, if one has been calculated outside of the actor using the ability. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    FTransform LiteralTransform;

    /** A source actor is needed for Actor-based targeting, but not for Socket-based targeting. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    AActor* SourceActor;

    /** Socket-based targeting requires a skeletal mesh component to check for the named socket. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    UMeshComponent* SourceComponent;

    /** Ability that will be using the targeting data */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    UGameplayAbility* SourceAbility;

    /** If SourceComponent is valid, this is the name of the socket transform that will be used. If no Socket is provided, SourceComponent's transform will be used. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    FName SourceSocketName;

    /** Optimized serialize function */
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};

Blueprint 公開部分

上記のコードからです。

 /** Type of location used - will determine what data is transmitted over the network and what fields are used when calculating position. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    TEnumAsByte<EGameplayAbilityTargetingLocationType::Type> LocationType;

    /** A literal world transform can be used, if one has been calculated outside of the actor using the ability. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    FTransform LiteralTransform;

    /** A source actor is needed for Actor-based targeting, but not for Socket-based targeting. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    AActor* SourceActor;

    /** Socket-based targeting requires a skeletal mesh component to check for the named socket. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    UMeshComponent* SourceComponent;

    /** Ability that will be using the targeting data */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    UGameplayAbility* SourceAbility;

    /** If SourceComponent is valid, this is the name of the socket transform that will be used. If no Socket is provided, SourceComponent's transform will be used. */
    UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting)
    FName SourceSocketName;

ノードの情報と一致しますね。SourceSocketNameSourceComponent と連動して利用されるようです。

続いて LocationType の型である EGameplayAbilityTargetingLocationType を確認します。

EGameplayAbilityTargetingLocationType

UENUM(BlueprintType)
namespace EGameplayAbilityTargetingLocationType
{
    /** What type of location calculation to use when an ability asks for our transform */
    enum Type
    {
        /** We report an actual raw transform. This is also the final fallback if other methods fail */
        LiteralTransform        UMETA(DisplayName = "Literal Transform"),

        /** We pull the transform from an associated actor directly */
        ActorTransform          UMETA(DisplayName = "Actor Transform"),

        /** We aim from a named socket on the player's skeletal mesh component */
        SocketTransform         UMETA(DisplayName = "Socket Transform"),      
    };
}

LiteralTransform は 他を指定した時に、失敗しても Fallback として利用されるとあります。ということは、FGameplayAbilityTargetingLocationInfoLiteralTransform プロパティにもなんらかは指定しておいた方が無難ということか FTransform なのでまあ何も指定しなくてもデフォルトの値が使われるということでしょうか。

LiteralTransformactual raw transform とありますが、よくわかりません。ただ、 FTransform を直接プロパティ値として受け取るので、そこで評価された値をそのまま利用するということなんでしょうか。(Literal って単語は解釈が毎度分からず苦手です 😅)

ちなみに、デフォルトコンストラクタでは LocationTypeLiteralTransform が指定されています。

GetTargetingTransform

おそらくここが値を利用する箇所になるかと思います。

 /** Converts internal format into a literal world space transform */
    FTransform GetTargetingTransform() const
    {
        //Return or calculate based on LocationType.
        switch (LocationType)
        {
        case EGameplayAbilityTargetingLocationType::ActorTransform:
            if (SourceActor)
            {
                return SourceActor->GetTransform();
            }
            break;
        case EGameplayAbilityTargetingLocationType::SocketTransform:
            if (SourceComponent)
            {
                // Bad socket name will just return component transform anyway, so we're safe
                return SourceComponent->GetSocketTransform(SourceSocketName);
            }
            break;
        case EGameplayAbilityTargetingLocationType::LiteralTransform:
            return LiteralTransform;
        default:
            check(false);
            break;
        }

        // It cannot get here
        return FTransform::Identity;
    }

この関数から以下のような関係性というのが確認できました。

LocationType 利用するプロパティ
LiteralTransform LiteralTransform
ActorTransform SourceActor (fallback で LiteralTransform)
SocketTransform SourceComponent, SourceSocketName (fallback で LiteralTransform)
SourceSocketName 未指定の場合は SourceComponent の Transform を利用

ただ、ここだけ見ると fallback で LiteralTransform ではなく、単に FTransform::Identity が返されるだけですね。

ここまでで主要な部分は確認できました。(NetSerialize はスキップします)

続いて、この構造体を作るために用意された関数群を見ていきます。

MakeTargetLocationInfoFrom~

UCLASS(Blueprintable)
class GAMEPLAYABILITIES_API UGameplayAbility : public UObject, public IGameplayTaskOwnerInterface
{
~略
    // ----------------------------------------------------------------------------------------------------------------
    // Target Data
    // ----------------------------------------------------------------------------------------------------------------

    /** Creates a target location from where the owner avatar is */
    UFUNCTION(BlueprintPure, Category = Ability)
    FGameplayAbilityTargetingLocationInfo MakeTargetLocationInfoFromOwnerActor();

    /** Creates a target location from a socket on the owner avatar's skeletal mesh */
    UFUNCTION(BlueprintPure, Category = Ability)
    FGameplayAbilityTargetingLocationInfo MakeTargetLocationInfoFromOwnerSkeletalMeshComponent(FName SocketName);
FGameplayAbilityTargetingLocationInfo UGameplayAbility::MakeTargetLocationInfoFromOwnerActor()
{
    FGameplayAbilityTargetingLocationInfo ReturnLocation;
    ReturnLocation.LocationType = EGameplayAbilityTargetingLocationType::ActorTransform;
    ReturnLocation.SourceActor = GetActorInfo().AvatarActor.Get();
    ReturnLocation.SourceAbility = this;
    return ReturnLocation;
}

FGameplayAbilityTargetingLocationInfo UGameplayAbility::MakeTargetLocationInfoFromOwnerSkeletalMeshComponent(FName SocketName)
{
    FGameplayAbilityTargetingLocationInfo ReturnLocation;
    ReturnLocation.LocationType = EGameplayAbilityTargetingLocationType::SocketTransform;
    ReturnLocation.SourceComponent = GetActorInfo().SkeletalMeshComponent.Get();
    ReturnLocation.SourceAbility = this;
    ReturnLocation.SourceSocketName = SocketName;
    return ReturnLocation;
}

ヘッダとソースを貼っています。

見てわかる通りで、EGameplayAbilityTargetingLocationType のうち、LiteralTransform を除いた 2 つについて関数が用意されています。

GameplayAbility の関数であるため、関連する Actor にも簡単にアクセスできるようになっており、コードとしても特にややこしい部分はありません。

SourceAbility でしっかり this が指定されていることからも、SourceAbilityEGameplayAbilityTargetingLocationType に関係なく必要なことが分かります。

あれ、GA_Meteor_BP のノードって?

さて、ここで改めて GA_Meteor_BP からコピーしてきたノードを見てみます。

まず、MakeTargetLocationInfoFromOwnerActor で作成されていることが分かります。つまり、作成時点では EGameplayAbilityTargetingLocationTypeActorTransform になっているということです。

にもかかわらず、LiteralTransformLocation が変更されています。ここまで読んできた内容どおりで、最終的に GetTargetingTransform を経由して取得するならば、ここで LiteralTransform を変更したところでその値が使われることはありません。

GA_Meteor_BP の奥の cpp のコードまで読んでいないので、実際この LiteralTransform の変更が全くの無駄なのか、または GetTargetingTransform は使わずに直接 LiteralTransform から値を読んで使っているのかは分かりません。

ただ、何も考えずにコピーしてきた私が良くなかったのは確かです 😥 反省します。。