UEのC++のデバッグを便利にする”マイコード”関連のメモ

概要

UnrealEngineのデバッグをしている時に、Blueprintやリフレクションの為に実装されているコードがあると思います。
そういったコードは階層が非常に深くなる傾向にある上、一般的なユーザーは、そのコードをデバッグする事は殆どありません。
ローカルウインドウを見て、Blueprint関数のコールスタックを確認する位だと思います。
コールスタックを確認する場合には、イルミナディウインドウからPrintScriptCallstackを呼ぶことによって解決するので尚更少ないケースだと思います。

learn.microsoft.com

そういったケースを解決する為に、natjmcファイルやnatstepfilterファイルに外部コードの指定や、ステップフィルターの設定を書き加える事でデバッグを助けてくれます。
詳細は上記の公式ドキュメントを参照してください。

natjmcファイルを使った外部コードの設定に関して

コールスタックでUnrealEngineのBlueprint関連のコードを外部コードにして切り替えている様子

外部コードを設定時と未設定時の動作の比較

未対応 対応済み

引用Microsoft公式ドキュメント
C++デバッグ中:

  • 非ユーザー コードで [デバッグ]>[ステップ イン] を選択すると (または F11 キ> ーを押すと)、コードがユーザー コードの次の行にステップ オーバーされます。
  • 非ユーザー コードで [デバッグ]>[ステップ アウト] を選択すると (または Shift+F11 キーを押すと)、ユーザー コードの次の行まで実行されます。

コールスタックの外部コード部分をまとめて表示する機能の他にデバッガーのステップインの動作を便利にしてくれる機能もあります。
設定を有効にする為には、%USERPROFILE%\My Documents\\Visualizersフォルダにnatjmcファイルを配置する必要があります。

下記に実際にnatjmcを書いた際のサンプルコードを載せます。
本来はモジュールに対してや、ファイルに対しても設定出来ますが、必要にならなかったので検証していないのでサンプルには無い事をご容赦ください。

関数の適応サンプル

void HogeHoge();
template<class T, class Y>
void HugaHuga();
template<class T, class Y>
void Foo();
<?xml version="1.0" encoding="utf-8"?>
<NonUserCode xmlns="http://schemas.microsoft.com/vstudio/debugger/jmc/2015">

  <Function Name="HogeHoge" />
  <!--templateの関数を扱う際には&lt;*&gt;を入れる必要があります。-->
  <Function Name="HugaHuga&lt;*&gt;"/>
  <!--&lt;*&gt;とtemplate引数を一括に対応するのではなく、個別に記述する事も出来ます。-->
  <Function Name="Foo&lt;int, float&gt;"/>

</NonUserCode>

クラスの適応サンプル

class Hoge
{
    void Piyo();
    template<class T>
    void Foo();
};

template<class Y>
class Huga
{
    void Piyo();
    template<class T>
    void Foo();
};
<?xml version="1.0" encoding="utf-8"?>
<NonUserCode xmlns="http://schemas.microsoft.com/vstudio/debugger/jmc/2015">

  <Function Name="Hoge::Piyo" />
  <Function Name="Hoge::Foo&lt;*&gt;"/>

  <Function Name="Huga&lt;*&gt;::Piyo" />
  <Function Name="Huga&lt;*&gt;::Foo&lt;*&gt;"/>

</NonUserCode>

natstepfilterファイルを使ったステップフィルターの設定に関して

調査中

実際に私が使用しているサンプル

github.com

Blueprintの動的バインディングに関して

はじめに

Blueprintのではマルチキャストデリゲートに関して、イベントをバインドする事が出来ます。

コチラの設計思想はとても大好きなのですが、バインドする対象が多くなってくると視認性が非常に落ちるという欠点が存在しています。

Actor、UserWidgetなどの一部のクラスでは動的バインディング?(正式名称が分からないので誰か教えて下さい)を扱う事が出来ます。

この機能は、詳細タブからクリックする事で自動的にイベントが作成されて、自動的にバインディングまで行ってくれるという素晴らしい機能です。

視認性が非常に優れているのもメリットだと思います。 本記事では、動的バインディングに関して、詳細を調べてきましたので、そのまとめ記事となります。

Actorの動的バインディングに関して

AActor::ExecuteConstruction関数でバインディング処理を呼んでいるようです。


bool AActor::ExecuteConstruction(const FTransform& Transform, const FRotationConversionCache* TransformRotationCache, const FComponentInstanceDataCache* InstanceDataCache, bool bIsDefaultTransform)
{
    // 省略

    // この処理でバインディングを行っているようです。
    // Bind any delegates on components
    UBlueprintGeneratedClass::BindDynamicDelegates(GetClass(), this); // We have a BP stack, we must have a UBlueprintGeneratedClass...

大雑把ですが、処理が呼ばれる順番は下記の表になります。

順番 処理 詳細
1 AActor::UserConstructionScript コンストラクションスクリプト
2 UBlueprintGeneratedClass::BindDynamicDelegates 動的バインディング
3 AActor::BeginPlay 開始処理

その為、コンストラクションスクリプトか、スポーン時タイミングで、動的バインディングに使われるオブジェクトの変数が初期化されていれば正しく動作します。

スポーン時に変数を初期化するか コンストラクションスクリプトで変数を初期化する必要があります。

自作の型で動的バインディングの設定する方法

動的バインディングをする為の条件

詳細タブに動的バインディングのUIが表示される為の条件を調べてみました。

詳細タブのイベント関連のコードはFBlueprintDetails::AddEventsCategory関数で行っているようです。

UBlueprint::AllowsDynamicBinding の可否で判定している模様

void FBlueprintDetails::AddEventsCategory(IDetailLayoutBuilder& DetailBuilder, FName PropertyName, UClass* PropertyClass)
{
    UBlueprint* BlueprintObj = GetBlueprintObj();
    check(BlueprintObj);

    // ここで、チェックをしてUIの表示できるかのチェックを行っています。
    // Check for Ed Graph vars that can generate events
    if ( PropertyClass && BlueprintObj->AllowsDynamicBinding() )
    {

UBlueprint::AllowsDynamicBindingでは、アクターかどうかで区別しているようです。

その為、UBlueprintを置き換える事が出来ればUIを表示できる事が分かります。

独自型専用の、UBlueprint派生の型を用意する事で解決するようです。

bool UBlueprint::AllowsDynamicBinding() const
{
    return FBlueprintEditorUtils::IsActorBased(this);
}

UBlueprintの置き換え

独自Blueprintを実装しているUserWidget関連のコードを見てみます。

UWidgetBlueprint では、実装しているモジュールはEditorの物になります。

独自Blueprintで特殊な事をしない限りは、エディターモジュールで実装して問題無いようです。

今回の記事ではエディターモジュールでの実装とします。

実装例のコードを下記に示します。

ヘッダーファイル

UCLASS(BlueprintType)
class UHogeBlueprint : public UBlueprint
{
    GENERATED_BODY()

public:

    /** @return true if the blueprint supports event binding for multicast delegates */
    virtual bool AllowsDynamicBinding() const;
};

// Blueprintコンパイラ
class FHogeBlueprintCompiler : public IBlueprintCompiler
{

public:
    FHogeBlueprintCompiler() {}

    bool CanCompile(const UBlueprint* Blueprint) override;
    void PreCompile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions) override {}
    void Compile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions, FCompilerResultsLog& Results) override {}
    void PostCompile(UBlueprint* Blueprint, const FKismetCompilerOptions& CompileOptions) override {}
    bool GetBlueprintTypesForClass(UClass* ParentClass, UClass*& OutBlueprintClass, UClass*& OutBlueprintGeneratedClass) const override;

};

ソースファイル

/** @return true if the blueprint supports event binding for multicast delegates */
bool UHogeBlueprint::AllowsDynamicBinding() const
{
    // 動的バインディングを常にするのでtrue
    return true;
}

bool FHogeBlueprintCompiler::CanCompile(const UBlueprint* Blueprint)
{
    return Cast<UHogeBlueprint>(Blueprint) != nullptr;
}

// この関数で、独自型と独自Blueprintクラスを紐づける必要があります。
bool FHogeBlueprintCompiler::GetBlueprintTypesForClass(UClass* ParentClass, UClass*& OutBlueprintClass, UClass*& OutBlueprintGeneratedClass) const
{
    if (ParentClass == UHogeScript::StaticClass() || ParentClass->IsChildOf(UHogeScript::StaticClass()))
    {
        OutBlueprintClass = UHogeBlueprint::StaticClass();
        OutBlueprintGeneratedClass = UBlueprintGeneratedClass::StaticClass();
        return true;
    }
    return false;
}

モジュールソースコード

class FSimpleBindingExampleEditorModule : public IModuleInterface
{
public:
    /** IModuleInterface implementation */
    void StartupModule() override
    {
        // ここでコンパイラに登録を行う
        IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>("KismetCompiler");
        KismetCompilerModule.GetCompilers().Add(&HogeBlueprintCompiler);
    }
    void ShutdownModule() override
    {
        // ここで登録解除を行う
        if (IKismetCompilerInterface* KismetCompilerModule = FModuleManager::GetModulePtr<IKismetCompilerInterface>("KismetCompiler"))
        {
            KismetCompilerModule->GetCompilers().Remove(&HogeBlueprintCompiler);
        }
    }
private:
    FHogeBlueprintCompiler HogeBlueprintCompiler;
};

ここまで実装すると、詳細タブにイベントの項目が表示されるようになります。

独自型の動的バインディングの設定方法

UBlueprintGeneratedClass::BindDynamicDelegatesを使う事で、動的バインディングを行う事が出来ます。

UBlueprintGeneratedClass::UnbindDynamicDelegates で解除をする事も出来ますが... 残念ながらエディター専用の処理となっていますので基本的に、一度バインドしたら解除せずに削除するのが方針だと思われます。

マルチキャストデリゲートから呼ばれないようにする為には、GCでUOBjectが削除されるかMarkAsGarbageを呼ぶ必要があります。

終了時には必ず呼ぶようにしましょう。

void UHogeScript::Initialize()
{
    // Bind any delegates on components            
    UBlueprintGeneratedClass::BindDynamicDelegates(GetClass(), this); // We have a BP stack, we must have a UBlueprintGeneratedClass...
}

void UHogeScript::Finalize()
{
    // 終了時にMarkAsGarbageを呼ぶ必要があります。
    // 呼ばないと、GCで消えるまで、Broadcastで登録したイベントが呼ばれてしまいます。
    MarkAsGarbage();
}

サンプルコード

github.com

本記事のサンプルコードを用意しました。

UnrealHeaderToolのPlugin公式サンプルに関して

はじめに

UnrealEngineに置いて、コードジェネレーターとしてUnrealHeaderToolが用意されています。

※以下UHTと略称します

 

今までのUHTの実装はC++で実装されていましたが、メンテナンスやビルド時間削減などの理由から、UnrealEngine5.1からはC#版が実装されました。

docs.unrealengine.com

 

↑のリリースノートでもありますが、公式で唯一用意してあるのはScriptGeneratorUbtPluginになります。

コード生成のPluginを作って見たかったので軽く触れて見ましたのでその備忘録を

C# スクリプト ジェネレータ プラグインのサンプルは「Engine/Plugins/ScriptPlugin/Source/ScriptGeneratorUbtPlugin」にあります。さらに、既存のすべての UHT エクスポータは「Engine/Source/Programs/Shared/EpicGames.UHT/Exporters」と同じメカニズムで作成されています。次に簡単なガイドを示します。


VisualStudioのプロジェクト周りの話

libcpp.hatenablog.com

↑が今回の件で調べた内容

-ALLPLATFORMS等を指定するとPrograms/UnrealBuildTool.Pluginsフォルダが追加されます。

そこでScriptGenreratorUbtPlugin.ubtpluginを見れるようになります。

配布版のビルドをDLしただけだと、UHTのPluginのバイナリは存在しません。

手動でビルドをする必要があります。

出力先は下記のフォルダに出力されます。

C:\Program Files\Epic Games\UE_5.1\Engine\Binaries\DotNET\UnrealBuildTool\Plugins\ScriptGeneratorUbtPlugin

Luaの有効化に関して

"C:\Program Files\Epic Games\UE_5.1\Engine\Plugins\ScriptPlugin\Source\Lua\README.txt"

LuaのGenreratorを有効にする詳細の手順は↑を参照にしてください。

一応流れは書きます

www.lua.org

↑のLuaの公式サイトからLua5.3をダウンロードして解凍してください。

C:\Program Files\Epic Games\UE_5.1\Engine\Plugins\ScriptPlugin\Source\Lua

↑のフォルダに解凍したフォルダをコピーしてください

下がコピーしたディレクトリのイメージ(ビルド後なので分かり難いですが..

Lua.slnを開いて、x64+Debug,x64+Relesaeビルドを両方行う

上記をするとLuaジェネレーターが動作するようになります。

 

Luaソースコードジェネレーターの出力に関して

C:\Program Files\Epic Games\UE_5.1\Engine\Plugins\ScriptPlugin\Intermediate\Build\Win64\UnrealEditor\Inc\ScriptPlugin\UHT

上記のフォルダに出力を行われます。

Project側のUObjectも同様に出力されるので注意してください。

 

UnrealEngineのソリューション構成にC#プロジェクトの表示を増やす方法

UnrealHeaderTool周りのコードを書くときに使ったので備忘録

 

対応方法

この記事は主に、配布版(InstalledBuild)向けの内容になります。

一応、公式ドキュメントに表示を増やす方法が記述されています。

docs.unrealengine.com

ProjectFileGeneratorの項目がプロジェクトを生成する時のオプションになります。
この記述に従って、BuildConfiguration.xmlを記述すれば生成されると思いましたが…

 

UE_5.1\Engine\Source\Programs\UnrealBuildTool\ProjectFiles\ProjectFileGenerator.cs:1293行目

生成時のコマンドライン引数によって、BuildConfiguration.xml で記述した内容が上書きされてしまいます。

逆に言うと、生成時のオプションによってソリューション構成を変える事が出来ます。

 

Running C:/Program Files/Epic Games/UE_5.1/Engine/Build/BatchFiles/Build.bat  -projectfiles -project="プロジェクトディレクトリ/プロジェクト名.uproject" -game -rocket -progress -log="XXXX/Logs/UnrealVersionSelector-2023.01.14-23.19.45.log"

↑は右クリック時のコンテキストメニューの際のログです。

引数に"-game"が含まれている為C#関連のコードは生成されなくなります。

但し、配布ビルド(InstalledBuild)の場合には、強制的に、BuildConfiguration.xmlのパラメーターを上書きされているケースもあるので注意してください。

結論!

つまり、自分の表示したいソリューションの範囲を広げたい場合には専用のbatファイルを用意するなどの対応が必要になります。


UE5.1時点での対応している引数一覧

BuildConfiguration.xmlのパラメーターを記述していない場合+"-ALLPLATFORMS"の場合


プロジェクトファイル生成時に下記のパラメーターを指定出来ます。

コチラと、BuildConfiguration.xmlのパラメーターを組み合わせてソリューションを作れるようです

"-ALLPLATFORMS":
"-CURRENTPLATFORM":
"-THIRDPARTY":
"-NOPROGRAMS":
"-GAME":
"-ENGINE":
"-NOCPP":
"-NOINTELLISENSE":
"-INTELLISENSE":
"-SHIPPINGCONFIGS":
"-NOSHIPPINGCONFIGS":
"-DEBUGCONFIGS":
"-NODEBUGCONFIGS":
"-DEVELOPMENTCONFIGS":
"-NODEVELOPMENTCONFIGS":
"-DOTNET":
"-NODOTNET":
"-ALLLANGUAGES":
"-USEPRECOMPILED":
"-INCLUDETEMPTARGETS":