【UE4メモ】C++初心者がUE4エンジンソースを解析してみよう SetActorLocation編

  • 5
    いいね
  • 0
    コメント

C++初心者がUE4の挙動を覚えるために無謀にもエンジンソースの解析を試みた記事になります。
C++初心者ゆえ、間違いがあると思うのでやんわりと指摘していただけると助かります。

今回はSetActorLocationを解析してみます。
検証は4.15プレビュー版で行っています。
将来処理が変わる可能性大なので、参考程度にしてください。

SetActorLocationを解析してみる

BP上でのSetActorLocationを見てみましょう。

SetActorLocation.png

いつもお世話になっています。
BPでのSetActorLocationノードにあたる、C++のメソッドは以下だと推測されます。

Actor.cpp

bool AActor::K2_SetActorLocation(FVector NewLocation, bool bSweep, FHitResult& SweepHitResult, bool bTeleport)
{
    return SetActorLocation(NewLocation, bSweep, (bSweep ? &SweepHitResult : nullptr), TeleportFlagToEnum(bTeleport));
}

BPとC++の引数との違いを書いてみました。

ピン BP引数名 BP型名 C++引数名 C++型名
入力 New Location Vector NewLocation FVector
入力 Sweep bool bSweep bool
出力 Sweep Hit Result Hit Result (構造体) SweepHitResult FHitResult&
入力 Teleport bool bTeleport bool

※BP型名はエディター上で確認できる型名となります。

どうやらc++のクラスにもSetActorLocationメソッドがあるみたいです。

Sweepがtrueの場合、SweepHitResultのアドレスを渡しているみたいです。
つまり、Sweepがfalseである場合、SweepHitResultは常にnullptrであると推測できます。

TeleportはTeleportFlagToEnumという名前のメソッドを呼んで相手のメソッドに渡しているみたいです。

EngineTypes.h
FORCEINLINE ETeleportType TeleportFlagToEnum(bool bTeleport) { return bTeleport ? ETeleportType::TeleportPhysics : ETeleportType::None; }

戻り値がETeleportTypeという列挙型みたいですね。

K2_SetActorLocationの解析はさっと終わったので、SetActorLocationメソッドの中身を読んでみます。

Actor.cpp

bool AActor::SetActorLocation(const FVector& NewLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
{
    if (RootComponent)
    {
        const FVector Delta = NewLocation - GetActorLocation();
        return RootComponent->MoveComponent(Delta, GetActorQuat(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);
    }
    else if (OutSweepHitResult)
    {
        *OutSweepHitResult = FHitResult();
    }

    return false;
}

必要ないと思いますが、BPでのSetActorLocationとC++のSetActorLocationの引数の違いです。

ピン BP引数名 BP型名 C++引数名 C++型名
入力 New Location Vector NewLocation (const) FVector&
入力 Sweep bool bSweep bool
出力 Sweep Hit Result Hit Result(構造体) OutSweepHitResult FHitResult*
入力 Teleport bool Teleport ETeleportType

ざっと簡単に解析してみます。
まず、最初のif文でRootComponentに変数のアドレスが設定されているかどうかを判断しているみたいです。

Actor.h
USceneComponent* RootComponent;

どうやらLocationを設定するためにはRootComponentにアドレスが設定されていることが必須のようです。
続いて、GetActorLocationメソッドの中身を追ってみます。

Actor.h
    /** Returns the location of the RootComponent of this Actor*/ 
    FORCEINLINE FVector GetActorLocation() const
    {
        return TemplateGetActorLocation(RootComponent);
    }

さらにTemplateGetActorLocationメソッドを読んでるみたいです。このメソッドの中身を見てみます。

Actor.h
    /*~
     * Returns location of the RootComponent 
     */ 
    template<class T>
    static FORCEINLINE FVector TemplateGetActorLocation(const T* RootComponent)
    {
        return (RootComponent != nullptr) ? RootComponent->GetComponentLocation() : FVector::ZeroVector;
    }

これを見ると、RootComponentにアドレス設定している場合はポインタに設定されたGetComponentLocationメソッドを呼び出し、ない場合はZeroVectorを返しているみたいですね。

GetComponentLocationメソッドを追ってみました。

SceneComponent.h
    /** Return location of the component, in world space */
    FORCEINLINE FVector GetComponentLocation() const
    {
        return ComponentToWorld.GetLocation();
    }
TransformVectorized.h
    FORCEINLINE FVector GetLocation() const
    {
        return GetTranslation();
    }

    /**
     * Returns the translation component
     *
     * @return The translation component
     */
    FORCEINLINE FVector GetTranslation() const
    {
        DiagnosticCheckNaN_Translate();
        FVector OutTranslation;
        VectorStoreFloat3(Translation, &OutTranslation);
        return OutTranslation;
    }
UnrealMathSSE.h
/**
 * Stores the XYZ components of a vector to unaligned memory.
 *
 * @param Vec   Vector to store XYZ
 * @param Ptr   Unaligned memory pointer
 */
FORCEINLINE void VectorStoreFloat3( const VectorRegister& Vec, void* Ptr )
{
    union { VectorRegister v; float f[4]; } Tmp;
    Tmp.v = Vec;
    float* FloatPtr = (float*)(Ptr);
    FloatPtr[0] = Tmp.f[0];
    FloatPtr[1] = Tmp.f[1];
    FloatPtr[2] = Tmp.f[2];
}

なるほど!! わからない!! 
すみません、自分のスキル不足です。

おそらく自分の現在値(Trancform)をメモリから取得しているのかな?
そんな感じがします。

これにより新しい座標から、現在の座標を引いていると推測できます。
それからMoveComponentメソッドを呼んでいるので、新しい座標へ移動させている感じですかね。

MoveComponentメソッドを追ってみましょう。

SceneComponent.h
FORCEINLINE_DEBUGGABLE bool USceneComponent::MoveComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* Hit, EMoveComponentFlags MoveFlags, ETeleportType Teleport)
{
    return MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport);
}
SceneComponent.cpp
bool USceneComponent::MoveComponentImpl(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit, EMoveComponentFlags MoveFlags, ETeleportType Teleport)
{
    SCOPE_CYCLE_COUNTER(STAT_MoveComponentSceneComponentTime);

    // static things can move before they are registered (e.g. immediately after streaming), but not after.
    if (IsPendingKill() || CheckStaticMobilityAndWarn(SceneComponentStatics::MobilityWarnText))
    {
        if (OutHit)
        {
            *OutHit = FHitResult();
        }
        return false;
    }

    // Fill in optional output param. SceneComponent doesn't sweep, so this is just an empty result.
    if (OutHit)
    {
        *OutHit = FHitResult(1.f);
    }

    ConditionalUpdateComponentToWorld();

    // early out for zero case
    if( Delta.IsZero() )
    {
        // Skip if no vector or rotation.
        if (NewRotation.Equals(ComponentToWorld.GetRotation(), SCENECOMPONENT_QUAT_TOLERANCE))
        {
            return true;
        }
    }

    // just teleport, sweep is supported for PrimitiveComponents. This will update child components as well.
    const bool bMoved = InternalSetWorldLocationAndRotation(GetComponentLocation() + Delta, NewRotation, false, Teleport);

    // Only update overlaps if not deferring updates within a scope
    if (bMoved && !IsDeferringMovementUpdates())
    {
        // need to update overlap detection in case PrimitiveComponents are attached.
        UpdateOverlaps();
    }

    return true;
}

うむむ、どうやらここでActorを移動させているのは間違いないみたいです。
完全に解析しきれてないので次回以降、ここはじっくりと読みます。

RootComponentにアドレスが設定されてない場合、OutSweepHitResultにアドレスが設定されているかチェックしています。
もし、設定されている場合はFHitResultをMoveComponentImplメソッドでメモリに設定された値から取得しているようです。

これで大体、自分なりにSetActorLocationの挙動を理解しました。

まとめ

今回からボチボチと勉強のためにエンジンソースを読み始めたいと思います。C++がまったくわからないので、意味を調べながらになるので更新は遅いと思いますが、2017年中にある程度エンジン処理を把握できればなと思います。
正直全部は時間がいくらあっても見れないと思うので、普段よく使うBPノードから解析していきたいと思っています。