53.4K Views
March 02, 24
スライド概要
講演者:太田 尚哉
「Unreal Engine Meetup Connect - Vol.1 - ゲーム開発編」の講演資料です。
アーカイブ動画:
https://www.youtube.com/live/nzKB5sCBCnk?si=ujPx55_yqWyLhjZC
イベントページ:
https://leon-gameworks.connpass.com/event/305752/
Unreal Engine をメインとするゲーム会社、株式会社Leon Gameworks のアカウントです。
ゲーム開発を助ける エディタ拡張と プラグイン化について 太田 尚哉 / Ota Naoya
自己紹介 現在フリーランスソフトウェアエンジニアとして、 主にUnreal Engine関連のゲーム開発に携わっています。 個人では数年前からUnreal Engine向けのコードプラグインの開発と マーケットプレイスへの出品を行っています。 Naotsun (@Naotsun_UE)
今回の講演について 私がマーケットプレイスへ出品している コードプラグインの実装内容を元に、 ゲーム開発を助けるエディタ拡張の事例と それらの機能をプラグイン化するメリットや テクニックに関してお話させて頂きます。
目次 1 ゲーム開発を助けるエディタ拡張 2 エンジン改造を無くしてプラグイン化 3 プラグインを出品する際の注意点
ゲーム開発を助けるエディタ拡張 1 1 ゲーム開発を助けるエディタ拡張 1 詳細パネルの拡張 2 開発時専用のデータ 3 INIからコードを切り替える
詳細パネルの拡張 詳細パネルとは... レベルエディタやグラフエディタなどで よく見るプロパティを編集する所。 他にもプロジェクト設定や シーケンサのキーの中身なども
詳細パネルの拡張 IPropertyTypeCustomization 構造体の詳細パネルの拡張を行うインターフェース。 Int32やFVector、FColorなどBlueprintで利用できる エンジン組み込みの型の拡張を行っている。
詳細パネルの拡張 IDetailCustomization クラスの詳細パネルの拡張を行うインターフェース。 配置されたActorの詳細パネルのコンポーネントのリストや、 トランスフォームなどもここで拡張している。
詳細パネルの拡張 どのように活用する...? アセットを作成することで簡単にプルダウン表示にできる 構造体を追加できるプラグインの実装内容を元にご紹介します。
詳細パネルの拡張 そもそもなぜこの機能が必要なのか...? エンジン標準で用意されている機能のみを使用する場合、 スケルタルメッシュのソケットの名前や、 データテーブルのRowNameなどをFName型で扱う場合が多い。
詳細パネルの拡張 タイプミスや別のIDを指定してしまう可能性 FName型で設定を行う場合、 エディタで手入力する必要があるためタイプミスが発生したり、 別のIDをペーストする可能性がある。 また、C++ではどのIDも同じFName型なので、 別のIDの値を引数などで指定した場合にエラーにならない。
詳細パネルの拡張 大まかな実装の流れ まずは詳細パネル上での表示を拡張する構造体を定義する。 新しく定義しなくてもエンジン側で用意されている構造体に 独自の表示の拡張を行うこともできる。
詳細パネルの拡張 大まかな実装の流れ IPropertyTypeCustomizationを継承して、 FPulldownStructBaseを継承した構造体を詳細パネル上で プルダウンリストで表示されるように拡張する。
詳細パネルの拡張 大まかな実装の流れ 詳細パネルを拡張するクラスを FPropertyEditorModuleの RegisterCustomPropertyTypeLayoutと UnregisterCustomPropertyTypeLayoutで 登録/登録解除する。
詳細パネルの拡張 大まかな実装の流れ CustomizeHeaderか CustomizeChildrenで StructPropertyHandleから 拡張した表示から編集を 行いたい変数のハンドルを 取得して、それを通して変数の 実際の値を編集する。
詳細パネルの拡張 実装時の注意点 CustomizeHeaderとCustomizeChildrenの違いは 左の画像のようにプロパティ名と同じ行に表示する場合は CustomizeHeaderで実装して、 右の画像のように複数のプロパティがありプロパティ名の下に 表示する場合はCustomizeChildrenで実装する。
詳細パネルの拡張 実装時の注意点 シーケンサのキーで拡張した型を 使用する場合、CustomizeHeaderや CustomizeChildrenでプロパティの値を 変更するとエンジンコードで クラッシュする。 編集対象がUMovieSceneSignedObjectを 継承している場合は、値の設定を 1フレーム遅らせて解決できる。 ※実装方法によっては 発生しないかもしれません。
詳細パネルの拡張 Pulldown Builderでは
詳細パネルの拡張 Pulldown Builderはマーケットプレイスで 無料でダウンロードできる他、 Github上にソースコードを公開しているので、 詳細パネルを拡張する際には 参考にして頂ければ...!
おまけ Pulldown Builderのように文字列をプルダウンから 選択させる方法としてGetOptionsというメタ指定子もある。 FStringかFNameのもしくはその配列のプロパティで使用できる。 UFUNCTIONを付加したFString配列を返す関数の戻り値が プルダウンに表示される。 TMap向けにGetKeyOptionsとGetValueOptionsという メタ指定子も存在する。
ゲーム開発を助けるエディタ拡張 1 1 ゲーム開発を助けるエディタ拡張 1 詳細パネルの拡張 2 開発時専用のデータ 3 INIからコードを切り替える
開発時専用のデータ 開発時専用のデータとは... ゲームの開発中でのみ使用するデータ。 テスト用のマップ、デバッグメニュー、 データテーブルのテスト用の行データなど...。 エディタなどでの開発中は利用できて、 出荷時には削除されていてほしいデータのこと。
開発時専用のデータ エディタオンリーとはどう違う? WITH_EDITORやWITH_EDITORONLY_DATAのように UPROPERTYやUFUNCTIONにも対応した エディタ環境以外ではコンパイルされない ようにするマクロがある。
開発時専用のデータ エディタオンリーとはどう違う? アセット単位の場合は、 アセットのクラスでUObject::IsEditorOnlyをオーバーライドして エディタオンリーのクラスにしたり、 アセットのクラスをエディタモジュールでクラスを定義して パッケージには含まれないようにしたりもできる。
開発時専用のデータ エディタオンリーとはどう違う? 他にもクックしないディレクトリをプロジェクト設定から 指定できる。
開発時専用のデータ エディタオンリーとはどう違う? しかし、一部のテストデータやデバッグメニューなどを含む デバッグ機能はDevelopmentやTestの パッケージでも利用したいケースが多い。 クックしないディレクトリをビルド構成ごとに変更すれば アセット単位ではできるが、もっと細かい単位で テストデータを扱いたい...。
開発時専用のデータ どのように実装する...? 開発時専用の行データを追加したり、enumの要素と連動する 拡張データテーブルなどを追加するプラグインの実装内容を 元にご紹介します。
開発時専用のデータ 大まかな実装の流れ パッケージへの保存やクック時の保存はUObject::Serializeで行われる。 ここで 開発専用のデータを含めるかどうかを切り替える。
開発時専用のデータ 大まかな実装の流れ 引数のFStructuredArchiveRecordから現在セーブ中かロード中かなど シリアライズの状況を取得することができる。 他にもクック中か、クック対象のプラットフォームも取得できる。
開発時専用のデータ 大まかな実装の流れ セーブ/ロードのタイミングで 開発専用の行データのマップと、 両方の順序を保持するマップを 処理するかどうかを切り替える。
開発時専用のデータ 大まかな実装の流れ Enhanced Data Tablesではエディタ環境設定で指定された ビルド構成の場合でのみ開発専用の行データが 削除されるようになっている。
開発時専用のデータ 大まかな実装の流れ FArchiveから取得できるクック対象のプラットフォームを元に、 そのプラットフォームで設定されているビルド構成を取得する。 UE5.2より前と以降ではプラットフォームごとに設定されている ビルド構成を保持している場所が異なる。
開発時専用のデータ 大まかな実装の流れ 開発専用データ 有り 開発専用データ 無し
開発時専用のデータ 実装時の注意点 データのセーブロードは先頭から順に行われるため、削除するかもしれ ないデータは必ず存在するデータの後ろに配置する。 ここに気を付けないと場合によってはクラッシュする。
開発時専用のデータ Enhanced Data Tablesは マーケットプレイスにて販売しています。 サンプルデータで実際にパッケージの ビルド構成でデータが変化することが 確認できます。
おまけ TValueOrErrorは任意の型と、FString、FName、FTextの いずれかの型のエラーメッセージをセットで扱える。 エラーメッセージを出力引数で定義する必要が無くなる。
ゲーム開発を助けるエディタ拡張 1 1 ゲーム開発を助けるエディタ拡張 1 詳細パネルの拡張 2 開発時専用のデータ 3 INIからコードを切り替える
INIからコードを切り替える そもそもどういうこと...? エディタ上のプロジェクト設定や、エディタ環境設定などの INIの値をBuild.csで確認して、マクロの定義を変更することで INIの値からコンパイルするコードの内容を変更できる。
INIからコードを切り替える どのように活用する...? コードやアセットにリマインダを設定して 期限が切れたらコンパイルやクック時に エラーや警告を出すプラグインの実装内容を 元にご紹介します。
INIからコードを切り替える 大まかな実装の流れ まずはプロジェクト設定かエディタ環境設定でBuild.csから 確認したい変数を定義する。 INIを経由するのでどんな型でもBuild.cs側からは文字列として しか取得できない。
INIからコードを切り替える 大まかな実装の流れ Build.csでINIに保存されている値を確認して、 期限切れのコードがあった場合にコンパイルエラーにするのか、 コンパイル警告にするのかを切り替えている。
INIからコードを切り替える 大まかな実装の流れ ReadOnlyTargetRulesの ProjectFileから 起動中のプロジェクトの パスが取得できるので、 そこを基準に任意の INIファイルにアクセスする。
INIからコードを切り替える 大まかな実装の流れ INIファイル内の値を取得する方法については、プラグインなので ライブラリに依存せず愚直にパース処理を記述しているが、 WinAPIなどが使えるなら使った方が楽。
INIからコードを切り替える Deprecated Reminderはマーケットプレイスにて 販売しています。 他にもコンパイル時条件で警告を出したり、 コンテンツブラウザのフィルタを追加するなどの エディタ拡張も含まれています。
おまけ Enhanced Data Tablesでも同じテクニックを使用しています。 開発専用の行データを扱う部分のコードは エディタ環境設定で指定された ビルド構成ではコンパイルされません。
目次 1 ゲーム開発を助けるエディタ拡張 2 エンジン改造を無くしてプラグイン化 3 プラグインを出品する際の注意点
エンジン改造を無くしてプラグイン化 1 2 エンジン改造を無くしてプラグイン化 1 なぜプラグイン化するのか 2 Slateとは 3 なぜEUWやUTBではなくSlateを使うのか 4 3 Slate経由でエンジン側のUIを拡張
なぜプラグイン化するのか Unreal C++を使用するゲーム開発では大抵の場合、 エンジンビルドを行っているため、何かエンジン側の機能を 拡張したい場合は、エンジンコードに手を加えるはず。 エンジン拡張はエンジンの不具合を修正したり、 直接コードを変更するため、実装が用意だったりするが、 デメリットもある。 エンジン拡張のデメリット ・バージョンアップの際のマージ ・他プロジェクトへのマージ ・ビルド時間が長い などなど...
なぜプラグイン化するのか プラグイン化することでエンジンのバージョンが変わった際に 変更箇所をマージすることなく、 プラグインコードの対応のみで完結する。 他プロジェクトへ組み込む際もエンジンバージョンの違いによる コンパイルエラーは発生するかもしれないが、組み込みは簡単。 プラグインをGithubなどで公開すると色々なケースで使用されて、 今まで気が付かなかった不具合が見つかったりすることも...
エンジン改造を無くしてプラグイン化 1 2 エンジン改造を無くしてプラグイン化 1 なぜプラグイン化するのか 2 Slateとは 3 なぜEUWやUTBではなくSlateを使うのか 4 3 Slate経由でエンジン側のUIを拡張
Slateとは SlateはUnreal C++によって構成され、 メソッドチェーンを利用して記述する UIをデザイン/制御するライブラリ。 Unreal Editor自体も ほとんど全てSlateで作成されていて、 おなじみのUMGの内部でも Slateが利用されている。
Slateとは SlateはSWidgetというUObjectのような基礎クラスがあり、 それを継承して独自のSlateクラスを定義することができる。 慣例としてSlateクラスは接頭辞にSを付ける。
Slateとは メソッドチェーンで記述できるようにする コンストラクタの引数を定義する マクロ一式が提供されていて、少なくとも SLATE_BEGIN_ARGS、SLATE_END_ARGS、 void Construct(const FArguments& InArgs) の定義が必要。
エンジン改造を無くしてプラグイン化 1 2 エンジン改造を無くしてプラグイン化 1 なぜプラグイン化するのか 2 Slateとは 3 なぜEUWやUTBではなくSlateを使うのか 4 3 Slate経由でエンジン側のUIを拡張
なぜEUWやUTBではなくSlateを使うのか SlateはUIのデザインもコード上で行うため、プログラマ向け。 コードを触らずにエディタ機能などを作成するために Editor Utility Widget (EUW) や User Tool Box (UTB) など エディタ上でエディタ向けのUIや機能を作成できるものが 用意されている。
なぜEUWやUTBではなくSlateを使うのか EUWはUE4.23からあるため使ったことがある人も多いかも... UMGと同様の手順でエディタ機能のUIを作成できる。
なぜEUWやUTBではなくSlateを使うのか UTBはEUWを使用せず独自のツールボックスを作成できる機能。 独自のコマンドをBlueprintでも作成可能。
なぜEUWやUTBではなくSlateを使うのか EUWやUTBを使用した方がエディタ機能のUIは作りやすい。 しかし、エディタに組み込まれたUIに変更を加えたり、 エンジン側で用意されたUIを流用したりするには Slateを扱う必要がある。 また、Slateで作成された機能はランタイムでも動作するため、 Slateでエディタのタブで動作するデバッグメニューを 作った場合、パッケージでも同様の機能を利用できるように したりすることができる。
エンジン改造を無くしてプラグイン化 1 2 エンジン改造を無くしてプラグイン化 1 なぜプラグイン化するのか 2 Slateとは 3 なぜEUWやUTBではなくSlateを使うのか 4 Slate経由でエンジン側のUIを拡張
Slate経由でエンジン側のUIを拡張 Slateを使うメリットとして、 エンジン側で実装されたエディタのUIに対して変更を行ったり、 エディタUIの任意の箇所に自前のUIを挿入することができる。
Slate経由でエンジン側のUIを拡張 どのように実装する...? グラフエディタにグラフ全体のミニマップを追加する プラグインの実装内容を元にご紹介します。
Slate経由でエンジン側のUIを拡張 Slateの構造 Slateはウィンドウ以下は全て ツリー構造になっている。 ツリー上のいずれかの ウィジェットを取得すれば 親ウィジェットや子ウィジェットを 辿って目的のウィジェットを 取得することができる。
Slate経由でエンジン側のUIを拡張 足がかりになるSlateのウィジェットの取得 FSlateApplicationのLocateWindowUnderMouseでマウスカーソル 直下のウィジェットのパスを取得できる。
Slate経由でエンジン側のUIを拡張 足がかりになるSlateのウィジェットの取得 FGlobalTabManagerのGetActiveTabで現在フォーカスされている タブのウィジェットを取得できる。 タブはメインフレームと結合していても単独のウィンドウでも 取得することができる。
Slate経由でエンジン側のUIを拡張 足がかりになるSlateのウィジェットの取得 FSlateApplicationのOnFocusChangingや FUserActivityTrackingのOnActivityChangedでユーザーの操作で フォーカスが変わる時に変更前後のウィジェットを取得できる。
Slate経由でエンジン側のUIを拡張 目的のSlateのウィジェットを探す 親ウィジェットと子ウィジェットを 再帰的に走査する。
Slate経由でエンジン側のUIを拡張 目的のSlateのウィジェットを探す Slateのウィジェットはインスタンスを作成する際に SNewやSAssignNewを使用するが、このマクロの中で 型名を文字列で渡す処理があり、ウィジェットはそれぞれ 自身の型名を文字列で保持している。
Slate経由でエンジン側のUIを拡張 目的のSlateのウィジェットを探す Slateのウィジェットの型を取得することができるため、 間違った型を避けてキャストすることも可能。 Slateのウィジェットは基本TSharedPtrなどで扱うため、 キャストにはStaticCastSharedPtrや StaticCastSharedRefを使用する。
Slate経由でエンジン側のUIを拡張 目的のSlateのウィジェットを探す 関数テンプレートとマクロを作成して、UObjectのCastのように Slateのウィジェットをキャストできるようにする。
Slate経由でエンジン側のUIを拡張 目的のSlateのウィジェットを探す 親ウィジェット、子ウィジェットの走査とキャストを 組み合わせることで基本的にはどこのウィジェットにも アクセスすることができる。
Slate経由でエンジン側のUIを拡張 独自のウィジェットを挿入する SOverlayやSVerticalBoxなどパネル系のウィジェットに アクセスして、動的に独自のウィジェットを挿入できる。
Slate経由でエンジン側のUIを拡張 Graph Minimapはマーケットプレイスにて販売しています。 Slateのウィジェットを挿入するテクニックの他、 グラフエディタを描画する方法なども 含まれています。
Slate経由でエンジン側のUIを拡張 エディタ上のテキストボックスを編集する エディタ上のテキストを翻訳して結果を表示するプラグインの 実装内容を元に実装方法をご紹介します。
Slate経由でエンジン側のUIを拡張 エディタ上のテキストボックスを編集する コンテンツブラウザなどの検索欄のようなシンプルな編集可能な テキストボックスは、素直にSetTextで値を設定することができる。
Slate経由でエンジン側のUIを拡張 エディタ上のテキストボックスを編集する 関数や変数の名前のようにダブルクリックなどで編集モードになる SInlineEditableTextBlockの場合はSetTextする前に EnterEditingModeで編集モードにする必要がある。
Slate経由でエンジン側のUIを拡張 Translation Toolkitはマーケットプレイスにて販売しています。 エディタ上のテキストを編集するテクニックの他、 ツールチップのテキストを編集する方法や C++からPythonを実行して結果を取得する 方法なども含まれています。
目次 1 ゲーム開発を助けるエディタ拡張 2 エンジン改造を無くしてプラグイン化 3 プラグインを出品する際の注意点
プラグインを出品する際の注意点 1 3 プラグインを出品する際の注意点 1 UE5.2以降のC#版UHTで気を付けること 2 UE5.3でStrictIncludesが使用できない
UE5.2以降のC#版UHTで気を付けること UE5.2以降C++で作成されていたUHTがC#版のものに変更された ことによる不具合で、ローカルビルドでは問題なくビルドできるが、 マケプレ側のビルドでのみ~.gen.cpp内でインクルードパスが 利用できない旨のビルドエラーが発生する場合がある。
UE5.2以降のC#版UHTで気を付けること Engine/Source/Programs/Shared/EpicGames.UHT/Exporters/CodeGen/UhtCodeGenerator.csの InitHeaderInfoにログを仕込んで確認した所、PublicIncludePathsを使用 するとUhtPackage.Module.IncludeBaseの値が最初に指定した 値に置き換わってしまうようだった。
UE5.2以降のC#版UHTで気を付けること PublicIncludePathsを使用する場合、UE5.2以降では 最初にプラグインの正しいディレクトリを指定することで回避できる。 PrivateIncludePathsは使用しても問題ない。
プラグインを出品する際の注意点 1 3 プラグインを出品する際の注意点 1 UE5.2以降のC#版UHTで気を付けること 2 UE5.3以降でStrictIncludesが使用できない
UE5.3以降でStrictIncludesが使用できない そもそもStrictIncludesとは...? UATを使用してプラグインをビルドする際に指定できるパラメータ。 インクルードファイルを厳しく判定し、 指定しない場合にビルドが通るコード場合でも StrictIncludesを指定している場合は通らないケースがある。 例)TSubclassOf #include "Templates/SubclassOf.h"
UE5.3以降でStrictIncludesが使用できない タイトルの通り、UE5.3以降では今のところStrictIncludesを指定して プラグインをビルドすることができないが、先ほどのケースと違い インクルードエラーがエンジンコードで発生しているため、 プラグインコード側での修正が難しい。 普段StrictIncludesを使用している場合は、 UE5.3でビルドする際にのみ使用しないようにする必要がある。
UE5.3以降でStrictIncludesが使用できない 原因はまだ調査中... おそらくこちらの問題もC#版UHTになったことによる不具合かと 思いますが、エラーの発生箇所がエンジンコードになっているため 調査が難航中です。 何かご存じの方が居りましたら、ご教示頂けると助かります。
目次 1 ゲーム開発を助けるエディタ拡張 2 エンジン改造を無くしてプラグイン化 3 プラグインを出品する際の注意点
ご清聴 ありがとう ございました