読者です 読者をやめる 読者になる 読者になる

ほげたつブログ

Unreal Engine 4 を使ってなんかやります

GameplayDebugger をプロジェクト用にカスタマイズして利用する

C++ UE4 開発

こちらは「Unreal Engine 4 (UE4) Advent Calendar 2016」3日目の記事です。

Unreal Engine 4 (UE4) Advent Calendar 2016 - Qiita

GameplayDebugger が便利なのにあまり使われていなくて可哀想なので記事にします。

GameplayDebugger とは

docs.unrealengine.com

GameplayDebugger は様々な情報をオーバーレイ表示できます。
大層な名前ですが、その名に恥じない程に便利な機能を持つツールです。


例えばAIで考えてみましょう。
AIで動いているPawnの状態はゲームの時間経過と共に目まぐるしく変化していきます。
更に昨今では EQS(様々な条件を元に自身の周りのポイントを重み付けし、一番都合が良いポイントを探す手法)を使う機会も増えてきており、文字情報だけで状況を把握する事が困難になっています。
そこで GameplayDebugger を使うと、文字情報はもちろんのこと、シーンに対して直接オーバーレイ表示できるので、現在の状況を直感的に把握することが可能です。

f:id:hogetatu:20161202135934p:plain


なぜ GameplayDebugger はこんなにマイナーなのか

おそらくですが、日本語キーボード配列が原因です

GameplayDebugger を起動するデフォルトのキーは「'」(アポストロフィー)なのですが、これが日本語キーボード配列だと Shift+7 で入力できます。
しかし、どうも GameplayDebugger を起動する部分は同時押しキーの対応が行われていないようで、Shift+7 を入力しても GameplayDebugger は起動しません

それだけで大変不便なのですが、UE4.12 までだったらコンソールコマンドから「Enable GDT」して起動する事で回避が可能でした。
それが UE4.13 からだと GameplayDebugger の大幅な設計変更の煽りを受け、コマンド自体が消滅しています


つまりは現状だと、デフォルトの状態では起動する術がありません

(つ∀`*)アイタタ


まずは起動できるようにしよう

「Project Settings」→「Engine」→「Gameplay Debugger」を開きます。

「Activation Key」が「Apostrophe」設定になっているので、ここを何か別のキーに設定します。
今回はとりあえずこんな感じにしました。

f:id:hogetatu:20161202231722p:plain


その後、ゲームを起動し、「1」キーを入力すると GameplayDebugger が起動します。

f:id:hogetatu:20161202033714p:plain


GameplayDebugger で表示する内容を切り替える

GameplayDebugger はゲーム中いつでも表示する内容(カテゴリ)を切り替えることができます。

Navmesh

デバッグ対象アクターが参照している Navmesh を表示します。

f:id:hogetatu:20161202143424p:plain

AI

デバッグ対象アクターのコントローラ名や、実行中のビヘイビア、アクティブなタスク等の様々なプロパティを表示します。

f:id:hogetatu:20161202143501p:plain

Behavior Tree

実行中のビヘイビアツリーやブラックボードの状態を表示します。

f:id:hogetatu:20161202143531p:plain

EQS

実行中のEQSや、各ポイントのスコアを表示します。

f:id:hogetatu:20161202143609p:plain


プロジェクト用にカスタマイズする

上記の標準機能だけでも十分便利ではあるのですが、GameplayDebugger は更にデキル子です。

GameplayDebugger で表示する内容(カテゴリ)をプロジェクトコードから追加できます!


独自のカテゴリはモジュールの Startup 時に登録できます。
今回は以下の様に、プロジェクトモジュールから独自のカテゴリ「GameplayDebuggerCategory_NekoNeko」を追加してみます。

f:id:hogetatu:20161202234208p:plain


GameplayDebugger モジュールを関連付ける

今回はプロジェクト名を NekoNekoSandbox としていますので、以後 NekoNekoSandbox という表記があった場合はプロジェクト名に置き換えてください。

NekoNekoSandbox.Build.cs に、以下のコードを追記します。

if (UEBuildConfiguration.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test))
{
    PrivateDependencyModuleNames.Add("GameplayDebugger");
    Definitions.Add("WITH_GAMEPLAY_DEBUGGER=1");
}

独自カテゴリ用のクラスを追加する

カテゴリは FGameplayDebuggerCategory を継承して作成します。
最低限用意するべきインターフェースは以下の通りです。

関数名 役割
CollectData デバッグ対象アクター(DebugActor)が引数として与えられるので、デバッグ表示に必要なプロパティを収集します。
DrawData 収集したプロパティを元にデバッグ表示を行います。


後に動画として上げているデモでは、Hateプロパティが 0 より多ければプレイヤーを追いかけるようにしています。
Hate プロパティはプレイヤーの行動により加算され、時間経過と共に減っていきます。


そこで今回はデバッグ対象アクターの Hate 値を常に画面に表示し、更にアクターの頭上に Hate 値の減少と共に赤から緑に変化する箱を描画するようにしてみました。

Hate 値が低い時

f:id:hogetatu:20161203000931p:plain

Hate 値が高い時

f:id:hogetatu:20161203001011p:plain



これを実現するコードは以下のものです。

GameplayDebuggerCategory_NekoNeko.h

#pragma once

#if WITH_GAMEPLAY_DEBUGGER

#include "GameplayDebuggerCategory.h"

class FGameplayDebuggerCategory_NekoNeko : public FGameplayDebuggerCategory
{
public:
    FGameplayDebuggerCategory_NekoNeko();

    virtual void CollectData(APlayerController* OwnerPC, AActor* DebugActor) override;
    virtual void DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) override;

    static TSharedRef<FGameplayDebuggerCategory> MakeInstance();

protected:
    struct FRepData
    {
        FVector Location;
        FRotator Rotation;
        float Hate;

        void Serialize(FArchive& Ar);
    };
    FRepData DataPack;
};

#endif // WITH_GAMEPLAY_DEBUGGER

 

GameplayDebuggerCategory_NekoNeko.cpp

#include "NekoNekoSandbox.h"
#include "Kismet/KismetMathLibrary.h"
#include "GameplayDebuggerCategory_NekoNeko.h"

#if WITH_GAMEPLAY_DEBUGGER

FGameplayDebuggerCategory_NekoNeko::FGameplayDebuggerCategory_NekoNeko()
{
    SetDataPackReplication<FRepData>(&DataPack);
}

TSharedRef<FGameplayDebuggerCategory> FGameplayDebuggerCategory_NekoNeko::MakeInstance()
{
    return MakeShareable(new FGameplayDebuggerCategory_NekoNeko());
}

void FGameplayDebuggerCategory_NekoNeko::FRepData::Serialize(FArchive& Ar)
{
    Ar << Location << Rotation << Hate;
}

void FGameplayDebuggerCategory_NekoNeko::CollectData(APlayerController* OwnerPC, AActor* DebugActor)
{
    static const FName HatePropertyName(TEXT("Hate"));

    if (!DebugActor)
    {
        return;
    }

    for (auto Property : TFieldRange<const UProperty>(DebugActor->GetClass()))
    {
        if (Property->GetFName() == HatePropertyName)
        {
            auto FloatProperty = Cast<UFloatProperty>(Property);
            if (!FloatProperty)
            {
                continue;
            }

            // DataPackに対して必要なプロパティを設定
            DataPack.Location = DebugActor->GetActorLocation();
            DataPack.Rotation = DebugActor->GetActorRotation();
            DataPack.Hate = FloatProperty->GetPropertyValue(Property->ContainerPtrToValuePtr<float>(DebugActor));
        }
    }
}

void FGameplayDebuggerCategory_NekoNeko::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext)
{
    FString HateString = FString::SanitizeFloat(DataPack.Hate);
    if (!HateString.IsEmpty())
    {
        // Hate値をスクリーンに表示
        CanvasContext.Printf(TEXT("Hate: {yellow}%s"), *HateString);

        // DebugActor上にHate値に合わせた色の箱を描画
        FLinearColor Color = UKismetMathLibrary::LinearColorLerpUsingHSV(
            FLinearColor::Green,
            FLinearColor::Red,
            FMath::Clamp(DataPack.Hate / 5.0f, 0.f, 1.f)
        );
        DrawDebugSolidBox(
            OwnerPC->GetWorld(),
            DataPack.Location + FVector(0, 0, 100),
            FVector(16, 16, 16),
            Color.Quantize()
        );
    }
}

#endif // WITH_GAMEPLAY_DEBUGGER

 

プロジェクトモジュールで GameplayDebugger に対して登録する

プロジェクトモジュールの StartupModule/ShutdownModule をオーバーライドし、GameplayDebugger に対してカテゴリの登録/解除を行います。

NekoNekoSandbox.cpp

#include "NekoNekoSandbox.h"

#if WITH_GAMEPLAY_DEBUGGER
#include "GameplayDebugger.h"
#include "GameplayDebugger/GameplayDebuggerCategory_NekoNeko.h"
#endif // WITH_GAMEPLAY_DEBUGGER

class FNekoNekoSandboxModule : public FDefaultGameModuleImpl
{
    // Begin IModuleInterface
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
};

IMPLEMENT_PRIMARY_GAME_MODULE( FNekoNekoSandboxModule, NekoNekoSandbox, "NekoNekoSandbox" );

void FNekoNekoSandboxModule::StartupModule()
{
    FDefaultGameModuleImpl::StartupModule();

#if WITH_GAMEPLAY_DEBUGGER
    IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
    GameplayDebuggerModule.RegisterCategory("NekoNeko", IGameplayDebugger::FOnGetCategory::CreateStatic(&FGameplayDebuggerCategory_NekoNeko::MakeInstance));
    GameplayDebuggerModule.NotifyCategoriesChanged();
#endif
}

void FNekoNekoSandboxModule::ShutdownModule()
{
    FDefaultGameModuleImpl::ShutdownModule();

#if WITH_GAMEPLAY_DEBUGGER
    if (IGameplayDebugger::IsAvailable())
    {
        IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
        GameplayDebuggerModule.UnregisterCategory("NekoNeko");
        GameplayDebuggerModule.NotifyCategoriesChanged();
    }
#endif
}


これで使用する準備は完了です。

こんな感じになります

www.youtube.com


検証プロジェクト

今回の検証は NekoNekoSandbox 上で行っており、公開済みです。

github.com


おまけ

デバッグ対象アクター(DebugActor)を切り替えるには、以下のコードを FunctionLibrary なりで用意すればできます。

#include "GameplayDebuggerCategoryReplicator.h"

void UGameplayDebuggerFunctionLibrary::SetGameplayDebugActor(AActor* Actor)
{
#if WITH_GAMEPLAY_DEBUGGER
    if (!Actor)
    {
        UE_LOG(LogNekoNekoGeneral, Warning, TEXT("SetGameplayDebugActor failed."));
        return;
    }

    UWorld* World = Actor->GetWorld();
    APlayerController* PlayerController = UGameplayStatics::GetPlayerController(World, 0);

    for (TActorIterator<AGameplayDebuggerCategoryReplicator> It(World); It; ++It)
    {
        AGameplayDebuggerCategoryReplicator* Replicator = *It;
        if (Replicator && !Replicator->IsPendingKill())
        {
            if (PlayerController == Replicator->GetReplicationOwner())
            {
                Replicator->SetDebugActor(Actor);
            }
        }
    }
#endif
}

 

明日のアドカレ

明日(4日目)は無事にゲーム業界入りを果たした らりほまさん による「らりー」です。
楽しみですらりー。