HoloLensで始めるMRDesignLabs - ObjectCollection、各種ボタン、イベント制御を使う

#HoloToolkitとは実装方法が異なります。
MResignLabsとHoloToolkitはイベントの処理方法がかなり違います。今回はイベント制御で利用するRecieverと、ObjectCollectionを使ってMRDesignLabsの中にあるサンプル用のScene切り替えを行うメニュー画面を作りました。その中でそれぞれの実装の具体的な方法については説明したいと思います。

## MRDesignLabsのイベント周りについての情報
MRDesignLabsのイベント周りについては[dy_karous](https://twitter.com/dy_karous)さんが詳細に整理した資料があります。とてもわかりやすく参考になりますので一読しておくことをお勧めしておきます。

[MRDesignLabを使いジェスチャー入力を取り扱う](http://blog.d-yama7.com/archives/850)

## 開発環境
* Windows 10 Pro
* Unity 5.6.1f1

## 完成したメニューアプリについて
MRDesignLabsにはサンプルとして8つシーンがあります。「MRDesignLabs」と「MrDesignLabs_Examples」の中にそれぞれ4つずつです。これらのシーンへの遷移を行うメニューをMRDesignLabsの「ObjectCollection」オブジェクト、コントロールでUIを作成し、InteractionReceiverを使ってシーン遷移を実現します。実装の手順としては以下の通りになります。

1. 空のプロジェクト(3D)の作成とMRDesignLabsの設定([]())
2. ObjectCollectionの作成とコントロールの追加
3. ObjectCollectionの表示を切替えるToolBarの追加
4. シーン遷移を行うSceneChangeReceiverクラスの実装
5. ObjectCollectionの表示を切り替えるCollectionChangeReceiverクラスの実装
6. Receiverの設定

初めに今回使用するObjectCollectionやRecieverの基本的な設定について説明します。
### 各種機能について
#### ObjectCollection
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/9637d4a4-8950-c920-9278-608955bc64bf.png)

ObjectCollectionの子のGameObjectを色々な形状で整列して表示することができるコレクションです。GridLayoutGroupを拡張したものと考えるとイメージしやすいと思います。
ObjectCollectionは以下の設定情報を持っています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/ef70a64c-bab3-a6b0-c93c-92bc4f192959.png)

| No. | プロパティ名 | 説明 | 設定値など |
|:-:|:-:|:--|:--|
| 1 | NodeList | コレクションの整列対象になるリスト。このリストについてはObjectCollectionの子に入れたGameObjectが対象になります。追加されるリストにはさらに設定があります。<br/><font color="OrangeRed">※CollectionにGameObjectをぶら下げると初期値として自動的に追加されます。以降の変更は手動になります。</font><dl><dt>Size</dt><dd>コレクション内の子のGameObject数<dd/><dt>GameObject名</dt><dd><dl><dt>Name</dt><dd>Nodeの名前（後述のソート順はこの値が対象)</dd><dt>Offset</dt><dd>セル内のオブジェクトの位置</dd><dt>Transform</dt><dd>ノードに対応するGameObjectのTransform</dd></dl></dd></dl>  |   |
| 2 | SurfaceType  | 子のGameObjectの配置形状を設定値。設定値は4つあり、円柱の表面に配置される「Cylinder」、面に配置される「Plane」、球体の表面に配置される「Sphere」、ランダムに配置される「Scatter」です。 | ・Cyliner<br/>・Plane<br/>・Scatter<br/>・Phere |
| 3 | SortType  | オブジェクトを配置時のソート順です。Hierarchyの並び順または名前順での昇順/降順が指定可能です。 | ・Transform<br/>・Alphabetical<br/>・Transform Reversed<br/>・Alphabetical Reversed|
| 4 | OrientType  | オブジェクトの向きを指定します。中心点に対して子のGameObjectを向ける「Face Origin」、常に正面を向ける「Face Forward」及びy軸方向に180度回転させる「Face Origin Reversed」「Face Forward Reversed」の4つの設定があります。 |・Face Origin<br/>・Face Forward<br/>・Face OriginReversed<br/>・Face Forward　Reversed |
| 5 | LayoutType  | コントロールを配置する順序です。行→列の順で並べる「Column Then Row」と列→行の順で名レベル「Row Then column」の2つ設定があります。  | ・Column Then Row<br/>・Row Then Column |
| 6 | Radius  | コントロールの配置する形状の半径です。 |   |
| 7 | Rows  | コントロールを配置する行数を指定します。 |   |
| 8 | Cell Width  | 1セルの幅を指定します。 |   |
| 9 | Cell Height  | 1セルの高さを指定します。 |   |

このGameObjectは特殊でプロパティの設定後「Update Collection」を押下してください。このボタンを押さない限りUI上では設定が反映されません（値は保存されている）。

#### ボタンコントロール

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/69a549c7-c702-4b13-8234-af4cdbcd8507.png)

MRDesignLabsで提供されているボタンコントロールです。左上から順に「SpriteButton」「MeshButton」「ObjectButton」「SquareButton」「RectangleButton」「CircleButton」です。このうち「SpriteButton」「ObjectButton」を除く4つは「CompundButton」をベースにプリセットで用意されているボタンです。

| No. | 種類 | 説明 |
|:-:|:-:|:--|
|1| SpriteButton | ボタンの状態（押下やフォーカス等）に応じて形状をSpriteを用いて変更するボタンです。押下時に色を変えるなどが可能です。 |
|2| MeshButton  |  Meshで形状を変更できるボタンです。SruareButtonなどのアイコンやテキスト表示がないプレーンなボタンです。 |
|3| ObjectButton  | ボタンの状態（押下やフォーカス等）に応じて形状をPrefabを用いて変更するボタンです。押下時に色を変えるなどが可能です。  |
|4| SquareButton  | 正方形のパネル上のボタンです。初期値ではアイコン表示＋テキストの形状ですがアイコンのみやテキストのみ等設定変更可能です。  |
|5| RectangleButton  | 長方形のパネル上のボタンです。初期値ではアイコン表示＋テキストの形状ですがアイコンのみやテキストのみ等設定変更可能です。  |
|6| CircleButton  | 円状のパネル上のボタンです。初期値ではアイコン表示＋テキストの形状ですがアイコンのみやテキストのみ等設定変更可能です。  |

#### ToolBar
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/f43d5c15-a1b1-b103-ea1c-3effe1bfc15b.png)

予め5つの「SquareButton」を横並びにしたPrefabです。Tagalongが設定済みですのでアプリケーションの機能ボタンとしてのベースに利用しやすいものになっています。

### 実装手順
#### 1. 空のプロジェクト(3D)の作成とMRDesignLabsの設定

MRDesignLabsのアセットのインポートを実施します。詳細は[HoloLensで始めるMRDesignLabs - 空セットアップとBounding Boxの利用](http://qiita.com/miyaura/items/cf0ebc41733a4b31e137)と同じ手順ですのでそちらを参照してください。

#### 2. ObjectCollectionの作成とコントロールの追加

まず、シーンを切り替えるボタンをObjectCollectionを用いて配置します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/4bec3870-90f6-2764-6a66-c8dbed1158a7.png)

ObjectCollectionの追加はメニューより「HUX」-「CreateCollection」で行います。

次にコレクションの中にボタンを追加します。ボタンは好きなものを追加してください。今回は
「RectangleButton」を使用しました。Hierarchyで先ほど追加したコレクションを選択し「HUX」-「Buttons」-「Add Compund Button(Rectangle)」を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/673a76f8-01fb-e69b-0231-3d601b7818b0.png)

コントロールは計9つ追加しています。使用するボタンは8つですが、3X3の配置を試すためにわざとそうしています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/acea6089-7af2-d959-4299-43b899d5cfe0.png)

ボタンの名前はそれぞれ遷移するシーン名に合わせてください。これはのちのシーン切替え時にボタンの名前のシーンに遷移するというロジックを書くためです。

* ManipulationGizmo_Examples
* ObjectCollection_Examples
* Progress_Examples
* InteractableObject_Examples
* ButtonsAndReceivers
* CursorExamples
* DebugMenuExample
* InputValidator

#### 3. ObjectCollectionの表示を切替えるToolBarの追加

次にObjectCollectionの形状を変更するためのToolbarを追加します。Projectの検索欄にToolbarと入力し、ToolBar.prefabをHierarchyにドラッグして追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/c88d15e0-f3a2-6a2c-c5bd-54b7f4274660.png)

ObjectCollectionの形状は4種類ですので、ボタンを1個削除します。また、ToolBarのアイコンをそれらしいものに変更します。ObjectCollectionの形状を表すテスクチャはすでにありますのでそれを利用します。ToolBarのいずれかのボタンを選択しInspectorで変更します。Inspectorでコンポーネント「Compund Button Icon」の中の「Select ButtonIconProfile or create new profile」でprofileを「DefaultButtonIconProfileTexture」に変更します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/571393dd-3317-110d-91bf-711d7c88135a.png)

変更後プロパティの内容が変わります。この中からIconの選択リストの中に「ObjectCollectionCylinder」「ObjectCollectionPlane」「ObjectCollectionScatter」「ObjectCollectionSphere」がありますので4つのボタンそれぞれに設定を行います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/8fe1334b-82c1-1cc0-c185-cf28da63bc77.png)

以上で、UI周りの設定は完了です。あとはそれぞれのボタンに対するイベント制御を行います。

#### 4. シーン遷移を行うSceneChangeReceiverクラスの実装
シーン遷移についてはUnityのSceneManager.LoadSceneメソッドを用いて行います。最初にシーン遷移を行うための処理を書きます。Projectの任意の場所にC#スクリプトで「SceneChangeReceiver」を追加します。次に中身の実装を行います。まず、継承クラスをMonoBehaviorからInteractionReceiverに変更します。これによりジェスチャーによって各イベントの処理を記述できるようになります。今回はボタン押下ですのでタップのイベントを追加します。
タップ時に発生するイベントはOnTappedイベントになります。
処理は以下のように書きます。OnTtapedメソッドの第一引数にはタップされたオブジェクトが入っているので、このオブジェクトの名前のシーンに遷移するというシンプルなロジックになります。

```csharp:SceneChangeReceiver.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using System.Collections;
using System.Collections.Generic;
using HUX.Interaction;
using HUX.Receivers;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneChangeReceiver : InteractionReceiver
{
    protected override void OnTapped(GameObject obj, InteractionManager.InteractionEventArgs eventArgs)
    {
        SceneManager.LoadScene(obj.name);
    }
}
```

#### 5. ObjectCollectionの表示を切り替えるCollectionChangeReceiverクラスの実装
ObjectCollectionの形状を変更するためのボタン処理をするスクリプトの実装を行います。Projectの任意の場所にC#スクリプトで「CollectionChangeReceiver」を追加します。先ほどと同様に継承クラスをMonoBehaviorからInteractionReceiverに変更します。このスクリプトでもToolBarのボタン押下時に処理を実施するためOnTappedメソッドを実装します。先ほど同様オブジェクトの名前に応じてシーン遷移用のObjectCollectionのパラメータを変更します。

```csharp:CollectionChangeReceiver.cs
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using System.Collections;
using System.Collections.Generic;
using HUX.Collections;
using HUX.Interaction;
using HUX.Receivers;
using UnityEngine;

public class CollectionChangeReceiver : InteractionReceiver
{
    private ObjectCollection _objectCollection;

    void Start()
    {
        _objectCollection = this.Targets[0].GetComponent<ObjectCollection>();
        SetPlaneParams();
    }

    protected override void OnTapped(GameObject obj, InteractionManager.InteractionEventArgs eventArgs)
    {
        if (obj.name.Equals("ObjectCollectionCylinder"))
        {
            SetCylinderParams();
        }
        else if (obj.name.Equals("ObjectCollectionPlane"))
        {
            SetPlaneParams();
        }
        else if (obj.name.Equals("ObjectCollectionScatter"))
        {
            SetScatterParams();
        }
        else if (obj.name.Equals("ObjectCollectionSphere"))
        {
            SetSphereParams();
        }
    }
    
    private void SetSphereParams()
    {
        _objectCollection.SurfaceType = ObjectCollection.SurfaceTypeEnum.Sphere;
        _objectCollection.transform.localPosition = Vector3.zero;
        _objectCollection.Radius = 2f;
        _objectCollection.Rows = 3;
        _objectCollection.UpdateCollection();
    }

    private void SetScatterParams()
    {
        _objectCollection.SurfaceType = ObjectCollection.SurfaceTypeEnum.Scatter;
        _objectCollection.transform.localPosition = new Vector3(0f, 0f, 2f);
        _objectCollection.Radius = 1f;
        _objectCollection.UpdateCollection();
    }

    private void SetPlaneParams()
    {
        _objectCollection.SurfaceType = ObjectCollection.SurfaceTypeEnum.Plane;
        _objectCollection.transform.localPosition = new Vector3(0f, 0f, 2f);
        _objectCollection.Radius = 2f;
        _objectCollection.Rows = 3;
        _objectCollection.UpdateCollection();
    }

    private void SetCylinderParams()
    {
        _objectCollection.SurfaceType = ObjectCollection.SurfaceTypeEnum.Cylinder;
        _objectCollection.transform.localPosition = Vector3.zero;
        _objectCollection.Radius = 2f;
        _objectCollection.Rows = 1;
        _objectCollection.UpdateCollection();
    }
}


```

#### 6. Receiverの設定

次に、作成した「SceneChangeReceiver」「CollectionChangeReceiver」をコンポーネントとして追加し、Inspectorでプロパティの設定を行います。まず空のGameObjectを追加して名前を「Receivers」に変更します。名前はわかりやすいものであれば任意です。次に、先ほど作成した２つのコンポーネントを追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/b30b6c8b-70ad-24f8-7f6e-31c4e3b229a9.png)

追加後、Receiverオブジェクトを選択しInspectorのプロパティに先ほどの２つのコンポーネントが追加されていることを確認してください。最後に、それぞれのコンポーネントのプロパティを以下のように変更します。Elementの要素については順序は特にありませんが設定が漏れているといべんとが反応しないためもれなく設定してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/b72cfb96-bcd1-6221-2751-f140790dc411.png)

##### CollectionChangeReceiverの設定
* Interractables
 * Size : 4
 * Element0～3 : 以下のGameObjectをHierarchyからドラッグ
     * ObjectCollectionCylinder
     * ObjectCollectionPlane
     * ObjectCollectionScatter
     * ObjectCollectionSphere
* Targets
 * Size : 1
 * Element0 : CollectionをHierarchyからドラッグ

##### SceneChangeReceiverの設定
* Interractables
 * Size : 8
 * Element0～7 : 以下のGameObjectをHierarchyからドラッグ
     * ManipulationGizmo_Examples
     * ObjectCollection_Examples
     * Progress_Examples
     * InteractableObject_Examples
     * ButtonsAndReceivers
     * CursorExamples
     * DebugMenuExample
     * InputValidator
* Targets : 設定なし
  
設定は以上になります。

#### 7.ビルド

最後に、ビルドを実行します。ビルド設定では0番目に今回作成したメニューのシーンを設定し、以降に8つのシーンをすべて追加します。それ以外は通常のHoloLensの設定でビルドを実施します。

### 最後に
MRDesignLabsの方がイベントの可視化されている点と関連性がわかりやすいことやnGUI等を含めて同じ実装方法で制御ができるというメリットはあります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/84549d88-40c9-8ffa-492a-921b72408568.png)
例：Receiverが含まれるオブジェクトを選択すると緑色の線はイベントを受けるオブジェクト、赤色の線はイベントによって何らかの作用を受けるオブジェクトで補助線がひかれる。

一方、作るもの次第ではベースにするものを変えていく必要があります。例えばUI操作がないものにMRDesignLabsのAssetを投入する意味があるのかないのかなどです。ライブラリとして上にのせるものが増えるとそれだけ処理は重くなっていく傾向はあるのでバランスを見て導入が必要になると思います。