blog heroalche.studio

【UE5.4】 プラグインの単体ビルドに関するあれこれ

著者:

Riku Ishikawa

投稿日 2024.07.19更新日 2024.08.10

はじめに

Unreal Engine を使った開発をやっていると、実装の再利用性やポータビリティの向上のためにプラグインを作成することがよくあります。

ふつう、作成したプラグインに含まれるのは C++ のソースコードであり、ビルド済みのバイナリは含まれません。 しかし、以下のような状況で、プラグイン内にビルド済みのバイナリを同梱したいことがあります。

  • プラグインを共有する相手の環境に C++ ビルド環境がない
    • 相手の環境でバイナリを作成できないので利用してもらうことができない……
  • 大きなプラグインなので、ビルド済みのバイナリを含めることでチーム全体のビルド時間を短縮したい

また、ソースコードを削除して、特定プラットフォームに向けたバイナリのみを共有するようにすれば、以下のような事もできるでしょう。

  • プラグインを共有して利用してもらいたいが、ソースコードは公開したくない
    • ソースコードを含めず ビルドしたバイナリだけを渡すことができれば……
  • 試用バージョンとしてプラグインを共有したいので、Editor では試せるが、Game のパッケージングには利用できないプラグインを作りたい
    • ソースコードを含めず、 Editor バイナリだけを入れて渡したバージョンを作れれば……

これらは、UE 標準の方法で実現可能です。実際に、プラグインをビルドする方法とその設定を見ていきましょう。

前提環境

  • C++ ビルド環境
  • UE 5.4

記事中のすべてのコマンドは、 Windows 上で PowerShell から実行することを想定しています。

ソースコード+バイナリ同梱プラグインをビルドする

エディタからビルドする方法

プラグインをビルドする方法はいくつかあります。最も単純なのは、エディタのプラグイン一覧に表示されている Package ボタンを押すことです。

image

一応、これでもビルドは実行されますが、どのような設定でビルドを行うのかの細かな制御を行うことができません。

コマンドからビルドする方法

そこで、コマンドから実行する方法を利用できます。前述のエディタから実行する方式も、こちらのコマンドを生成して実行する仕組みになっています。

このコマンドは、 BuildPlugin という名前で、 Unreal Automation Tool (UAT) から利用できます。UAT はエンジンに同行されている RunUAT.bat で呼び出せるので、以下のような形を基本形として利用することになります。

<エンジンのパス>\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin `
-Plugin=<uplugin ファイルへのフルパス> `
-Package=<ビルド出力先のフルパス>

もちろん、これだけではビルド設定を制御する目的が果たせていません。オプションを加えることで、細かいビルドの設定を行うことができます。以下に、オプションの一部を紹介します。

オプション名説明例
-NoHostPlatformいかなる HostPlatform 向けのバイナリも作成しない。-NoHostPlatform
-HostPlatforms+ 区切りで、ビルドする HostPlatform 名 (Editor) を指定する。-HostPlatforms=Win+Linux
-NoTargetPlatformsいかなる TargetPlatform 向けのバイナリも作成しない。-NoTargetPlatforms
-TargetPlatforms+ 区切りで、ビルドする TargetPlatform 名 (Game) を指定する。-TargetPlatforms=Android+IOS
-Architecture_<Platform><Platform> に指定した TargetPlatform をビルドするときに対象とする CPUアーキテクチャを + 区切りで指定する。-Architecture_Mac=x64+arm64
-NoDeleteHostProjectプラグインのビルドのために自動生成される一時プロジェクトを、ビルド後も削除せずに残す。ビルドのデバッグやテストに使える。-NoDeleteHostProject
-Unversioned出力するビルド済みプラグインの .uplugin にエンジンのバージョン情報を含めない。-Unversioned

HostPlatform と TargetPlatform について

ここで、 HostPlatform と TargetPlatform というのが何を指すのかわかりにくいかもしれません。 単純に言えば、 HostPlatform は Editor のこと、 TargetPlatform は Game のことです。

Unreal Engine では同じプラグインであっても、 Editor 上で利用するのと、パッケージング後に Game でも利用するのとでは、異なるバイナリが必要となります。両方で利用されるプラグインであれば、両方のバイナリが必要です。

ビルドコマンド例

これらを組み合わせると、たとえば以下のような設定でビルドを実行できます。

Win64 向けの Game / Editor 用バイナリを作成

<エンジンのパス>\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin `
-Plugin=<uplugin ファイルへのフルパス> `
-Package=<ビルド出力先のフルパス> `
-TargetPlatforms=Win64 `
-HostPlatforms=Win64

Win64, Android 向けに、 Game 用バイナリのみを作成

<エンジンのパス>\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin `
-Plugin=<uplugin ファイルへのフルパス> `
-Package=<ビルド出力先のフルパス> `
-TargetPlatforms=Win64+Android `
-NoHostPlatform

Win64, Linux 向けに、 Editor 用バイナリのみを作成

<エンジンのパス>\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin `
-Plugin=<uplugin ファイルへのフルパス> `
-Package=<ビルド出力先のフルパス> `
-HostPlatforms=Win64+Linux `
-NoTargetPlatforms

バイナリのみのプラグインを作成する

前節までの手順で、ソースコード + バイナリの状態のプラグインを作成することはできるようになりました。共有先の環境にビルド環境を不要にするという目的であれば以上で完了です。

しかし、ソースコードが含まれている以上、ビルド環境さえあれば任意のプラットフォームに対するバイナリを利用者が作成可能であるため、制限にはなりません。また、当然ですがソースは閲覧可能です。そこで、ビルドしたプラグインからソースコードを削除し、バイナリのみで共有する手順を紹介します。

こちらの手順は、ビルドしたプラグインに含まれているソースコードを削除していくものなので、まずは前述のビルド手順を行ってから先に進んでください。

ビルドされたプラグインの中身を確認する

Game 向けバイナリと Editor 向けバイナリでは作成されるものと場所が違うので、個別に中身を確認していきます。

まずは、Win64 Editor 向けバイナリのみが含まれた状態の構成を確認します。

.
├── Binaries
│   └── Win64
│       ├── UnrealEditor-ModuleName.dll
│       ├── UnrealEditor-ModuleName.pdb
│       └── UnrealEditor.modules
├── Intermediate
│   └── Build
│       └── Win64
│           ├── UnrealEditor
│           │   └── Inc
│           │       └── 中間生成ソースコード
│           └── x64
│               └── UnrealEditor
│                   └── Development
│                       └── ModuleName
│                           └── UnrealEditor-ModuleName.lib
├── Source
│   └── ModuleName
│       ├── Private
│       │   └── Private の C++ ヘッダ, ソース (.h, .cpp)
│       ├── Public
│       │   └── Public の C++ ヘッダ (.h)
│       └── ModuleName.Build.cs
└── MyPlugin.uplugin

Editor ビルドでは、プラグイン内のモジュールが .dll といったライブラリ形式でバイナリ化されています。また、 Intermediate の中には UnrealHeaderTool が Unreal C++ を処理して生成した追加のソースコードが含まれています。

次に、 Win64 Game 向けバイナリのみが含まれた状態の構成を確認します。

.
├── Intermediate
│   └── Build
│       └── Win64
│           ├── UnrealGame
│           │   ├── Development
│           │   │   └── ModuleName
│           │   │       └── ModuleName.precompiled
│           │   ├── Inc
│           │   │   └── 中間生成ソースコード
│           │   └── Shipping
│           │       └── ModuleName
│           │           └── ModuleName.precompiled
│           └── x64
│               └── UnrealGame
│                   ├── Development
│                   │   └── ModuleName
│                   │       └── Development Game 用 Object ファイル (.obj)
│                   └── Shipping
│                       └── ModuleName
│                           └── Shipping Game 用 Object ファイル (.obj)
├── Source
│   └── ModuleName
│       ├── Private
│       │   └── Private の C++ ヘッダ, ソース (.h, .cpp)
│       ├── Public
│       │   └── Public の C++ ヘッダ (.h)
│       └── ModuleName.Build.cs
└── MyPlugin.uplugin

Game ビルドでは、ソースをコンパイルして生成された .obj ファイルが直接含まれています。ライブラリ形式にまとめられたりはしていません。

また、 .precompiled というプラグインに含まれるビルド済みモジュールの定義ファイルが生成されていることもわかります。 .precompiled の中には、そのモジュールに関連する .obj ファイルのパスのリストが記載されています。

なぜ生成されるバイナリの形式が違うのか?

これは、 Unreal Engine の標準設定では Editor ビルドはモジュラービルド、 Game ビルドはモノリシックビルドという、異なるビルドモードでビルドが実行されるからです。

モジュラービルドでは、あらゆるモジュールは .dll として個別の動的リンクライブラリにビルドされます。この場合、エンジンが起動する際(つまり実行時)にモジュールの .dll をロードすることでモジュールの機能をエンジンに取り込みます。このため、 Editor 起動時にモジュールの .dll が存在していれば、ソースが削除されていても起動が可能です。

一方、モノリシックビルドでは、ビルド時にすべてのモジュールが単一のバイナリにまとめられます。こちらの場合、.cpp ファイルが一度 .obj にコンパイルされ、それらをまとめてリンクすることで単一のバイナリにします。このため、そのモジュールが生成する .obj が揃っていれば、ソースが削除されていてもパッケージング時に行われるリンクを通過することができるのです。

実際にソースを削除する

まずは、 Win64 Editor のソースを削除してみます。すると以下のようになります。

.
├── Binaries
│   └── Win64
│       ├── UnrealEditor-ModuleName.dll
│       └── UnrealEditor.modules
├── Source
│   └── ModuleName
│       └── ModuleName.Build.cs
└── MyPlugin.uplugin

Editor の場合、モジュール定義である .Build.cs および .modules や、ビルド済みバイナリ本体である .dll さえ残せば、あとの C++ ソースや Intermediate は削除してしまって問題ありません。

次に、 Win64 Game のソースを削除してみます。

.
├── Intermediate
│   └── Build
│       └── Win64
│           ├── UnrealGame
│           │   ├── Development
│           │   │   └── ModuleName
│           │   │       └── ModuleName.precompiled
│           │   └── Shipping
│           │       └── ModuleName
│           │           └── ModuleName.precompiled
│           └── x64
│               └── UnrealGame
│                   ├── Development
│                   │   └── ModuleName
│                   │       └── Development Game 用 Object ファイル (.obj)
│                   └── Shipping
│                       └── ModuleName
│                           └── Shipping Game 用 Object ファイル (.obj)
├── Source
│   └── ModuleName
│       └── ModuleName.Build.cs
└── MyPlugin.uplugin

Game の場合、パッケージング時に利用するバイナリは Intermediate 内に配置されています。また、そのバイナリを指定する .precompiled も同様です。これらは削除してはいけません。 ただし、 Intermediate 内の中間生成ソース (Inc) は Editor 同様に削除できます。

Source 以下のソース削除についても Editor と同じで、 .Build.cs を残して削除して問題ありません。

対象とするプラットフォームを増やした場合、プラットフォーム名 (Win64とかAndroidとか……) を冠したフォルダが増えますが、内部構造は同じなので、同様の対応を取ることができます。

余談ですが、この構造を見ると分かる通り、 BuildPlugin コマンドでプラグインをビルドしたときには Development と Shipping に向けたバイナリしか出力されません。 Debug など異なる設定のバイナリを含めたい場合には、 BuildPlugin コマンドでは対応できません。 (自力で作ることはできます)

.Build.cs を修正する

以上でソースの削除は完了ですが、この状態で Plugins フォルダに配置して利用しようとすると、バイナリが存在するにもかかわらずプラグインのビルドが試みられてしまいます。当然、ソースがないのでビルドに失敗し、エディタ起動やパッケージングにも失敗してしまいます。

これを回避するために、 .Build.cs を修正する必要があります。とはいっても簡単で、以下の1行を書き足すだけです。

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

		bUsePrecompiled = true; // これを追記!

    // 以下略

このフラグが意味するのは、「このモジュールには事前コンパイル済みのバイナリがあるので、ビルドは試みなくてよい」ということです。このフラグが立っていると、 UBT はモジュールのバイナリが存在することを期待して該当モジュールに対するビルドを実行しなくなるので、バイナリが含まれてさえいれば正常に動作するようになります。

以上の操作で、プラグインをバイナリだけで共有することが可能になります!

Game と Editor 両方のバイナリを含めたプラグインを配布するには?

.Build.cs への追記は行ったうえで、双方が必要とするバイナリをどちらも残した状態にするだけでOKです。必要とするファイルは .Build.cs と .uplugin を除いて被っていないので、共存できます。

Launcher 版エンジン

Epic Games Launcher からインストールできるエンジンのモジュールは、みな Editor / Game の両方に向けたバイナリを含む状態になっています。Launcher 版のエンジンのモジュールを見に行くと、上記の構造がインストール時から存在していることがわかります。

外部から C++ で参照されるヘッダ等を持つ場合

Unreal Engine のモジュールには、Private なヘッダと Public なヘッダが含まれることができます。 Private なヘッダはモジュール内部のみで include されるヘッダですが、Public なヘッダが外部のモジュールのソースでも include 可能です。

たとえプラグインにバイナリを含めて配布したとしても、外部から include される必要があるヘッダがあるときには、そのヘッダは削除できません。 include が解決できない時点でコンパイル前にビルドが失敗してしまうからです。

このため、C++ 向けの API を持つプラグインのときには以下のようなソースの残し方をする必要があります。

.
├── Binaries
│   └── (残し方は Game か Editor かで変わる)
├── Intermediate
│   └── (残し方は Game か Editor かで変わる)
├── Source
│   └── ModuleName
│       ├── Public
│       │   └── Public の C++ ヘッダ (.h)
│       └── ModuleName.Build.cs
└── MyPlugin.uplugin

Source のうち、 Private だけを削除した形です。 このようなケースでは、モジュール内部が適切に設計され、外部から参照されるべき最低限の API と実装のみが Public 以下に配置されていないと、ソース削除の効果が薄くなることに注意が必要です。 ビルド以前に、適切な宣言と定義の分離、設計がなされている必要があるでしょう。

また勘違いされがちですが、Blueprint 向け API の実装は C++ モジュールの Public / Private とは関連性がないので、Blueprint 向けの関数の実装があるからといってヘッダを残したりする必要はありません。

ビルド済みプラグインの使い方

通常のプラグインと同様に利用することができます。 Plugins フォルダ内部に配置すれば、ビルド環境がなくても自作プラグインを利用してもらうことができます。

おわりに

特に業務でプラグインを開発していると、そのビルドや配布形態について考えなくてはいけなくなることがあります。 UE のプラグインはエンジンやプロジェクトの外に配置されたモジュールとして設計されているので、そのビルドや構成ファイルの役割の理解には、モジュールの知識が役立ちます。

構成ファイルの中身が理解できると、ロードさせるものを細かく制御したり、含めるものを取捨選択したりできるようになって便利なので、ぜひ生成されたプラグインの中身を覗いてみてください。