SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

【UE5】Commandlet備忘録

はじめに

こんにちは。エンジニアの佐々木です。
今回はUE5でのCommandletについて最小構成ソースコードデバッグ環境構築、引数のパースや非同期処理などを備忘録的に書いていきます。

Commandletとは

コマンドレットとは、「Unreal Engine」環境内部で実行するコマンドラインのプログラムです。コマンドレットが使用されるのは、コンテンツに大量の変更を加える場合や、情報を得るためにコンテンツをイタレートする場合、ユニットテストとして用いられる場合です。デフォルトでは、多数のコマンドレットがエンジンに含まれています。また、新たなコマンドレットを追加することによって
、付加的な機能を実行することが可能です。
引用:UDK | CommandletHomeJP


具体的な例でいうと
・ライトビルド
・ナビゲーションビルド
・アセット一括変更
などをエディタを起動することなく実行することができます。
手動でやっていたものを自動化できると気分がいいですね。
C++で拡張するため、プロジェクト独自のコマンド追加をしたり自由度も高い印象です。

Commandletの実装

さっそくですが最小構成のCommandletを実装してみます。
コマンドレット名はSampleとします。
クラス名は「U[コマンドレット名]Commandlet」とするのがセオリーのようなので、USampleCommandletとします。

SampleCommandlet.h

#pragma once
#include "Commandlets/Commandlet.h"
#include "SampleCommandlet.generated.h"

UCLASS()
class USampleCommandlet : public UCommandlet
{
	GENERATED_BODY()

public:
	USampleCommandlet(const FObjectInitializer& ObjectInitializer);

	virtual int32 Main(const FString& Params) override;
};

SampleCommandlet.cpp

#include "SampleCommandlet.h"

USampleCommandlet::USampleCommandlet(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{

}

int32 USampleCommandlet::Main(const FString& Params)
{
    return 0;
}

実行方法

普通に実行すればOKです。
UE4までは"UE4Editor-Cmd.exe"でしたが、
UE5からは"UnrealEditor-Cmd.exe"のようです。

[EngineRoot]\Engine\Binaries\Win64\UnrealEditor-Cmd.exe [ProjectRoot]\MyProject.uroject -run=SampleCommandlet

デバッグ環境構築

動作確認をしたかったのでVisual Studioから実行してブレークポイントを使用できるようにします。
分かりやすくまとめてくださってる方がいましたので説明は割愛します。
qiita.com

もろもろ設定して実行してみると…

できたー!

おまけ

最小構成ソースコードデバッグ環境構築だけでは味気ないので、
他コマンド、引数指定とパース方法、非同期処理についても軽く触れておきます。

他コマンド

シンプルに -[任意コマンド]で指定するのみです。

UnrealEditor-Cmd.exe MyProject.uroject -run=SampleCommandlet -[任意コマンド]

たとえば -buildlighting -allowcommandletrendering などがあります。
他のコマンドについてドキュメントようなものは見当たりませんでした。(あればぜひ教えてください…!)
ヒストリアさんの記事でいくつか触れているほか、ContentCommandlets.cppでも確認できるようです。
historia.co.jp

引数指定とパース方法について

Commandletの引数はFString型なのでパースが必要になります。
実行時には以下のように指定します。

-[パラメータ名]=値

今回はintの2を指定してみましょう。

-Param=2
#include "SampleCommandlet.h"
#include "Logging/LogMacros.h"

DEFINE_LOG_CATEGORY_STATIC(LogSampleCommandlet, Log, All);

USampleCommandlet::USampleCommandlet(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{

}

int32 USampleCommandlet::Main(const FString& Params)
{
    // コマンドライン引数のパース
    FString Value;
    if (!FParse::Value(*Params, TEXT("Param="), Value))
    {
        UE_LOG(LogSampleCommandlet, Error, TEXT("Failed to parse. Param=%s"), *Value);
        return 1;
    }

    // FStringからint32へ変換
    const int32 Param = FCString::Atoi(*Value);
    UE_LOG(LogSampleCommandlet, Log, TEXT("Param=%d"), Param);

    return 0;
}


これで引数「2」を受け取ることができました!

非同期処理

Commandletでは非同期処理を同期処理する必要があります。

例えばHTTPリクエストです。
HTTPリクエストを使ったCommandletを実装する機会があり、こちらの記事が非常に参考になりました。

この UE4 の HTTP モジュールは "非同期処理" を前提に実装され、インターフェースが提供され、 examples でも非同期処理で HTTP モジュールを扱っている例が幾つか見当たる。
usagi.hatenablog.jp


以下は自分の実装例です。
HTTPリクエストを投げて返ってきたレスポンスを利用して色々作ったりしました。

TFunction<void()> Func = [this]()
	{
		do
		{
			constexpr float Seconds = 0.010f;
			FPlatformProcess::Sleep(Seconds);
			FHttpModule::Get().GetHttpManager().Tick(Seconds);
			FHogeClass::Get().GetTaskManager().Tick(Seconds); // 本来Tickで行われる処理もこのように扱うことができます
		} while (!IsDoneBuilding());
	};

// こんな感じで一度に投げるリクエスト数に制限をかけたり…など
// (サーバー都合で一度に送るリクエスト数が多いとエラーになることがあったりした)
constexpr int32 NumRequestsPerFrame = 16;
BuidImmediately(NumRequestsPerFrame, Func);

おわりに

以上です!

アセット操作に使われることが多いCommandletですが、特定のレベルを開いてSpawnActorなどもできるのでシーンキャプチャに利用できたり、C++で実装するコストがかかる分やれることも非常に幅広いです。
シーンキャプチャのComanndletはざっくり分けると「レベルを開く、ワールド初期化、ライト計算、シェーダーコンパイル、被写体アクターやカメラのSpawn、SceneCaptureセットアップ、撮影、テクスチャアセット化」という流れです。
実はUE4で実装したものを紹介しようと思っていましたが、UE5では動かなくなっていたので機会があれば修正して紹介します!

個人的にUE4とUE5で実行ファイルが
"UE4Editor-Cmd.exe"
から
"UnrealEditor-Cmd.exe"
に変わっているのが地味な罠だったので気を付けましょう。
それではまた!