GameplayDebugger をプロジェクト用にカスタマイズして利用する
こちらは「Unreal Engine 4 (UE4) Advent Calendar 2016」3日目の記事です。
Unreal Engine 4 (UE4) Advent Calendar 2016 - Qiita
GameplayDebugger が便利なのにあまり使われていなくて可哀想なので記事にします。
GameplayDebugger とは
GameplayDebugger は様々な情報をオーバーレイ表示できます。
大層な名前ですが、その名に恥じない程に便利な機能を持つツールです。
例えばAIで考えてみましょう。
AIで動いているPawnの状態はゲームの時間経過と共に目まぐるしく変化していきます。
更に昨今では EQS(様々な条件を元に自身の周りのポイントを重み付けし、一番都合が良いポイントを探す手法)を使う機会も増えてきており、文字情報だけで状況を把握する事が困難になっています。
そこで GameplayDebugger を使うと、文字情報はもちろんのこと、シーンに対して直接オーバーレイ表示できるので、現在の状況を直感的に把握することが可能です。
なぜ GameplayDebugger はこんなにマイナーなのか
おそらくですが、日本語キーボード配列が原因です 。
GameplayDebugger を起動するデフォルトのキーは「'」(アポストロフィー)なのですが、これが日本語キーボード配列だと Shift+7 で入力できます。
しかし、どうも GameplayDebugger を起動する部分は同時押しキーの対応が行われていないようで、Shift+7 を入力しても GameplayDebugger は起動しません。
それだけで大変不便なのですが、UE4.12 までだったらコンソールコマンドから「Enable GDT」して起動する事で回避が可能でした。
それが UE4.13 からだと GameplayDebugger の大幅な設計変更の煽りを受け、コマンド自体が消滅しています。
つまりは現状だと、デフォルトの状態では起動する術がありません
(つ∀`*)アイタタ
まずは起動できるようにしよう
「Project Settings」→「Engine」→「Gameplay Debugger」を開きます。
「Activation Key」が「Apostrophe」設定になっているので、ここを何か別のキーに設定します。
今回はとりあえずこんな感じにしました。
その後、ゲームを起動し、「1」キーを入力すると GameplayDebugger が起動します。
GameplayDebugger で表示する内容を切り替える
GameplayDebugger はゲーム中いつでも表示する内容(カテゴリ)を切り替えることができます。
Behavior Tree
実行中のビヘイビアツリーやブラックボードの状態を表示します。
EQS
実行中のEQSや、各ポイントのスコアを表示します。
プロジェクト用にカスタマイズする
上記の標準機能だけでも十分便利ではあるのですが、GameplayDebugger は更にデキル子です。
GameplayDebugger で表示する内容(カテゴリ)をプロジェクトコードから追加できます!
独自のカテゴリはモジュールの Startup 時に登録できます。
今回は以下の様に、プロジェクトモジュールから独自のカテゴリ「GameplayDebuggerCategory_NekoNeko」を追加してみます。
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 値が低い時
Hate 値が高い時
これを実現するコードは以下のものです。
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 }
これで使用する準備は完了です。
おまけ
デバッグ対象アクター(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日目)は無事にゲーム業界入りを果たした らりほまさん による「らりー」です。
楽しみですらりー。