OpenXR IL2CPP Integration.ja - toydev/HC_VRTrial GitHub Wiki
BepInEx on IL2CPP に OpenXR を組み込んでゲームの VR 化を行う試みについてまとめています。
必要な関連ソースな Unity Editor で OpenXR プロジェクトを作成して手に入れます。 本来それらのソースは Unity Editor が IL2CPP を通してビルドしアプリケーションに組み込まれるものですが、BepInEx プラグインに統合するには自前でビルドする必要があります。 そうした場合、BepInEx プラグイン本体と同様に Unity へのアクセスは Il2CppInterop を介す必要があり、そのための適合作業が必要となります。
この wiki はその適合作業を整理することが目的です。
適合作業の大まかな流れは以下の通りです。
- 取込対象の決定
- コンパイルシンボルの決定
- ビルドエラーの除去
- ランタイムエラーの除去
いずれも試行錯誤が必要な作業であり、手戻りが頻繁に発生します。 この wiki は現時点での試行錯誤の結果をまとめたものです
様々な要因によって本内容を他のゲームに適用する場合はそのまま適用できない場合があります。 例えば IL2CPP の Stripping や使用しているレンダリングパイプラインの影響などを受けて対応が変わる部分が出てきます。
-
[email protected]
- 依存
-
com.unity.xr.core-utils
が前提としている。 -
com.unity.xr.openxr
が前提としている。 -
com.unity.xr.interaction.toolkit
が前提としている。
-
- 取込対象
-
Unity プロジェクトルート/Library/PackageCache/[email protected]/InputSystem
配下の*.cs
(サブディレクトリ含む、Editor
,Plugins/PlayerInput
,Devices/Remote
は除く)
-
- 依存
-
[email protected]
- 依存
-
com.unity.xr.openxr
が前提としている。
-
- 取込対象
-
Unity プロジェクトルート/Library/PackageCache/[email protected]/Runtime
配下の*.cs
-
- 依存
-
[email protected]
- 依存
-
com.unity.xr.interaction.toolkit
が前提としている。
-
- 取込対象
-
Unity プロジェクトルート/Library/PackageCache/[email protected]/Runtime
配下の*.cs
(サブディレクトリ含む)
-
- 依存
-
[email protected]
- 取込対象
-
Unity プロジェクトルート/Library/PackageCache/[email protected]/Runtime
配下*.cs
UnitySubsystemsManifest.json
windows/x64/UnityOpenXR.dll
Loaders/windows/x64/openxr_loader.dll
-
- 取込対象
-
[email protected]
- 取込対象
-
Unity プロジェクトルート/Library/PackageCache/[email protected]/Runtime
配下の*.cs
(サブディレクトリ含む、AR
は除く)
-
- 取込対象
各パッケージに共通する対応を本 wiki にまとめます。個別の事情は各パッケージ毎のページにまとめます。
#if
キーワードで全体を概観した上で条件付きコンパイルシンボルを決定します。
全体に共通するポイントは以下です。
-
UNITY_EDITOR
は不要である。 - Unity のバージョンに関するものは「Unity 2021.3.14f1」のプロジェクトであることを基準に要否を決定する。
詳細はパッケージ毎に分析の上で決定する必要があります。
Il2CppInterop
によって提供される Unity
機能へのインターフェイスには様々な基本型(System.*
のクラス/インターフェイス群)の扱いに変化があります。
例えば以下のような変化です。
-
System.*
のクラス/インターフェイスがIl2CppSystem.*
のクラスに置き換わっている。-
System.Attribute
クラスを継承したクラス群がIl2CppSystem.Attribute
クラスを継承したクラスに変わっている。 -
System.Collections.Generic.List
インスタンスを受け取る(または返す)メソッドがIl2CppSystem.Collections.Generic.List
インスタンスを受け取る(または返す)メソッドに変わっている。 -
System.Collections.Generic.ICollection
インターフェイスがIl2CppSystem.Collections.Generic.ICollection
クラスに変わっている。
-
- 各種クラスと
System.*
インターフェイスの実装関係がなくなっている(ただし、メソッド自体は存在する)。-
Il2CppSystem.Collections.Generic.List
クラスがICollection
インターフェイスを実装していない。 - 各種クラスが
System.IDisposable
インターフェイスを実装していない。
-
これにより様々なビルドエラーが発生します。
これらの基本型の変化は構文レベルでの影響が発生するため、それぞれの事情に即した対応が必要となります。
+=
や -=
でのメソッドのイベント登録や解除処理がビルドエラーになります。
このビルドエラーを目印に対応を行います。
基本的な対応は以下の通りです。
-
Il2CppSystem.Action<引数>
のフィールドを用意する。 -
Awake
などでIl2CppSystem.Action<引数>
フィールドにイベント用のメソッドをキャストして代入する。- このキャストは暗黙的型変換により専用のインスタンスを生成する。
-
+=
や-=
にIl2CppSystem.Action<引数>
フィールドを指定する。
フィールドを媒介する理由は +=
と対となる -=
を適切に処理するためです。
また、Application.onBeforeRender
については上記に加えて特別な対応として RenderPipelineManager.beginContextRendering
への変更も合わせて行います。
これは正確には URP 対応であり、レンダリングパイプラインの影響に対する対応です。
Il2CppSystem.Attribute
の派生となったクラスは属性クラスとして機能せずビルドエラーになります。
このビルドエラーを目印に対応を行います。
IL2CPP 上ではマネージドコードに設定した属性クラスは処理されません。 IL2CPP 上メタデータとして属性クラスに相当する情報があるのではと想像していますが、現状 Il2CppInterop でそれを設定する方法についての知識を持ち合わせていません。その前提で対応を考えます。
ビルド/UnityEditor/シリアライズ/表明などに関連する属性クラスは MOD の文脈においては不要なのでコメントアウトしてよいと判断します。 ほとんどの対応はコメントアウトとなりますが、動作に関連する属性クラスは個別対応が必要になることがあります。 それらは各パッケージのそれぞれのコンテキストに置いて判断が必要になります。
以下はコメントアウトしてよいと判断した属性クラス群です。
属性クラス | 対応 | 補足 |
---|---|---|
AddComponentMenu | コメントアウト | UnityEditor 系 |
AOT.MonoPInvokeCallback | コメントアウト | ビルド系 |
CreateAssetMenu | コメントアウト | UnityEditor 系 |
DisallowMultipleComponent | コメントアウト | 表明系 |
ExecuteInEditMode | コメントアウト | UnityEditor 系 |
FormerlySerializedAs | コメントアウト | シリアライズ系 |
Header | コメントアウト | UnityEditor 系 |
HelpURL | コメントアウト | UnityEditor 系 |
HideInInspector | コメントアウト | UnityEditor 系 |
Min | コメントアウト | 表明系 |
MovedFrom | コメントアウト | シリアライズ系 |
PreferBinarySerialization | コメントアウト | シリアライズ系 |
Preserve | コメントアウト | ビルド系 |
Range | コメントアウト | 表明系 |
SelectionBase | コメントアウト | UnityEditor 系 |
SerializeField | コメントアウト | シリアライズ系 |
Tooltip | コメントアウト | UnityEditor 系 |
メソッドの引数や戻り値が要求するコレクションの型が変化するためビルドエラーになります。 このビルドエラーを目印に対応を行います。
基本的な対応は以下の通りです。
- 使っている
System.Collections.Generic.Xxx
クラスをIl2CppSystem.Collections.Generic.Xxx
クラスに変更する。 - 関連するコードを必要に応じて修正する。
必要に応じた修正の例は以下の通り。
//// Il2CppSystem.Collections.Generic.List に対して Il2CppSystem.Collections.Generic.IEnumeable を要求してくる場合
// 変更前:
m_MasterComponentStorage.AddRange(k_TempComponentList);
// 変更後:
// Il2CppSystem.Collections.Generic.List が元の System.Collections.Generic.List のインターフェイスを完全に再現していないため明示的なキャストが必要になる。
// Il2CppSystem は IL2CPP ネイティブコードの処理を仲介する単なるラッパーであり、IL2CPP ネイティブコード側でインターフェイスの実装関係が成立しているので動作する。
m_MasterComponentStorage.AddRange(k_TempComponentList.Cast<Il2CppSystem.Collections.Generic.IEnumerable<TFilterType>>());
//// 配列(System.Array)に対して Il2CppSystem.Collections.Generic.IEnumeable を要求してくる場合
// 変更前:
m_MasterComponentStorage.AddRange(currentEntry.HostedComponents);
// 変更後:
// foreach の Add に置き換えた。
foreach (var i in currentEntry.HostedComponents) m_MasterComponentStorage.Add(i);
UnityEngine.MonoBehaviour.StartCoroutine
については要求する引数の型が Il2CppSystem.Collections.IEnumerator
となり通常の書き方ではビルドエラーになります。
これについては用意された専用の方法使います。具体的な対応は以下の通りです。
-
using BepInEx.Unity.IL2CPP.Utils;
を追加する。 -
StartCoroutine
をthis.StartCoroutine
に変更し、MonoBehaviourExtensions.StartCoroutine
を適用する。
各種クラスが System.IDisposable
インターフェイスを実装していないことから using 構文でビルドエラーになります。
このビルドエラーを目印に対応を行います。
具体的な対応は以下の通りです。
-
System.IDisposable
をラップするSystem.DisposableExtensions
を実装する。 -
System.DisposableExtensions
でラップして using を使えるようにする。
Debug.LogException
が要求する型が変化するためビルドエラーになります。
このビルドエラーを目印に対応を行います。
具体的な対応は以下の通りです。
-
UnityEngine.DebugExtensions
を実装する。 - ビルドエラーの該当箇所を
UnityEngine.DebugExtensions
で置き換える。
typeof
による引数の受け渡し箇所がビルドエラーになります。
代表例は GameObject
コンストラクタです。
このビルドエラーを目印に対応を行います。
変更前:
var interactionManagerGO = new GameObject("XR Interaction Manager", typeof(XRInteractionManager));
変更後:
var interactionManagerGO = new GameObject("XR Interaction Manager", Il2CppInterop.Runtime.Il2CppType.From(typeof(XRInteractionManager)));
基本型と同じく各種 UnityEngine
型の扱いにも変化があります。
例えば以下のような変化です。
- インターフェイスや構造体がクラスに変わっている。
- インターフェイスの実装関係がなくなっている。
- メソッドの可視性が
protected
からpublic
に変わっている。 - メソッドが存在しなくなっている。
-
IL2CPP
の Managed code stripping によりメソッド自体が削除されている。
-
これらにより様々なビルドエラーが発生します。それぞれの事情に即した対応が必要となります。 特に厄介なのが Managed code stripping によりメソッドが削除されている場合です。 代替方法を考える必要があります。
インターフェイスからクラスに変わっており、多重継承でビルドエラーになります。 このビルドエラーを目印に対応を行います。
UnityEngine.ISerializationCallbackReceiver
はシリアライズに関係するもので基本的に不要な前提で無効化します。
具体的な対応は以下の通りです。
- 代替となる
UnityEngine.ISerializationCallbackReceiverExtensions
インターフェイスを実装する。 -
UnityEngine.ISerializationCallbackReceiver
をUnityEngine.ISerializationCallbackReceiverExtensions
で置き換える。
// 変更前(ビルドエラー)
var plane = new Plane(s_Corners[0], s_Corners[1], s_Corners[2]);
// 変更後
var plane = new Plane();
plane.Set3Points(s_Corners[0], s_Corners[1], s_Corners[2]);
// 変更前(ビルドエラー)
new Pose(position, rotation);
// 変更後
new Pose{ position = position, rotation = rotation };
// 変更前(ビルドエラー)
BaseInstance = Resources.Load(path) as T;
// 変更後
BaseInstance = Resources.Load<T>(path);
IL2CPP でジェネリクスを扱うのは難しいので XRDisplaySubsystem / XRInputSubsystem を全般的にIntegratedSubsystem として扱い、識別が必要な場合は追加した以下の定数を使って識別する形で全体的に書き換える方針としました。
public partial class OpenXRLoaderBase : XRLoaderHelper
{
public const string OPEN_XR_DISPLAY = "OpenXR Display";
public const string OPEN_XR_INPUT = "OpenXR Input";
また、IntegratedSubsystem には Stripping により消失し、かつ Il2CppInterop による復元がうまくいっていないメソッドがいくつか存在するので手動で代替を実装し置き換えるなどして補います。
...
IL2CPP にライフサイクルメソッドを認識させるため MonoBehaviour / ScriptableObject クラスに以下の静的コンストラクタを追加します。
static Xxx()
{
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<Xxx>();
}
ClassInjector を適用したクラスの IL2CPP 相互運用不可メソッド/プロパティに [Il2CppInterop.Runtime.Attributes.HideFromIl2Cpp] 属性クラスを適用します。
この作業をしないと ClassInjector 実行時にエラーが出るので、その実行時エラーをつぶす作業です。 具体的にはメソッドの戻り値や引数の型/プロパティの型に純マネージドコードの型が含まれているもの全てに適用する必要があります。
...