2015年9月9日

UE4 Visual Logger のご紹介

作成 Joe Graf

ゲームクラスのバグの中には、ユーザーからの報告だけでは追跡しにくいものがあります。そのようなバグには必ずといっていいほど、その時のゲーム ステートに応じた AI の判断が伴う複雑なステップが絡んでいます。このような症状の場合、ユーザーは問題点の見える部分だけしか報告することができません。そのため、問題が特定のゲームプレイ ステートに関係している場合、原因追跡が非常に難しくなります。そのような状況でも対応可能なツールが、UE4 の Visual Logger です。コアとなる機能はこれまで目にしてきたようなログ システムと同じで、ログ対象のアクタからステートをキャプチャし、それをインゲームまたはエディタで表示します。このツールのすごいところは、問題発生時のゲームプレイ ステートをユーザーからの報告と一緒に表示する点です。データが視覚化されれば、そのデータと基本コードから期待される動きと比較することができます。

[Windows] から [Developer Tools]、[Visual Logger] を選択して、実際の Visual Logger の動作を見てみましょう。バージョンが 4.7 以前の場合は、コンソールコマンド "VisLog" を使います。これは StrategyGame のセッションです。上が Visual Logger、下がエディタのビューポートの画像です。選択中の AI のパスが紫の線、そしてタイムラインで選択したポイントが赤の位置マーカーで表示されていることが分かります。


動作中の Visual Logger


エディタのレベル ビューポート内に Visual Logger 情報が表示されています

下の画像の黄色く囲まれた部分に記録されたセッションの中で Visual Logger へ情報を記録したアクタが一覧表示されます。検索バーもあるので、名前で簡単にアクタが見つかります。


アクタ一覧と検索オプション

今度はタイムライン ビューを見てみましょう。画像では、スクラバーの 23.53 秒にマークがあります。アクタ StrategyAIController_1 が選択されているので、ここ以外のエリアには、その時間の StrategyAIController_1 に関する情報が表示されます。色付のバーが記録されたイベントになります。タイムバーを前後にスクラブして、特定のスナップショットで他のエリアを更新することができます。


タイムライン エリア

下の画像をご覧ください。タイムラインで指定された時間に Visual Logger がこのアクタに対してキャプチャしたスナップショット データは、左下の枠内にすべて表示されます。このデータは、アクタが Visual Logger による情報を要求している一定のフレームごとに UE_VLOG() マクロによってキャプチャされます。同一フレーム内で Visual Logger の情報が複数回要求されると、そのフレームに対しては同じデータが使用されます。スナップショットの一部としてキャプチャされたデータは、ゲーム用に分類およびカスタマイズすることができます (コードのサンプルは後で出てきます)。


カスタム カテゴリを展開した状態のアクタ スナップショット エリア

次は Visual Logger のログ エリアの説明です。この黄色い枠内には、ログ、およびログ メッセージそのものが書き出されるカテゴリが表示されます。フレームごとに複数のログ メッセージがある場合は、このエリアに一覧表示されます。


メッセージが表示されるエリア

以上が Visual Logger の主要なエリアの説明です。次は、ゲームでのサポートの追加方法を見ていきましょう。この画像は、First Person Template を使って GDC のプレゼンテーション用に作成した GDC という名前のプロジェクトです。アクタに関するステート情報のキャプチャ用の関数と、Visual Logger のキャプチャをトリガーする UE_VLOG() マクロ コールを 1 つずつ追加しました。


サンプルデータが入った Visual Logger

ツールのスナップショット エリアを埋めるために、GrabDebugSnapshot() という関数をオーバーライドする必要があります。この関数はアクタの一部として実装されているので、カスタム情報が必要なければこのステップは飛ばしてください。Visual Logger はビルド設定に合わせてコンパイルすることができるので、正しいヘッダで関数宣言をラップする必要があります。スナップショット サポート用の GDCCharacter.h にコードを追加してみました。

#if ENABLE_VISUAL_LOG

    /** Appends information about this actor to the visual logger */

    virtual void GrabDebugSnapshot(FVisualLogEntry* Snapshot) const override;

#endif

このメソッドを実装すると、以下のようにカテゴリが 1 つと、そのカテゴリ内にエントリが 1 つ追加されます。ビルド設定が Visual Logger サポートをコンパイルするといけないので、ここでもまた、正しい #ifdef でラップします。このコードが GDCCharacter.cpp に追加されます。

#if ENABLE_VISUAL_LOG

void AGDCCharacter::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const

{

    Super::GrabDebugSnapshot(Snapshot);

    const int32 CatIndex = Snapshot->Status.AddZeroed();

    FVisualLogStatusCategory& PlaceableCategory = Snapshot->Status[CatIndex];

    PlaceableCategory.Category = TEXT("GDC Sample");

    PlaceableCategory.Add(TEXT("Projectile Class"), ProjectileClass != nullptr ?ProjectileClass->GetName() :TEXT("None"));

}

#endif

この関数は、アクタが Visual Logger にデータを記録すると呼び出されます。サンプルとして分かりやすいように、First Person Template コードへ簡単にトリガーと追加ができるように発射物の発射を記録しておきました。下の OnFire() function では UE_VLOG() マクロが使われていることに注目してください。このようにして、アクタのデータをキャプチャするように Visual Logger に指示します。繰り返しになりますが、マクロを最初に使う時、Visual Logger は GrabDebugSnapshot() を呼び出してスナップショット ペインに表示する必要のあるデータを収集します。他の UE_LOG() マクロと同様にこのマクロもコンパイルを行うので、 #ifdef に明確にラップする必要はありません。

void AGDCCharacter::OnFire()

{

    // try and fire a projectile

    if (ProjectileClass != NULL)

    {

        const FRotator SpawnRotation = GetControlRotation();

        // MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position

        const FVector SpawnLocation = GetActorLocation() + SpawnRotation.RotateVector(GunOffset);

        UWorld* const World = GetWorld();

        if (World != NULL)

        {

            // spawn the projectile at the muzzle

            World->SpawnActor<AGDCProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);

            UE_VLOG(this, LogFPChar, Verbose, TEXT("Fired projectile (%s) from location (%s) with rotation (%s)"),

                *ProjectileClass->GetName(),

                *SpawnLocation.ToString(),

                *SpawnRotation.ToString());

        }

    }

    // try and play the sound if specified

    if (FireSound != NULL)

    {

        UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());

    }

    // try and play a firing animation if specified

    if(FireAnimation != NULL)

    {

        // Get the animation object for the arms mesh

        UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();

        if(AnimInstance != NULL)

        {

            AnimInstance->Montage_Play(FireAnimation, 1.f);

        }

    }

}

テキスト情報の記録だけでなく、一番最初の StrategyGame のレベル ビューポートの画像で表示されていた視覚形状情報も記録することができます。ゲームの中で何が起こっているかを視覚化する優れた機能です。このような種類の形状を使って、視覚的な記録を行います。


パス情報、シリンダー、コーン、カプセル、ボックスのサンプル形状

形状の記録には、以下のマクロが使われています。

  • UE_VLOG_SEGMENT
  • UE_VLOG_LOCATION
  • UE_VLOG_BOX (axis aligned box)
  • UE_VLOG_OBOX (oriented box)
  • UE_VLOG_CONE
  • UE_VLOG_CYLINDER
  • UE_VLOG_CAPSULE

 

これらのマクロは、形状情報を記録するために異なるパラメータを要求します。VisualLogger.h でマクロを検索して、記録するにはそれぞれ何が必要かを確認することができます。

これらのファイルをディスクに保存して、後からセッションのデバッグを行うことができるのも Visual Logger ならではの機能です。QA チームがこれらのセッションを常に記録している場合、作成したエントリーの不具合追跡に適用することができます。その後で対応するレベルと Visual Logger ファイルをロードして何が起きているのか確認することができます。Visual Logger で、デバッグ処理がもっと楽に行うことができるようになります。