ぼっちプログラマのメモ

UE4とかUE5とかについて書いたり書かなかったり。

【UE4】GUIフレームワーク「Dear ImGui」を使ってデバッグ・ツール用UIを作ってみよう! < 導入・基本編 >

本記事はUnreal Engine 4 (UE4) Advent Calendar 2020 13日目の記事 です。
今回はC++メインということもありプログラマさん向けです。非エンジニアの方はプログラマさんに実装を相談又は手伝ってもらいましょう!

はじめに

「Dear ImGui」はご存知でしょうか?
C++を使って直感的かつ簡単にGUIを構築できる非常に優れたGUIフレームワークで、デバッグやツール用のUIを構築する際によく用いられます。従来のライブラリですとウィンドウを出したりボタンに処理を紐付けたりするのに手間がかかりましたが、Dear ImGui(以下、ImGui)なら非常にシンプルに書くことが出来ます。まずは下の参考記事をご覧ください!簡単過ぎて驚くと思います!

qiita.com

更に簡単だけでなく非常に多機能です。デフォルトで用意されている物を使うことで下図のようなGUIを作成することが可能です。

f:id:pafuhana1213:20201212221547p:plain:w400f:id:pafuhana1213:20201212222943p:plain:w400
https://github.com/ocornut/imgui より

UE4の標準機能でデバッグ・ツール用UIを作る場合はUMGを使うことになりますが、上図のようなデバッグ向けのパーツを一から用意するのは正直大変です。また、ImGuiはUE4のビューポート上に別ウィンドウ的な形で表示されるので、下図のように自由にウィンドウを配置・整理することが可能です。デバッグ用UIが作業のジャマになっては本末転倒なのでこのような機能は非常に助かります(念の為触れますが、ゲームで実際に使うUIのようなアニメーションしたり様々なグラフィックを表示するのにはImGuiは向いていません。あくまでデバッグ・ツール用UI向けです)。

そんな神ってるDear ImGuiはその知名度・注目度が年々増してきており、最近だとCEDEC AWARDS 2019 のエンジニアリング部門で優秀賞を受賞したり、FINAL FANTASY VII REMAKEにて採用されたりしています!すごい!

更に、有志による UE4 対応版Dear ImGuiとして「Unreal ImGui」がなんと公開されています…!
github.com

更に更に、まだ触れていないのですが、実機上で表示したImGuiウィンドウをPCに繋いだマウス・キーボードで制御することが可能になる NetImGuiが組み込まれているようです!こりゃ触るしかないですね…!
github.com
https://github.com/sammyfreg/netImgui/raw/master/Web/img/InputWithNetImgui.gif
https://github.com/sammyfreg/netImguiより


ということでやっと触ってみました。まだ調査しきれていない部分もあるのですが、ひとまず色々動いたので導入・基本部分について書いていこうと思います。
なお、ImGui自体の使い方についてはあまり解説しません。というより、先程の参考記事が素晴らし過ぎるのでそちらをご確認ください(あと、Dear ImGui公式のWikiも是非)

導入編

まずはUnreal ImGuiの各コード・ファイルをDLし、それらをプロジェクトのPlugins/Imguiフォルダに全て入れます。
f:id:pafuhana1213:20201212230006p:plain

そして、プラグインをビルドした後にImGuiプラグインを有効にします。

f:id:pafuhana1213:20201212230117p:plain:w500

その後、PIEなどで実行中にコンソールコマンド ImGui.ToggleDemo を実行して以下のウィンドウが表示されたら導入成功です!

f:id:pafuhana1213:20201212230730p:plain

しかし、マウス操作をUE4のビューポートに取られてしまっているため、せっかく表示したImGuiのウィンドウを操作することができません…。
そんな時は慌てずに ImGui.ToggleInputを実行しましょう!これでImGui側の各ウィンドウを操作することが可能になります!

ImGuiに慣れるためにも、このデモを色々触ってみるのが良いと思います!特に、DemoWindowボタンを押すと出てくるサンプルはImGuiに用意された多くの機能を実際に見て触ることができるので非常に参考になります!以下のコードを眺めながら見るのもオススメです。

  • Plugins\ImGui\Source\ImGui\Private\ImGuiDemo.cpp
  • Plugins\ImGui\Source\ThirdParty\ImGuiLibrary\Private\imgui_demo.cpp

基本編

次は実際に自分のプロジェクト用のImGuiウィンドウを作ってみましょう!

まずは、実装するモデュールのBuild.cs に

PublicDependencyModuleNames.Add("ImGui");

または

PrivateDependencyModuleNames.Add("ImGui");

を追加します。

次に、Unreal ImGuiを使うための「おまじない」まとめをプロジェクトに追加します。

ImGuiCommon.h

#pragma once

#ifdef IMGUI_API
#define WITH_IMGUI 1
#else
#define WITH_IMGUI 0
#endif // IMGUI_API

#if WITH_IMGUI
#include <ImGuiModule.h>
#include <ImGuiDelegates.h>

#include <imgui.h>
#endif // WITH_IMGUI

そして、新規Actor継承クラス AImGuiTest に ImGui用コードを追加します。

ImGuiTest.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

// ImGui
#include "ImGuiCommon.h" 

#include "ImGuiTest.generated.h"

UCLASS()
class UNREALIMGUI_API AImGuiTest : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AImGuiTest();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

#if WITH_IMGUI
	void ImGuiTick();
#endif // WITH_IMGUI
};

ImGuiTest.cpp

#include "ImGuiTest.h"

// Sets default values
AImGuiTest::AImGuiTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AImGuiTest::BeginPlay()
{
	Super::BeginPlay();
	
#if WITH_IMGUI
	// ImGuiのWorld Delegateに処理を登録
	FImGuiDelegates::OnWorldDebug().AddUObject(this, &AImGuiTest::ImGuiTick);
#endif // WITH_IMGUI
}

void AImGuiTest::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

#if WITH_IMGUI
	// ImGuiのWorld Delegateに登録した処理を解除
	FImGuiDelegates::OnWorldDebug().RemoveAll(this);
#endif // WITH_IMGUI
}

// Called every frame
void AImGuiTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

#if WITH_IMGUI
	ImGui::Begin("ImGui Debug Order Test");
	ImGui::Text("Actor Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'",
		*GetNameSafe(this), *GetNameSafe(GetWorld()), *GetNameSafe(GWorld));
	ImGui::End();
#endif // WITH_IMGUI
}

#if WITH_IMGUI
void AImGuiTest::ImGuiTick()
{
	ImGui::Begin("ImGui Debug Order Test");
	ImGui::Text("ImGui World Tick: Actor = '%ls', World = '%ls', CurrentWorld = '%ls'",
		*GetNameSafe(this), *GetNameSafe(GetWorld()), *GetNameSafe(GWorld));
	ImGui::End();
}
#endif // WITH_IMGUI

で、このActorをレベルに配置して実行すると以下のウィンドウが表示されるはずです!
f:id:pafuhana1213:20201212233737p:plain

こうして書いてみると少し長く感じるかもしれませんが、ImGuiのために追加したコードは30行ほどです!あとはTick関数・ImGuiTick関数にコードを追加していくことでいい感じのデバッグ・ツール用UIが作れそうです!

基本編の補足 < Tick と ImGuiTick の違いは何?>

Tickはお馴染みの処理なので説明不要ですが、ImGuiTickは ImGuiのWorld Delegateに登録することで動作させています。こうすることでポーズやSlomoなどでActorのTick頻度が変化した場合でもImGuiTick関数を毎フレーム走らせることができます。

下図が実際の動作の様子です。一時停止させるとActorのTickが止まるため、ImGuiウィンドウに表示されるテキストが減っていることが確認できます。

f:id:pafuhana1213:20201213000010g:plain

ということで、常に動いてて欲しい項目の場合は ImGuiのWorld Delegateに登録する形にするのが無難かと思います。なお、登録した処理は不要になったらちゃんと解除するようにしましょう!(AImGuiTest::EndPlay 参照)

なお、ImGuiにはMulti-Context Delegateというものもあるのですが…こちらはまだ十分に検証できていないので今回は解説しません。マルチプレイヤーゲームを作る際に活躍するもの(のはず)です。

設定編 < ショートカットキーToggle Input はいいぞ >

プロジェクト設定の Plugins/ImGui より、ImGuiのウィンドウ表示・操作に関する設定を変更することが可能です。色々項目がありますが、まずオススメなのがToggle Inputを設定することです。ここのToggle Inputにショートカットキーを設定することで、毎回 ImGui.ToggleInputを実行する手間を省くことが出来ます。

f:id:pafuhana1213:20201213000517p:plain

少し応用編 < テクスチャアセットをImGuiウィンドウで使う方法について >

と言っても、ほぼUnreal ImGuiの公式ページに書いてることなのですが…

h 側に以下を追加

UPROPERTY(EditAnywhere)
UTexture2D* Texture;

BeginPlayに以下を追加

FImGuiModule::Get().RegisterTexture("TextureName", Texture);

EndPlayに以下を追加

FImGuiTextureHandle TextureHandle = FImGuiModule::Get().FindTextureHandle("TextureName");
FImGuiModule::Get().ReleaseTexture(TextureHandle);

Tick または ImGuiTick に以下を追加

ImGui::Image(TextureHandle, ImVec2(200,200));

という感じのコードを書くことで、以下のようにUE4側の画像データをImGuiで使うことが出来ます(最小限のコードなのでぬるぽ注意です。ちゃんとチェックコード入れましょう!)。

f:id:pafuhana1213:20201213002017p:plain:w500

上のコードから少し察している方も多いかと思いますが、UE4とImGuiとの画像データのやり取りは文字列(FName)とIDデータを持つFImGuiTextureHandleを使って行います。そのため、ImGui側に渡す文字列は結構重要だったりします。ユニークである必要もあるため、恐らくアセット名を渡すケースが多いのではないかと思います。

FImGuiModule::Get().RegisterTexture(FName(*Texture->GetName()), Texture);

さいごに

雑多感じの内容にはなりましたが、日本語でのUE4へのImGui組み込みに関する情報が見当たらなかったのでこれから触る方には参考になる…はず…!と信じて書ききりました。

エディタ拡張をUMGで行う EditorUtilityWidget など便利な機能がUE4には沢山ありますが、「そこまで工数・作業時間は取れないけど、そこそこリッチ かつ 必要十分な機能を持ったデバッグUIが欲しい」という要望に応えるのは難しい場合があります。そんな時は是非ImGuiの利用を検討してみるのも良いかもしれません。

そして、是非使ってみた系の記事を…何卒…|д゚)チラッ


以上で本記事は終了です!
Unreal Engine 4 (UE4) Advent Calendar 2020 14日目の担当は、みんな大好きキンアジちゃん( https://twitter.com/kinnaji_blog ) です!神記事全裸待機してます!