HoloLens 1アプリ、復活させてみない？HoloLens 2へのマイグレーション手順 vol.0

# HoloLens 1の時のアプリケーションどうしました？

HoloLens 1で作ったアプリ。色々あったかと思います。HoloLens 2は国内では7月から購入できた状況です、つまり最近ようやくHoloLens 2向けアプリができているということ。ということは結構多くのアプリはHoloLens 1で作ったものが多いのでは？と思います。

今回はHoloLens 1のアプリをHoloLens 2で復活させるためのノウハウ蓄える第1弾としてHoloLens 1で動くアプリ「MRDL_Lunar_Module」をHoloLens 2で動かすまでをやってみたいと思います。

![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/413026d8-0312-9a43-c7b5-2aacfd858755.gif)

Lunar Moduleというアプリは「Mixed Reality Design Labs」というMixed Reaity のUXデザインを検討するために作られたプロジェクトのサンプルの1つです。アタリの「月面着陸ゲーム」という昔懐かしのゲームをMixed Reaityで体験できるという素敵なアプリです。
時々遊ぶのですがHoloLens 1で動かすのが少し面倒になりHoloLens 2向けに移植を行ってみようと思いました。今回はUnity 2019.4上でビルドできるように変更しHoloLens 2へデプロイするところまでです。最終的にはMixed Reality Toolkit V2への移植までを取り扱う予定です。

1. 参照情報
2. HoloLens 1とHoloLens 2の違い
3. 移行の手順をLunar Moduleで実施し、まずはHoloLens 2で動かす
4. まとめ


## 1. 参照情報

* (公式)[HoloLens (第 1 世代) アプリを HoloLens 2 に移植する](https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/porting-apps/porting-hl1-hl2?WT.mc_id=MR-MVP-5003104)
* (公式)[Lunar Module](https://docs.microsoft.com/en-us/windows/mixed-reality/develop/unity/lunar-module?WT.mc_id=MR-MVP-5003104)
* お題
  * Git(Microsoft):[MRDL_Unity_LunarModule](https://github.com/microsoft/MRDL_Unity_LunarModule)
  * Git(HoloLens 2で動かせる版):[MRDL_Unity_LunarModule(branch:migrate_to_mrtk)](https://github.com/TakahiroMiyaura/MRDL_Unity_LunarModule/tree/migrate_to_mrtk)
  * [Lunar Module_1.0.0.0_ARM.zip(ビルド済みモジュール)](https://1drv.ms/u/s!AnOsAEvu_kOYg_96RXe5c_LcluKRIA?e=paMjXK)

## 2. HoloLens 1とHoloLens 2の違い
移植をするにあたって最低限知っておくといいかもしれない情報を整理しておきましょう。

### デバイスの違い

HoloLens 1からHoloLens 2では色々違いはありますが移植を考えるともっと差異があるのは以下の入力方法とデバイスのアーキテクチャーになります。

| 項目 | HoloLens | HoloLens 2 |
|:-:|:-:|:-:|
| 入力 | Head Gase + Gesture / Voice Input | Head/Eye Gaze + Gesture / Voice Input, Hand Tracking, Eye Tracking  |
| アーキテクチャ | x86 | ARM/ARM64  |

アーキテクチャーについてはCPUが変更になっているため、HoloLens 1で作ったアプリケーションはARMを含めて対応できる形に変更する必要があります。単純にHoloLens 1で作ったAppxをデプロイしても動かないことがあるということです。
移植の基本はこのx86アーキテクチャーのアプリケーションをARM/ARM64アーキテクチャで動かせるようにするということになります。

### 開発環境の違い

HoloLens 1のコンテンツ開発はUnityがメインとなっているパターンが多いと思います。当時のUnityのバージョンはUnity 2018以前のものが多く、現行のHoloLens 2への移植を考えると、Unityのバージョンも上げていく必要があります。
現状推奨されるHoloLens 2の開発環境は以下の通りです。
(公式ドキュメントはこの組み合わせでチュートリアルは構成されています)

* Unity 2019.4
* MRTK 2.4.0

ここで問題になるのが、「Scripting Backend」が「.NET」のものです。Unity 2019では「Scripting Backend」として「.NET」は廃止されています。代わりにIL2CPPを利用する必要があります。Unity上でのみコーディングをしていたものは特に意識する必要ないのですが、いったんC#でUnityから出力し修正していたものについては、現状Visua Studio上で変更していたコードをモジュール化しUnity側に取込むなどが必要になると思います。

## 3. 移行の手順をLunar Moduleで実施し、まずはHoloLens 2で動かす

実際に移植に必要な手順としては(公式)[HoloLens (第 1 世代) アプリを HoloLens 2 に移植する](https://docs.microsoft.com/ja-jp/windows/mixed-reality/develop/porting-apps/porting-hl1-hl2?WT.mc_id=MR-MVP-5003104)の手順が基本になります。

### 3.1.最新ツールのインストール

まずは、開発環境を整えましょう。HoloLens 2への移行に関しては開発ツールを新しくしておきます。

* Unity 2019.4.XX
* Visual Studio 2019(16.8)

Unity 2019.4はLTSバージョンでMixed Reality Toolkit V2でも推奨のバージョンなので当面はこのバージョンでよいと思います。Visual Studio 2019はエディションはどれでも構いませんが、バージョンは最新の16.8に。Mixed Reality Toolkitと組合わせて使う時に既知の問題となっているものです。Visual Studio 2019（バージョン16.8)より前ではSolver、Hand Menu等いくつかの機能が正しく動作しないという問題があります。MRTK自体は回避コードも提供されているため実際には影響受けないのですが、16.8では解消されているのでこの際最新化しておきましょう。

参考サイト：[HoloLens 2の実機でHandMenuやSolverなどが動作しない場合の対処](https://blog.hololab.co.jp/entry/2020/09/27/232640)

### 3.2.Unityバージョンの更新

まず、最初に実施するのが、古いUnityバージョンで作られたHoloLensプロジェクトをアップグレードをします。
これは特に難しく考えずにUnityで開きなおす形です。「Lunar Module」のリポジトリは現在Unity 5.6.1f1となっていて相当古いですね。。。
これをUnity 2019.4で開いていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/0d411b3a-2f5c-e312-09cf-f1a7b8aba351.png)

Unity Hubを開き、[リストに追加]を選択し、[MRDL_Unity_LunarModule]リポジトリを追加します。追加後Unity バージョンを「2019.4.X」に変更します。変更後起動すると確認ダイアログが表示されますので、[確認]を押して作業を進めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/14d288f3-0ca4-6d86-58d9-5d3f77b344fb.png)

Unity Editorが開く過程でアップグレードが進みます。自動的にパッケージの更新などが実施されます。
無事に開くと（？）エラーが出てくると思います。まずはこれらをつぶしていく形になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/9fb9c8cf-ca7e-458a-af14-e61d1764b42c.png)

### 3.3.次々現れるエラーをつぶしていく

今回のLunar Moduleではいくつか修正が必要になります。
Unityの古いバージョンから新しいバージョンに変更すると問題になるのはたいてい以下の名前空間に関するものです。

* UnityEngine.<Font color="red">VR</font>.WSA... → UnityEngine.<font color="red">XR</font>.WSA...

入力ソースに関するものは、VRだけではなくなったためにXRと名前空間が変わっているのでまずはこの辺りを直します。
なお、修正箇所については#ifディレクティブ内で記述されているものもあるため、必ずしもEditor上で表示されるメニューだけではありません。
文字列検索で調べると漏れはなくなると思います。
修正を完了するとまたいくつかエラーが出てくると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/77229a76-5752-69af-e90d-86844ff2952d.png)

Lunar ModuleについてはMRDesignLabs_Unity_Tools内のMovieScreen.csも見直しが必要になるようです。
このスクリプトではMovieTextureクラスを使用しているのですが、VideoPlayerに変更します。
合わせて、いくつかの実装を見直します。

```csharp:MovieScreen.cs(20行目付近)
#if !UNITY_ANDROID && !UNITY_WEBGL
  public VideoPlayer targetMovie;
#endif
```


```csharp:MovieScreen.cs(49行目付近)
#if !UNITY_ANDROID && !UNITY_WEBGL
  if (MovieStates.Length > newState)
  {
    MovieStateDatum stateDatum = MovieStates[newState];
    if (stateDatum.targetMovie != null)
    {
      mRenderer.material.SetTexture("_MainTex", stateDatum.targetMovie.texture);
      mRenderer.material.SetTexture("_AlphaTex", stateDatum.targetMovie.texture);
      stateDatum.targetMovie.isLooping = true;
```

```csharp:MovieScreen.cs(49行目付近)
        yield return new WaitForSeconds((float)stateDatum.targetMovie.clockTime);
```

修正するとさらにエラーが・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/ce73db76-24a8-1457-9be7-6f2295476ec1.png)

今度はInteractionManager周りですね。UnityEngine.<Font color="red">VR</font>.WSA.Input.InteractionManager → UnityEngine.<font color="red">XR</font>.WSA.Input.InteractionManagerに変更します。ただ、このクラスは発生するイベントおよびデリゲートがごっそり変わっているので、色々変更が必要になります。

まずデリゲート周りでは、イベントの登録/解除のプロパティ名が変更になっています。

```csharp:InputSourceHand.cs（375行目付近。変更前）
    private void Awake() {
#if UNITY_WSA
        InteractionManager.SourceDetected += WSASourceEntered;
        InteractionManager.SourceLost += WSASourceLost;
        InteractionManager.SourceUpdated += WSASourceUpdate;

        InteractionManager.SourcePressed += WSAFingerPressed;
        InteractionManager.SourceReleased += WSAFingerReleased;
#endif
    }

    // Cleanup hand event subscriptions
    private void OnDestroy() {
#if UNITY_WSA
        InteractionManager.SourceDetected -= WSASourceEntered;
        InteractionManager.SourceLost -= WSASourceLost;
        InteractionManager.SourceUpdated -= WSASourceUpdate;

        InteractionManager.SourcePressed -= WSAFingerPressed;
        InteractionManager.SourceReleased -= WSAFingerReleased;
#endif
    }
```


```csharp:InputSourceHand.cs（375行目付近。変更後）
    private void Awake() {
#if UNITY_WSA
        InteractionManager.InteractionSourceDetected += WSASourceEntered;
        InteractionManager.InteractionSourceLost += WSASourceLost;
        InteractionManager.InteractionSourceUpdated += WSASourceUpdate;

        InteractionManager.InteractionSourcePressed += WSAFingerPressed;
        InteractionManager.InteractionSourceReleased += WSAFingerReleased;
#endif
    }

    // Cleanup hand event subscriptions
    private void OnDestroy() {
#if UNITY_WSA
        InteractionManager.InteractionSourceDetected -= WSASourceEntered;
        InteractionManager.InteractionSourceLost -= WSASourceLost;
        InteractionManager.InteractionSourceUpdated -= WSASourceUpdate;

        InteractionManager.InteractionSourcePressed -= WSAFingerPressed;
        InteractionManager.InteractionSourceReleased -= WSAFingerReleased;
#endif
    }
```

デリゲートの定義も変わっているので見直しましょう。

```csharp:InputSourceHand.cs(399行目付近。変更前)
#if UNITY_WSA
    public void WSASourceEntered(UnityEngine.XR.WSA.Input.InteractionSourceState state) {...}

    public void WSASourceUpdate(UnityEngine.XR.WSA.Input.InteractionSourceState state) {...}

    public void WSASourceLost(UnityEngine.XR.WSA.Input.InteractionSourceState state) {...}

    private void WSAFingerReleased(UnityEngine.XR.WSA.Input.InteractionSourceState state) {...}

    private void WSAFingerPressed(UnityEngine.XR.WSA.Input.InteractionSourceState state) {...}
```

```csharp:InputSourceHand.cs(399行目付近。変更後)
#if UNITY_WSA
    public void WSASourceEntered(UnityEngine.XR.WSA.Input.InteractionSourceDetectedEventArgs args) {...}

    public void WSASourceUpdate(UnityEngine.XR.WSA.Input.InteractionSourceUpdatedEventArgs args) {...}

    public void WSASourceLost(UnityEngine.XR.WSA.Input.InteractionSourceLostEventArgs args) {...}

    private void WSAFingerReleased(UnityEngine.XR.WSA.Input.InteractionSourceReleasedEventArgs args) {...}

    private void WSAFingerPressed(UnityEngine.XR.WSA.Input.InteractionSourcePressedEventArgs args) {...}
```

見直し時に引数が変わっているのですが、変更前のInteractionSourceStateの値はargs.stateプロパティで取得できるので、さほど大きな修正は不要です。
このように繰り返してエラーの個所を解消していきます。

なお、HoloToolkitやMRTK V1を使っている場合は、上記以外にビルドを支援するツールの中でもエラーが発生する場合があります。これは廃止になった.NET Scripting Backendの話が影響しています。具体的にはEditorUserBuildSettings.wsaGenerateReferenceProjectsプロパティが廃止になっておりエラーになります。現状代替案がないのと公式も旧資産のため対応しないことになっていますので、とりあえずコメントアウトします。

```
#if !UNITY_2019_1_OR_NEWER
      var oldWSAGenerateReferenceProjects = EditorUserBuildSettings.wsaGenerateReferenceProjects;
      if (buildInfo.WSAGenerateReferenceProjects.HasValue)
      {
        EditorUserBuildSettings.wsaGenerateReferenceProjects = buildInfo.WSAGenerateReferenceProjects.Value;
      }
#endif
```

上記のようにとりあえず、#ifディレクティブで無効化します。（この影響でHoloToolkitのメニューからビルドはできなくなります）
すべてのエラーに対処すればとりあえずは完了です。この時点で一度資産管理上コミットしておきましょう。

### 3.4.ARM用にコンパイル

次に、修正後の資産をARMでビルドできるようにします。ポイントになるのは以下の1点のみ

* <font color="red">使ってるライブラリARMのライブラリある？</font>

これだけです。使ってるライブラリを全部調査してみてください。基本的にUnity Package Manager上のものはARMでも問題ないものが多いようです。問題なのは個別に入れているパッケージ類です。
Lunar Moduleの場合HoloToolkitを利用しています。この中には「[Mixed Reality Toolkit](https://github.com/microsoft/mixedrealitytoolkit)」の「PlaneFinding」と「SpatialUnderstanding」が使われており、これらのライブラリがLunar Moduleではx86用しか用意されていません。
ですので、これらのライブラリのARM版のDLLを用意してあげます。[Mixed Reality Toolkit](https://github.com/microsoft/mixedrealitytoolkit)から自分でビルドしてもいいのですが、[Mixed Reality Toolkitのhtk_releaseブランチ](https://github.com/microsoft/MixedRealityToolkit-Unity/tree/htk_release)からDLLは取得可能です。それぞれ以下の場所にpluginsとして格納されているのでコピーしましょう。

* /Assets/HoloToolkit/SpatialMapping/Plugins配下
* /Assets/HoloToolkit/SpatialUnderstanding/Plugins

ここまでやってビルド設定をHoloLens 2向きの設定にしてビルドをします。あとはVisual Studioを起動してHoloLens 2にデプロイできれば完了です。動作確認をしてみましょう。なお、今回のLunar Moduleでは問題なく動作しました。HoloLens 2に慣れ切ってる人たちは今一度「Head Gaze + AirTap」操作を思い出して月面着陸ゲームを楽しんでください。

### 3.5.MRTK V2に移行

上記までの手順でアプリケーションとして破綻せずに動くところまで確認が可能になります。次のステップとしては、Mixed Reality Toolkit V2への移行です。Lunar Moduleの場合はかなり古めのHoloToolkitを使っているため、これをMixed Reality Toolkit V2に変更して完全にHoloLens 2対応を行っていきます。
今考えているやり方は、いったんMixed Reality Toolkit V2に移行し、その後HoloLens 2に沿ったUX体験ができるように改良して完全対応させるというもの。
この辺りの知見はまた次の機会にしたいと思います。

## 4.まとめ

今回はHoloLens 1のアプリをHoloLens 2へ移行するための手順として、Lunar Moduleを題材にまずはHoloLens 2で動かせるようにUnity 2019.4用にソースコードを見直し、HoloLens 2にデプロイできるように追加のライブラリをARM版にも対応できるようにしました。まずはここまでの手順まで行えば、昔作った「あんなアプリ」や「こんなアプリ」もHoloLens 2で動かすことが可能なります。
とは、本格的にHoloLens 2に移行するにはMixed Reality Toolkit V2への対応が必要になります。今後もLunar Moduleを題材に実際の移行はどういう形になるか紹介していきたいともいます。