OpenXR IL2CPP Integration.ja - toydev/HC_VRTrial GitHub Wiki

1. 概要

BepInEx on IL2CPP に OpenXR を組み込んでゲームの VR 化を行う試みについてまとめています。

必要な関連ソースな Unity Editor で OpenXR プロジェクトを作成して手に入れます。 本来それらのソースは Unity Editor が IL2CPP を通してビルドしアプリケーションに組み込まれるものですが、BepInEx プラグインに統合するには自前でビルドする必要があります。 そうした場合、BepInEx プラグイン本体と同様に Unity へのアクセスは Il2CppInterop を介す必要があり、そのための適合作業が必要となります。

この wiki はその適合作業を整理することが目的です。

適合作業の大まかな流れは以下の通りです。

  1. 取込対象の決定
  2. コンパイルシンボルの決定
  3. ビルドエラーの除去
  4. ランタイムエラーの除去

いずれも試行錯誤が必要な作業であり、手戻りが頻繁に発生します。 この wiki は現時点での試行錯誤の結果をまとめたものです

様々な要因によって本内容を他のゲームに適用する場合はそのまま適用できない場合があります。 例えば IL2CPP の Stripping や使用しているレンダリングパイプラインの影響などを受けて対応が変わる部分が出てきます。


2. 前提


3. 取り込みパッケージ

  • [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 は除く)

4. 適合作業

各パッケージに共通する対応を本 wiki にまとめます。個別の事情は各パッケージ毎のページにまとめます。

4.1. コンパイルシンボルの決定

#if キーワードで全体を概観した上で条件付きコンパイルシンボルを決定します。

全体に共通するポイントは以下です。

  • UNITY_EDITOR は不要である。
  • Unity のバージョンに関するものは「Unity 2021.3.14f1」のプロジェクトであることを基準に要否を決定する。

詳細はパッケージ毎に分析の上で決定する必要があります。


4.2. ビルドエラーの除去

4.2.1. 基本型変化への対応

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 インターフェイスを実装していない。

これにより様々なビルドエラーが発生します。

これらの基本型の変化は構文レベルでの影響が発生するため、それぞれの事情に即した対応が必要となります。


System.Action クラス

+=-= でのメソッドのイベント登録や解除処理がビルドエラーになります。 このビルドエラーを目印に対応を行います。

基本的な対応は以下の通りです。

  • Il2CppSystem.Action<引数> のフィールドを用意する。
  • Awake などで Il2CppSystem.Action<引数> フィールドにイベント用のメソッドをキャストして代入する。
    • このキャストは暗黙的型変換により専用のインスタンスを生成する。
  • +=-=Il2CppSystem.Action<引数> フィールドを指定する。

フィールドを媒介する理由は += と対となる -= を適切に処理するためです。

また、Application.onBeforeRender については上記に加えて特別な対応として RenderPipelineManager.beginContextRendering への変更も合わせて行います。 これは正確には URP 対応であり、レンダリングパイプラインの影響に対する対応です。


System.Attribute クラス

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.* クラス

メソッドの引数や戻り値が要求するコレクションの型が変化するためビルドエラーになります。 このビルドエラーを目印に対応を行います。

基本的な対応は以下の通りです。

  • 使っている 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; を追加する。
  • StartCoroutinethis.StartCoroutine に変更し、MonoBehaviourExtensions.StartCoroutine を適用する。

System.IDisposable インターフェイス

各種クラスが System.IDisposable インターフェイスを実装していないことから using 構文でビルドエラーになります。 このビルドエラーを目印に対応を行います。

具体的な対応は以下の通りです。

  • System.IDisposable をラップする System.DisposableExtensions を実装する。
  • System.DisposableExtensions でラップして using を使えるようにする。

System.Exception クラス

Debug.LogException が要求する型が変化するためビルドエラーになります。 このビルドエラーを目印に対応を行います。

具体的な対応は以下の通りです。

  • UnityEngine.DebugExtensions を実装する。
  • ビルドエラーの該当箇所を UnityEngine.DebugExtensions で置き換える。

System.Type クラス

typeof による引数の受け渡し箇所がビルドエラーになります。 代表例は GameObject コンストラクタです。 このビルドエラーを目印に対応を行います。

変更前:
var interactionManagerGO = new GameObject("XR Interaction Manager", typeof(XRInteractionManager));
変更後:
var interactionManagerGO = new GameObject("XR Interaction Manager", Il2CppInterop.Runtime.Il2CppType.From(typeof(XRInteractionManager)));

4.2.2. 各種 UnityEngine 型変化への対応

基本型と同じく各種 UnityEngine 型の扱いにも変化があります。

例えば以下のような変化です。

  • インターフェイスや構造体がクラスに変わっている。
  • インターフェイスの実装関係がなくなっている。
  • メソッドの可視性が protected から public に変わっている。
  • メソッドが存在しなくなっている。

これらにより様々なビルドエラーが発生します。それぞれの事情に即した対応が必要となります。 特に厄介なのが Managed code stripping によりメソッドが削除されている場合です。 代替方法を考える必要があります。

UnityEngine.ISerializationCallbackReceiver インターフェイス

インターフェイスからクラスに変わっており、多重継承でビルドエラーになります。 このビルドエラーを目印に対応を行います。

UnityEngine.ISerializationCallbackReceiver はシリアライズに関係するもので基本的に不要な前提で無効化します。 具体的な対応は以下の通りです。

  • 代替となる UnityEngine.ISerializationCallbackReceiverExtensions インターフェイスを実装する。
  • UnityEngine.ISerializationCallbackReceiverUnityEngine.ISerializationCallbackReceiverExtensions で置き換える。

Plane コンストラクタ(Stripping)

// 変更前(ビルドエラー)
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]);

Pose コンストラクタ(Stripping)

// 変更前(ビルドエラー)
new Pose(position, rotation);
// 変更後
new Pose{ position = position, rotation = rotation };

Resources.Load メソッド(Stripping)

// 変更前(ビルドエラー)
BaseInstance = Resources.Load(path) as T;
// 変更後
BaseInstance = Resources.Load<T>(path);

UnityEngine.SubsystemsModule.ISubsystem インターフェイス

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 による復元がうまくいっていないメソッドがいくつか存在するので手動で代替を実装し置き換えるなどして補います。


...

4.3. ランタイムエラーの除去

4.3.1. ClassInjector の適用

IL2CPP にライフサイクルメソッドを認識させるため MonoBehaviour / ScriptableObject クラスに以下の静的コンストラクタを追加します。

static Xxx()
{
    Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<Xxx>();
}

4.3.2. HideFromIl2Cpp 属性クラスの適用

ClassInjector を適用したクラスの IL2CPP 相互運用不可メソッド/プロパティに [Il2CppInterop.Runtime.Attributes.HideFromIl2Cpp] 属性クラスを適用します。

この作業をしないと ClassInjector 実行時にエラーが出るので、その実行時エラーをつぶす作業です。 具体的にはメソッドの戻り値や引数の型/プロパティの型に純マネージドコードの型が含まれているもの全てに適用する必要があります。

...

⚠️ **GitHub.com Fallback** ⚠️