main() blog

プログラムやゲーム、旅、愛する家族について綴っていきます。

【UE5】StringTableを使ってみよう!(C++編)

概要

テキストデータを扱う場合、ローカライズなども考慮する必要があります。
DataTableで作成することも考えましたが、ローカライズを想定したStringTableというものがあるのでそちらを試してみたいと思います。
合わせてLocalizationDashboardも試してみたいと思います。

動作環境

UnrealEngine 5.4.4

実装

データの作成

コンテンツドロワーの "+追加" から "その他" → "ストリングテーブル" を選択し、新規に作成します。

下部のキー、ソースストリングに入力してデータを追加します。
追加した後でキーの変更はできないようです。
ソースストリングの変更は可能です。

CSVへエクスポート

csvにエクスポートしてみます。

Key,SourceString
EnemyName001,BUSHWACKER
EnemyName000,ROUGE

CSVからのインポート

csvからインポートしてみます。
試しに以下の様に1行追加してみます。

Key,SourceString
EnemyName001,BUSHWACKER
EnemyName000,ROUGE
EnemyName002,HIGHWAYMAN

CSVからインポートを選択します。

先ほどエクスポートしたファイルを選択してインポートします。

追加された行がインポートされていることが確認できます。

C++で参照する方法

この段階で文字列の取得を試してみます。

void Hoge()
{
    FText LocalizedText;

    // StringTable名とキーを指定してテキストを取得.
    LocalizedText = LOCTABLE("/Game/Game/StringTable/ST_Enemy.ST_Enemy", "EnemyName000");

    // 取得したテキストをログに表示.
    UE_LOG(LogTemp, Log, TEXT("Localized Text: %s"), *LocalizedText.ToString());
}

とりあえず文字列が取得できて、ログに出力されていることが確認できます。

ただ、指定したテーブルを読み込んではいないので、どこかで読み込まれていると思われます。
マクロの実装を確認したところ、引数でEStringTableLoadingPolicy::FindOrLoadが指定されているので、他で読み込まれていない場合は参照した段階でテーブルの読み込みが行われていると思われます。

別途、テーブルの読み込みについては確認が必要になりそうです。

LocalizationDashboard

ローカライゼーションダッシュボードはカルチャーを追加することで、そのカルチャー毎の翻訳データを作成、管理するための機能です。

"ツール" → "ローカライゼーションダッシュボード" から開くことができます。

"パッケージから収集" にチェックを入れ、Include Path Wildcards で参照するStringTableのパスを指定します。

新規カルチャーの追加を行います。

参考資料によると使用しない言語をデフォルトにしておいた方が無難とのことなので、とりあえず英語(アイルランド)を選択してみます。

ついでに日本語も追加しておきます。

この段階でテキストの収集を実行します。

これでデフォルト言語のワード数が更新されます。

追加、更新があった場合はこの操作でデフォルト言語の更新を行う必要があるようです。

個別の言語を編集する場合はそれぞれの言語の編集を押します。

試しに日本語の翻訳を編集してみます。

未翻訳のタブで編集したい項目のTranslation側をクリックします。
翻訳(日本語)を入力します。

ダッシュボードから言語ごとにコンパイルを実行します。

再度、テキストを収集を実行することで翻訳データが反映されます。

エディタ上で確認する場合はエディタの環境設定の一般の地域&言語のゲームのプレビュー言語を日本語にすることで日本語を確認することができます。

同じキーで参照して日本語訳が適用されていることが確認できました。

要検討

ゲーム側からはテーブルや言語に関係なく、Keyで文字列が取得できれば良いはずだが、テーブルとKeyを対で指定する必要があるがどのように実装するのが良さそうか?

LocalizationDashboardはテクスチャやサウンドなどのアセットも切り替えることができるとのこと。
ゲームによってはテキストとボイスそれぞれで言語切り替えが行えるタイトルなどもあるが、その場合はどのように対応すれば良いかなどまだ調査/検証が必要そうではある。

参考

historia.co.jp

qiita.com

docswell.com


【UE5】プラグインを作成してみよう!

概要

UE4プラグインは機能拡張のためにモジュールを追加することができます。
プロジェクト側でプラグインとしてモジュールを分けることはプロジェクト側(ゲーム側)依存を減らし、そのモジュールの独立性を高めることができます。
また、他のプロジェクトへプラグインとして提供することも可能になります。

※本記事はC++での開発を前提としています。

動作環境

UnrealEngine 5.4.4

作成手順

"編集" → "プラグイン" を選択してプラグインブラウザタブを開きます。

"+追加" ボタンをクリックしてプラグインリエーターダイアログを開きます。

作成するプラグインの一覧が表示されます。

作成できるプラグインは以下のものがあるようです。

今回は空白(blank)でプラグインを作成してみます。
空白を選択してプラグイン名を TestBlank とします。
右下の "プラグイン作成" をクリックします。

少し時間がかかりますが、コード生成やプロジェクト生成などのダイアログが表示されます。

VisualStudioのプロジェクトに作成したプラグインが追加されます。

ビルド後、エディタを起動し "編集" → "プラグイン" からプラグインブラウザタブを開き、追加したプラグインが認識されていることを確認します。

プラグインの項目の編集をクリックするとプロパティが開きます。
こちらでバージョンや説明、カテゴリなどの編集が行えます。

upluginファイルでも同様に編集が行えます。

これで空のプラグインが作成できました。

プラグインの実装を行った後で、プロジェクト(ゲーム側)からこのプラグインを使用する場合は、{Project}.Build.csの方に依存するモジュールを追記する必要があります。

参考

www.lancarse.co.jp

zenn.dev


【UE5】C++でクラスを追加する際にClass Typeを指定しない場合のインクルードパスの指定について

概要

C++のクラスを追加する際にUEの推奨の場合、Class TypeでPublic、Privateを指定する必要があります。
Class Typeを指定した場合、Public、Privateのフォルダが作成され、ヘッダーファイル、ソースファイルがそれぞれのフォルダに分けられます。
人によってはフォルダを分けられるのは分かりずらい、煩雑になるということで同じフォルダ内でヘッダーファイル、ソースファイルを配置したいというケースも出てくると思います。
その場合のインクルードパスの指定についてです。

※本記事はC++での開発を前提としています。

動作環境

UnrealEngine 5.4.4

Class Typeを指定した場合

C++のソースを追加する際にClass Typeを指定すると思います。
これはUEが推奨している方法でプロジェクト以下にPublic、Privateのフォルダ作成され、それぞれのフォルダ以下にcppやhが配置されます。
Publicの場合はヘッダーファイル(.h)はPublicに配置され、ソースファイル(.cpp)はPrivateに配置されます。
Privateの場合はヘッダーファイル、ソースファイルともにPrivate以下に配置されます。
これはモジュールとして外部に公開するものかどうかで指定するものと思われます。
Privateにした場合はモジュール内のみでの参照が可能となります。

新規にプロジェクトを作成した直後のvcxprojを確認すると、まだインクルードパスにPublic、Privateなどは追加されていません。
TestProjectというプロジェクトを作成した場合、vcxprojファイルは以下のフォルダに作成されます。

TestProject\Intermediate\ProjectFiles\TestProject.vcsproj

プロジェクトのSource/{Project}以下にPublic、Privateのフォルダが存在していれば、ソリューション生成(Generate VisualStudio project files)で生成されるvcxprojのもしくは<ClCompile_AdditionalIncludeDirectories>に....\Source{Project}\Public、....\Source{Project}\Privateが追加されます。
TestProjectというプロジェクトで、Class Typeを指定してソリューションの生成を行った場合、vcxprojには....\Source\TestProject\Public、....\Source\TestProject\Privateが追加されます。
これによりHoge/AHogeActor.cppなどを追加した際も以下のインクルードで該当ファイルが参照できるようになっています。

#include "Hoge/AHogeActor.h"

Class Typeを指定しない場合

個人やプロジェクトのルールなどでPublic、Privateに分けたくないなどの理由でPublic、Privateのフォルダを入れずにそのままヘッダーファイル、ソースファイルを同じフォルダ(階層)にするケースがあると思います。
C++でクラスを作成する際にClass Typeをしないで作成した場合に指定したフォルダ以下にヘッダーファイル、ソースファイルが追加されます。
この場合にC++で新規のクラスを追加した場合、ソースファイル(cpp)のビルドでヘッダファイルの参照が正しく行われずビルドエラーとなります。

#include "Hoge/AHogeActor.h" // ←ビルドエラー

その場合、vcxprojの方にはインクルードパスの設定が行われないため、そのままであれば{Project}のパスを追加、もしくは同フォルダ以内のヘッダーファイルであればそのフォルダのパスを削除する必要が出てきます。

#include "Hoge/AHogeActor.h" // ←ビルドエラー

 ↓
#include "AHogeActor.h"
もしくは
#include "TestProject/Hoge/AHogeActor.h"

これはUEが自動生成するソースがClass Typeを指定しないことで想定されていないインクルードパスの構成になってしまうためです。

Class Typeを指定しないケースでもClass Typeを指定した場合と同様のインクルードパスで参照できるようにしたい場合、プロジェクトのBuild.csにインクルードパスを指定することで対応できます。
TestProjectの場合、TestProject/Source/TestProject/以下にTestProject.Build.csというファイルがあるのでPublicIncludePathsでインクルードパスを追加することができます。

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class TestProject: ModuleRules
{
    public TestProject(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });

        PublicIncludePaths.Add(ModuleDirectory);
    }
}

ModuleDirectoryでプロジェクトの相対パスが取得できるようなので、そのパスをPublicIncludePathsで追加します。
追加した後で再度ソリューションファイルを生成します。
vcsprojのもしくは<ClCompile_AdditionalIncludeDirectories>に....\Source\TestProjectが追加されていることが確認できます。

この状態であればClass Typeを指定しない状態で新規に追加してもビルドエラーにはならず、Class Typeを指定した場合と同様のパスでヘッダーファイルを参照できるようになります。

Class Typeを指定しない場合はこのあたりに注意しておく必要が出てくると思われます。

参考

historia.co.jp


【UE5】コンソールコマンドを追加してみよう!

概要

独自のコンソールコマンドの追加方法です。
FAutoConsoleCommandを使用することで簡単に追加することができるようです。
以前はCheatManagerを使用してコンソールコマンドの追加を行っていましたが、現在はこちらでの実装で行う方が良いと思われます。

※本記事はC++での開発を前提としています。

動作環境

UnrealEngine 5.4.4

実装

FAutoConsoleCommandを使用して以下のようなコードを実装します。
デストラクタでコンソールコマンドのオブジェクトの登録が解除されるのでインスタンスを保持しておく必要があるので注意が必要です。
ですのでstaticで宣言するか、生存が保証されているクラスなどのメンバーとして宣言することでコマンドを登録することができます。

static FAutoConsoleCommand TestCommand = FAutoConsoleCommand(
    TEXT("Test.Command"),
    TEXT("Hoge.\nHoge Hoge.\nHoge Hoge Hoge."),
    FConsoleCommandDelegate::CreateLambda([]()
        {
            UE_LOG(LogTemp, Log, TEXT("Hello!!"));
        })
);

コンソールコマンドでTest.Commandが登録されていることが確認できます。

実行するとコマンドの処理が呼ばれていることが確認できます。

コマンドに"?"を付けることで説明文も表示することができます。

初期化時に渡すデリゲートでFConsoleCommandWithArgsDelegateを使用することでコマンドの引数を渡すことも可能です。

static FAutoConsoleCommand TestCommand2 = FAutoConsoleCommand(
    TEXT("Test.Command2"),
    TEXT(""),
    FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args)
        {
            UE_LOG(LogTemp, Log, TEXT("call Test.Command2"));

            for (const FString& Arg : Args)
            {
                UE_LOG(LogTemp, Log, TEXT("arg : [%s]"), *Arg);
            }
        })
);

引数を渡して実行してみます。

コマンドの実行時に引数が取得できていることが確認できます。

FConsoleCommandWithOutputDeviceDelegateも用意されています。 FOutputDeviceを引数として受け取れるのですが、ログなどの出力先などを変更する際などに使用するものでしょうか?
もう少し用途については調べてみたいと思います。

static FAutoConsoleCommand TestCommand3 = FAutoConsoleCommand(
    TEXT("Test.Command3"),
    TEXT(""),
    FConsoleCommandWithOutputDeviceDelegate::CreateLambda([](FOutputDevice& Device)
        {
            UE_LOG(LogTemp, Log, TEXT("call Test.Command3"));

            Device.CategorizedLogf("TestCategory", ELogVerbosity::Log, TEXT("call device log..."));
        })
);

コマンドの引数でWorldを受け取りたい場合は以下のクラスを使用します。

FAutoConsoleCommandWithWorld
FAutoConsoleCommandWithWorldAndArgs

static FAutoConsoleCommandWithWorld TestCommnad4 = FAutoConsoleCommandWithWorld(
    TEXT("Test.Command4"),
    TEXT(""),
    FConsoleCommandWithWorldDelegate::CreateLambda([](UWorld* World)
        {
            UE_LOG(LogTemp, Log, TEXT("call Test.Command3"));

            UE_LOG(LogTemp, Log, TEXT("world name : [%s]"), *World->GetName());
        })
);

FAutoConsoleCommandの中身はIConsoleManagerのRegisterConsoleCommand/UnregisterConsoleObjectを使用して登録/解除を行っているので、こちらを使用してもコンソールコマンドの追加が行えます。
試しにGameInstanceSubsystemの初期化/解除でコンソールコマンドを登録/解除を行ってみます。

クラスに登録されたコンソールコマンドのポインタを保持するメンバーを追加しておきます。

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "MyGameInstanceSubsystem.generated.h"

/**
 * 
 */
UCLASS()
class HOGE_API UMyGameInstanceSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

public:
    virtual void Initialize(FSubsystemCollectionBase& Collection);

    virtual void Deinitialize();

private:
    TArray<IConsoleCommand*> ConsoleCommands;
};

初期化時にRegisterConsoleCommandを使用してコマンド、説明文、コールバック関数等を登録します。
登録時のIConsoleCommandのポインタを保持しておきます。
解除時保持されているIConsoleCommandをすべてUnregisterConsoleObjectすることでコンソールコマンドを登録を解除します。

#include "MyGameInstanceSubsystem.h"

void UMyGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    UE_LOG(LogTemp, Log, TEXT("UMyGameInstanceSubsystem::Initialize()"));

    Super::Initialize(Collection);

    {
        auto* command = IConsoleManager::Get().RegisterConsoleCommand(
            TEXT("Test.Command"),
            TEXT(""),
            FConsoleCommandDelegate::CreateLambda([]()
                {
                    UE_LOG(LogTemp, Log, TEXT("Hello!!"));
                }),
            ECVF_Default);

        ConsoleCommands.AddUnique(command);
    }
}

void UMyGameInstanceSubsystem::Deinitialize()
{
    UE_LOG(LogTemp, Log, TEXT("UMyGameInstanceSubsystem::Deinitialize()"));

    Super::Deinitialize();

    {
        for (auto* command : ConsoleCommands)
        {
            IConsoleManager::Get().UnregisterConsoleObject(command);
        }
    }
}

この場合はエディタ起動後、テストレベルなどで実行時にGameInstanceが生成された段階でコンソールコマンドの登録が行われます。
実行を終了した時点でコンソールコマンドは解除されます。

レベルを起動していない状態のエディタでコマンドが認識されていないことも確認できます。


【UE5】DataTableを使ってみよう!(C++編)②

概要

データテーブルは、構造化データを整理するためのテーブル形式のデータ構造です。
前回はデータテーブルの定義の方法やデータの作成方法などを紹介しました。

www.main-function.com

今回はどういう型が使用できるかなどをまとめてみました。

※本記事はC++での開発を前提としています。

動作環境

UnrealEngine 5.4.4

実装

前回のEnemyDataの構造体にパラメータを追加してみたいと思います。
enumの扱いを試すために以下のenumを定義しておきます。

UENUM(BlueprintType)
enum class EEnemyType : uint8
{
    None,
    Warrior,    // 戦士.
    Mage,       // 魔法使い.
};

構造体の扱いを試すために以下の構造体も定義しておきます。
USTRUCTでBulueprintTypeを指定しておく必要があります。
操作したいメンバーにはUPROPERTYを付けておく必要があります。

USTRUCT(BlueprintType)
struct HOGE_API FEnemyCustomParam
{
public:
    GENERATED_USTRUCT_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName Name;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Age;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float Score;
};

前回使用したEnemyDataに定義したenumや構造体を追加してみます。
FVectorやFRorator、TArrayのコンテナも追加してみます。
固定長の配列はUPROPERTYで扱うことができないようで、コンパイルエラーとなります。

USTRUCT(BlueprintType)
struct HOGE_API FEnemyData : public FTableRowBase
{
    GENERATED_BODY()

public:

    // 攻撃力.
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Attack = 0;

    // 防御力.
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Defence = 0;

    // HP.
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 HP = 0;

    // タイプ.
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EEnemyType EnemyType = EEnemyType::None;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FEnemyCustomParam CustomParam;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FVector VectorParam;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FRotator RotatorParam;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<int32> IntParams;

    //UPROPERTY(EditAnywhere, BlueprintReadOnly)
    //int32 IntParams[2];

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FName> NameParams;
};

コンパイル後に前回作成したDT_EnemyTableを開くと追加した項目がプロパティに追加されていることが確認できます。

enumはドロップダウンリストから選択できるようになります。

別に用意した構造体も各メンバーもそのまま編集できます。

Vector、RotatorもX,Y,Zそれぞれ入力することができます。

TArrayの可変長配列も配列の追加/削除が行えて、それぞれに入力することができます。

エクスポート

追加したデータをCSVでエクスポートしてみます。
以下の様な内容で出力されています。
enumは文字列で出力されているようです。
構造体も定義したメンバーそれぞれがテキストで出力され、そのメンバーの値がカンマ(,)区切りで出力されて、構造体全体が括弧で括られて一つのカラムの値として出力されているようです。
Vector、Rotatorも同様にそれぞれのフォーマットで出力されています。
※Roratorはpitch,pan,rollではなくX,Y,Zとなっているようなので注意が必要です。
TArrayもフォーマットが決まっていて、同様に括弧で括られてその中に要素数分だけカンマ区切りで値が出力されているようです。

---,Attack,Defence,HP,EnemyType,CustomParam,VectorParam,RotatorParam,IntParams,NameParams
Enemy000,"0","0","0","Warrior","(Name="""",Age=0,Score=0.000000)","(X=0.000000,Y=100.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","(0,10)","(""DropItem000"","""","""","""")"
Enemy001,"0","0","0","Mage","(Name="""",Age=0,Score=0.000000)","(X=0.000000,Y=0.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","",""

インポート

エクスポートしたcsvの一部を編集してみます。
構造体のNameやAgeに値を設定してみます。

---,Attack,Defence,HP,EnemyType,CustomParam,VectorParam,RotatorParam,IntParams,NameParams
Enemy000,"0","0","0","Warrior","(Name=""Text_Enemy000"",Age=15,Score=0.000000)","(X=0.000000,Y=100.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","(0,10)","(""DropItem000"","""","""","""")"
Enemy001,"0","0","0","Mage","(Name="""",Age=0,Score=0.000000)","(X=0.000000,Y=0.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","",""

この状態でインポートしてみます。
編集した値が反映されていることが確認できます。

試しに以下のパラメータを追加してみます。
一行追加して、EnemyTypeにenumに無い値(Theif)を設定してみます。

---,Attack,Defence,HP,EnemyType,CustomParam,VectorParam,RotatorParam,IntParams,NameParams
Enemy000,"0","0","0","Warrior","(Name=""Text_Enemy000"",Age=15,Score=0.000000)","(X=0.000000,Y=100.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","(0,10)","(""DropItem000"","""","""","""")"
Enemy001,"0","0","0","Mage","(Name="""",Age=0,Score=0.000000)","(X=0.000000,Y=0.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","",""
Enemy002,"0","0","0","Thief","(Name="""",Age=0,Score=0.000000)","(X=0.000000,Y=0.000000,Z=0.000000)","(Pitch=0.000000,Yaw=0.000000,Roll=0.000000)","",""

このcsvをインポートした際にはエラーが出るようです。

FVectorやFRorator、TArrayなどをcsvで編集するのは厳しいので、Excelコンバーターなどを用意してcsvに変換した方が良さそうです。


【UE5】UnrealEngine5をソースからビルドしてみよう!

概要

UnrealEngine5のソースを取得してビルドする方法についてまとめてみました。

動作環境

Windows11
UnrealEngine5.4.4

事前準備

以下の項目については事前に準備しておく必要があります。

  • UnrealEngineのアカウント登録
  • GitHubのアカウント登録
  • VisualStudio 2022のインストール

UnrealEngineのアカウントとGitHubのアカウントを関連付ける

UnrealEngineのアカウント管理画面でGitHubとの関連付けを行います。
アカウント管理画面のアプリとアカウントの項目のアカウントタブをからGitHubの接続を選択します。

アカウントに接続を選択します。

EpicGamesへの許可の確認を求めれるので Authorize EpicGames を選択します。

GitHubからソースを取得する

正しく関連付けされていればGitHubのEpicのページにアクセスできます。

github.com

ブランチの設定releaseや5.4も選択できますが、releaseは最新になっていってしまうの可能性があるのと、5.4はその下のマイナーバージョンの指定がないので、今回はUE5.4.4のLatest版を使用します。
本当はgitでforkなどをした方が良いですが、今回はzipファイルをダウンロードしてビルドを行ってみます。
※解凍時に深いフォルダで行うとパス名の最大値の制限にひっかかってエラーになるので注意が必要です

フォルダ構成についてはいろいろ考えられると思いますが、今回はプロジェクトでカスタマイズする想定でプロジェクト以下で管理する前提のフォルダ構成としています。
※これはプロジェクトやエンジンの管理などによって構成が変わってくるところなので、この構成が正しい、推奨というわけではないので注意してください

D:\MyProject\UE5\

Setup.batの実行

ビルドを行うためのセットアップを行います。
依存するファイルのダウンロードや環境変数の設定など、ビルドするための環境設定を行います。
解凍したフォルダにあるSetup.batを実行します。

警告が出る場合でも詳細から「実行」を選択します。

コマンドプロンプトが実行され、依存ファイルのダウンロードなどが行われます。

少しすると実行を許可するかの確認が聞かれるので「はい」を選択します。
その後にFile Typesの登録を聞かれるので「はい」を選択します。

以下のような警告が出るので「はい」を選択します。

この不明な発行元からのアプリがデバイスに変更を加えることを許可しますか?
UnrealVersionSelector-Win64-Shipping.exe

バッチファイルが終了し、コマンドプロンプトもそのまま閉じます。
これでセットアップは完了となります。

ソリューションファイルの生成

解凍したフォルダにあるGenerateProjectFiles.batを実行します。
こちらも警告がでたら同じように詳細から「実行」を選択します。

UE5.slnが生成されれば完了です。

ビルド

生成されたUE5.slnを開きます。
UE5をスタートアッププロジェクトに設定します。

ソリューション構成が Development Editor に、プラットフォームがWin64になっていることを確認します。

ビルドを開始します。

ビルドが完了して、成功していることを確認します。

起動

VisualStudioからデバッグで実行(F5)もしくはビルドされた以下の実行ファイルを起動します。

D:\MyProject\UE5\Engine\Binaries\Win64\UnrealEditor.exe

起動時にネットワークへのアクセスの許可の確認が行われたら「許可」を選択します。

初回はシェーダーのビルドが走るのでエディタの起動までに時間がかかります。

プロジェクトブラウザの起動が確認できます。

以上がUnrealEngineをソースからビルドして起動するまでの手順となります。

VisualStudioの設定

ソリューション構成の幅が狭く、見づらいので幅を変更しておいた方が良いです。

  • ツール → カスタマイズを選択する
  • コマンドタブを選択する
  • ツールバーにチェックを付け、プルダウンから標準を選択する
  • プレビューからソリューション構成を選択する
  • 選択したボタンの編集で幅を変更する

必要なコンポーネントを要求されるケースがあるのでインストールしておきます。
※これはVisualStudioインストール時に個別のコンポーネントでインストールしておいても良いです

  • MSVC v143 - VS 2022 C++ x64/x86 ビルドツール(v14.38-17.8)
  • .NET 6.0 ランタイム(長期サポート)

参考

dev.epicgames.com


【UE5】ImGuiを日本語対応してみよう!

概要

ImGuiの日本語表示の対応方法についてです。

ImGuiはデフォルトでは日本語の表示が行えません。
ImGui側で機能はいろいろと用意されているので、プロジェクト側で対応していく必要があります。

最初はプラグイン側の変更は行わずにゲーム側で対応を検討していましたが、フォントサイズの変更などでフォントデータの再生成を行う必要もあるためプラグイン側での対応ですすめてみます。
今回はImGuiのプラグイン自体がゲーム側に組み込んでいるものなので、プロジェクトカスタマイズでの対応でも問題はないと思います。

※本記事はC++での開発を前提としています。

基本的な導入方法などは以下を参考にしていただければと思います。

www.main-function.com

動作環境

Windows11
UnrealEngine 5.4.4

フォントデータをソースに埋め込む

結論としてはこちらの対応が良さそうです。
ttfファイルの読み込みなども検討してみましたが、ソースコードにバイナリを変換したコードを埋め込むことにより、リソースの管理や読み込みなどを行う必要がなくフォントの追加を行うことができるのでパッケージ環境などでも問題が少なくなると思われます。

binary_to_compressed_c.exeを使用して、ttfファイルをソースに埋め込めるコードに変換することができます。
binary_to_compressed_c.cppを自分でビルドしても良いですし、以下のリンクのimgui-demo-binaries-20240105.zipをダウンロードし解凍するとexeが入っているのでそちらを使用することもできます。

github.com

試しにNotoSansJPを使用して日本語対応を進めてみたいと思います。
※半角も等幅の方が良いのでプロジェクトに応じて適切なフォントを選択してください

コマンドプロンプトから以下を実行します。

>binary_to_compressed_c.exe NotoSansJP-Medium.ttf NotoSansJP > NotoSansJP.cpp

実行すると以下のようなcppのファイルが出力されます。

// File: 'NotoSansJP-Medium.ttf' (5729568 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int NotoSansJP_compressed_size = 4127143;
static const unsigned int NotoSansJP_compressed_data[4127144/4] =
{
    0x0000bc57, 0x00000000, 0x206d5700, 0x00000400, 0x00010025, 0x82130000, 0x00042e04, 0x53414230, 0x6a57fb45, 0x020000c0, 0x081582b4, 0x4447e437, 
    :
    :
};

このソースをプロジェクトに追加します。
パスが参照できるところが良いので、今回はImGuiのプラグインのThridPartyのフォルダに入れておきます。
ここはモジュール側の設定でprivateで参照できる場所となっているので、プラグイン側の実装で完結できるのであればここに置いても問題と思われます。

ImGuiContextManager.cppのBuildFontAtlas()の中で日本語フォントを初期化してみます。
この関数はDPIScaleが変更されフォントがリビルドされた際にも呼び出されるので、このタイミングでフォントの初期化を行う必要がありそうです。
ですので、今回はプラグイン側に直接実装してみたいと思います。

圧縮時の引数のデフォルトでは圧縮がかかっているので以下の関数を使用して初期化できます。
コードのデータ配列の変数とサイズを引数で渡すことで初期化することができます。
また、日本語の文字コードの範囲の指定はFontAtlas::GetGlyphRangesJapanese()を使用して引数で指定することもできます。

ImFontAtlas::AddFontFromMemoryCompressedTTF()

以下が実装例です。
ImGuiContextManager.cppの最初の方でフォントデータのソースを直接インクルードします。
FImGuiContextManager::BuildFontAtlas()で日本語フォントの初期化を行います。
今回はAddFontDefault()でデフォルトフォントを生成していますが、いったんクリアして再生成を行っています。
このあたりはプロジェクトに併せて適宜修正を行っていただければと思います。

#include "Fonts/NotoSansJP.cpp"

 :
 :

void FImGuiContextManager::BuildFontAtlas(const TMap<FName, TSharedPtr<ImFontConfig>>& CustomFontConfigs)
{
    if (!FontAtlas.IsBuilt())
    {
        ImFontConfig FontConfig = {};
        FontConfig.SizePixels = FMath::RoundFromZero(13.f * DPIScale);
        FontAtlas.AddFontDefault(&FontConfig);

        :
        :
        :

#if 1
        // デフォルトフォントの置き換え.
        FontAtlas.Clear();
        FPlatformString::Strcpy(FontConfig.Name, sizeof(FontConfig.Name), "NotoSansJP");
        FontAtlas.AddFontFromMemoryCompressedTTF(NotoSansJP_compressed_data, NotoSansJP_compressed_size, 16.0f * DPIScale, &FontConfig, FontAtlas.GetGlyphRangesJapanese());
        FontAtlas.Build();
#endif

        unsigned char* Pixels;
        int Width, Height, Bpp;
        FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);

        OnFontAtlasBuilt.Broadcast();
    }
}

デフォルトフォントを残しつつ、マージで追加したい場合は以下のような記述となります。

     // マージで追加.
        FontConfig.MergeMode = true;
        FPlatformString::Strcpy(FontConfig.Name, sizeof(FontConfig.Name), "NotoSansJP");
        FontAtlas.AddFontFromMemoryCompressedTTF(NotoSansJP_compressed_data, NotoSansJP_compressed_size, 16.0f * DPIScale, &FontConfig, FontAtlas.GetGlyphRangesJapanese());

実行

日本語を含めた表示が行えていることが確認できます。

参考

qiita.com