MRDL:PeriodicTableで見るMRTK V2の機能 - Interractable 編


# MRDLのサンプルがHoloLens 2に対応
MRTK V2 は最近RC2が提供されて着々とリリースに向けた準備が進んでいます。そんな中、以前MRDL（Mixed Reality Design Labs)のサンプルとして提供されていた元素記号のサンプルがMRTK V2対応されて公開されています。HeloLens 2向けの機能を活用できるサンプルに仕上がっており簡単なイベントや動作を見るためには非常にわかりやすいサンプルになっています。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I have published a story on porting <a href="https://twitter.com/hashtag/HoloToolkit?src=hash&amp;ref_src=twsrc%5Etfw">#HoloToolkit</a> based app to <a href="https://twitter.com/hashtag/MRTK?src=hash&amp;ref_src=twsrc%5Etfw">#MRTK</a> v2 + <a href="https://twitter.com/hashtag/HoloLens2?src=hash&amp;ref_src=twsrc%5Etfw">#HoloLens2</a>: <a href="https://twitter.com/hashtag/MixedReality?src=hash&amp;ref_src=twsrc%5Etfw">#MixedReality</a> Design Lab&#39;s Periodic Table of the Elements app<a href="https://t.co/KqPuDDWHAI">https://t.co/KqPuDDWHAI</a> <a href="https://twitter.com/hashtag/MixedRealityToolkit?src=hash&amp;ref_src=twsrc%5Etfw">#MixedRealityToolkit</a> <a href="https://twitter.com/hashtag/MRDL?src=hash&amp;ref_src=twsrc%5Etfw">#MRDL</a> <a href="https://twitter.com/hashtag/GitHub?src=hash&amp;ref_src=twsrc%5Etfw">#GitHub</a> <a href="https://twitter.com/hashtag/AugmentedReality?src=hash&amp;ref_src=twsrc%5Etfw">#AugmentedReality</a> <a href="https://t.co/Jdm10svAXZ">pic.twitter.com/Jdm10svAXZ</a></p>&mdash; Dong Yoon Park (@cre8ivepark) <a href="https://twitter.com/cre8ivepark/status/1138173828931104768?ref_src=twsrc%5Etfw">June 10, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

特にこの中で使われているいくつかの機能のうち、MRTK V2でアクションに対する処理を簡単にできるInteractableを中心にいくつかの機能を紹介します。
実はPeriodicTableは設定上アイトラッキング向けの機能も実装されているのですが、実機がないのでそのあたりの確認はまだしていない状態です。またいずれ紹介したいと思います。

## 利用環境

今回の開発環境は以下のような感じなっています。参考にしてください。

 * Unity 2018.3.9f1()
 * Visual Studio 2017（15.9.11）
 * Windows 10 Pro 1809

## MRTK V2で使われている機能

PeriodicTableでではMRTK V2の機能として以下のものを利用しています。

### 共通機能

これらの共通機能は必須の部品となっています。

* MixedRealityToolkit  
MRTK V2では必須のPrefabになります。MRTKの動作を規定するための各種パラメータを設定するためのProfileを設定します。
* MixedRealityPlayspace  
MRTK V2で動的に生成されるオブジェクトなどを格納するためのルート要素です。実行前にはカメラ用のコンポーネントが入っています。実行するとCursorやDiagnoticsのコンポーネント等がここに配置されます。

MRTK V2を導入したUnity Projectではシーンの新規作成をするとダイアログが表示してまずは初期設定するように指示されます。
まずはDefaultMixedRealityToolkitConfigurationProfileを選択しておきます。

![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/d2d319d3-ead3-27ab-da99-7e0c0f7ec5fb.png)

プロファイルのカスタマイズはMixedRealityToolkitオブジェクトのInspector上で行います。

### 便利な機能

#### Interactable

HoloLens等のアプリケーションでアクション（AirTapやSelect、フォーカスなど）を行ったときにオブジェクトにリアクションを入れるための部品です。例えばフォーカスが入ると色を変えるであったりサイズを調整する実装を行う場合この部品を活用すると基本的なことであればノンコーディングで実施できてしまいます。

[MRTK V2のドキュメント(Interactable)](https://github.com/microsoft/MixedRealityToolkit-Unity/blob/mrtk_development/Documentation/README_Interactable.md)

![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/d095d0d6-3fba-4243-5d34-51df09f5e28a.png)

大きくはInteractableコンポーネントの動きを調整するパラメータとUI制御用の「Profiles」,「Events」の3つに分かれます。

まず動きを調整するパラメータについてです。

##### DefaultInteractableStates  
  Interactableコンポーネントで扱える状態の定義です。Interractableコンポーネントはイベントイベントを補足して内部で状態を持ちます。例えばDefalutInteractableStatesの場合以下の状態を持っています。 
 
  * Default - オブジェクトが何もしてない状態
  * Focus - オブジェクトにフォーカスが入っている状態
  * Pressed - オブジェクトでアクションが発生している状態
  * Disabled - オブジェクトが無効化されている場合
  
  Interactableコンポーネントを持つオブジェクトが選択されたりタップされたりすることで上記の状態が変わります。それに応じてProfilesセクションの設定に応じた変更が入ります。
  この状態はカスタイマイズも可能です。ただし、その場合Interactable自体を継承して拡張しなければいけません。
  
##### Enabled
  Interactableの機能自体の有効/無効を設定します。  
##### Input Actions  
入力の対象となるアクションをしています。アクションについてはMRTKで定義したものの中から選択します。 
 
* Is Global  
チェックを入れるとオブジェクトにフォーカスがなくてもイベントを補足するするようになります。
* Voice Command  
音声コマンド入力の単語を定義します。
* Dimensions  
この設定は主としてInteractableToggleCollectionとセットで利用する機能です。InteractableToggleCollectionはラジオボタンなどの排他のグループを構成し、「選択時のProfile」と「非選択時のProfile」を分けて適用できます。

  ![Interactable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/755d969d-ccd3-cd49-34d6-54394896f4fd.gif)

  この機能を利用する場合はDemensionsを2に設定します（InteractableToggleCollectionについては2以上設定しても意味はありません）。わかりにくいかもしれないですがProfileの上から1番目が「非選択時のProfile」、2番目が「選択時のProfile」となります。
  ![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/bc059263-5a6b-e243-eab1-ec3e21afd657.png)

  InteractableToggleCollectionは例えばこのように設定します。グループにするInteractableなオブジェクトをリストに追加するだけです。
  ![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/65199355-6d15-cebf-67fb-5448b1298147.png)

##### Profiles
  Profilesには先ほどのStateで設定されている状態に応じてオブジェクトのプロパティを設定していきます。

|Inspector|説明|
|:-:|:--|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/ad1ffea0-4b21-423f-0785-d6a0055819b7.png)|<dl><dt>Target</dt><dd>プロパティを適用するGameObjectを設定します。</dd><dt> Theme</dt><dd>適用するテーマを指定します。Themeはテンプレートのようにあらかじめ適用したいパターンで生成することができます。詳細な情報は直下に表示され設定変更も可能です。</dd><dt> Events</dt><dd>対象オブジェクトのイベントを定義します。</dd></dl>|

Profileのパラメータについては現在以下の種類が提供されています。今のところInteractableコンポーネント1つにつき1プロパティの変更のみできる形です。実際には「色を変えながら文字を変えたい」というような実装することはあると思います。このような場合はThemeを自分で作成する形になります。

|ThemeProperty名|説明|
|:--:|:--|
|InteractableActivateTheme|状態変更時、ターゲットとなるGameObjectの活性/非活性を設定
|InteractableAnimatorTheme|状態変更時、ターゲットとなるGameObjectが持つAnimatorコンポーネントに対して状態毎にトリガーを発生する|
|InteractableAudioTheme|状態変更時、状態毎に割り当てられたAudioClipを再生する|
|InteractableColorChildrenTheme|状態変更時、ターゲットとなるGameObjectの子要素のColorを設定する|
|InteractableColorTheme|状態変更時、ターゲットとなるGameObjectの文字色を状態に応じて設定する|
|InteractableMaterialTheme|状態変更時、ターゲットとなるGameObjectのMaterialを状態に応じて設定する|
|InteractableOffesetTheme|状態変更時、ターゲットとなるGameObjectのlocalPositionを状態に応じて設定する|
|InteractableRotationTheme|状態変更時、ターゲットとなるRotationを状態に応じて設定する|
|InteractableScaleTheme|状態変更時、ターゲットとなるGameObjectのlocalPositionを状態に応じて設定する|
|InteractableShaderTheme|状態変更時、ターゲットとなるGameObjectのlocalScaleを状態に応じて設定する|
|InteractableStringTheme|状態変更時、ターゲットとなるGameObjectのText,TextMeshの文字列を状態に応じて設定する
|InteractableTextureTheme|状態変更時、ターゲットとなるGameObjectのTextureを状態に応じて設定する|
|ScaleOffsetColorTheme|状態変更時、ターゲットとなるGameObjectのlocalScale,localPosition,背景色を状態に応じて設定する|

##### Events

EventsにはデフォルトでOnClickイベントがあります。このOnClickイベントは先ほどの"Input Action"で設定したイベント種別に対して発生する形になります。例えば、Input Actionを"Select"で定義していた場合は、HoloLensであればAirTap,IHMDではSelectボタン、HoloLens 2であればボタンを押す動作の時にこのイベントが発生します。
また、"Add Events"ボタンを押すことでOnClick以外のイベントも追加することができます。


|ThemeProperty名|説明|
|:--:|:--|
|InteractableAudioReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/6420e0b1-bdf0-560f-56d6-b85081d7e195.png)<br/>Interactableコンポーネントを持つオブジェクトがクリックされた場合に指定されたAudioClipを再生します。|
|InteractableOnClickReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/8fa93e86-6af0-3689-2e1d-bd74a50c6026.png)<br/>Interactableコンポーネントを持つオブジェクトがクリックされた場合に指定されメソッドを実行します（上記のOnClickと同じ？）|
|InteractableOnFocusReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/1980c5a8-2b00-9529-8e6d-15c1d212b568.png)<br/>Interactableコンポーネントを持つオブジェクトのフォーカスイン/フォーカスアウトでそれぞれ指定されたメソッドを実行します。|
|InteractableOnHoldReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/333a0438-a1a0-cbdc-6130-b1fb1343dbc8.png)<br/>Interactableコンポーネントを持つオブジェクトがホールドされた場合に指定されたメソッドを実行します。|
|InteractableOnPressReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/74140946-e473-04d4-8c15-64559b10e3ca.png)<br/>Interactableコンポーネントを持つオブジェクトが押された場合/離した場合に指定されたメソッドを実行します。|
|InteractableOnToggleReceiver|![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/f9a14b66-d8de-83bb-2736-ec49c80c7576.png)<br/>InteractableToggleCollectionと合わせて使用し、ターゲットのオブジェクトが選択/非選択の場合に指定されたメソッドを実行します。|

#### ObjectCollection

MRTKの途中でMRDLから移植された機能です。配列で格納されたGameObjectをいくつかのパターンで整列することができます。  
整列については動的ではなく、このコンポーネントのUpdateCollectionメソッドを呼び出して整列します。
整列のパターンは以下の通りです。

|種類|表示例|
|:-:|:--|
|Plane|2次元の表形式に配置<br/>![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/5ea0b2b5-11b3-b678-d0ab-c6af32bdd588.png)|
|Cilynder|円筒形に沿って配置<br/>![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/66157840-e441-fd5e-ce96-21e8a45fbe8a.png)|
|Radial|放射状に配置<br/>![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/a981c375-5267-3500-45e1-7f35ace3de2d.png)|
|Sphere|球体に沿って配置<br/>![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/7cddd8d5-7bdc-269f-2b6f-6bfb971e3853.png)|


#### AsyncCoroutineRunner

有用なユーティリティです(個人的に)。この機能は利用するには以下の2つのことをしておく必要があります。

1. AsyncCoroutineRunnerコンポーネントを持つオブジェクトをhierarchyに作成する。
1. この機能を利用するクラスにusing Microsoft.MixedReality.Toolkit.Utilitiesを追加する。

この2つを行うことでUnityのCoroutineを前提とした機能に対してc#言語のasync/await句を適用できます。
何のことかわかりにくいと思うのでサンプルを。
例えば、通信するための実装は以下のようなものが多いと思います。

```csharp
void Start()
{
  StartCoroutine(GetHoge());
}

public IEnumerator GetHoge()
{
  var webRequest = new UnityWebRequest("http://examples.com", "GET"
    , (DownloadHandler) new DownloadHandlerBuffer()
    , (UploadHandler) null);
  
  yield return  webRequest.SendWebRequest();

   //受信後の処理
}
```

これをこの機能で置き換えると以下のようになります。Unity系のライブラリ機能にも関わらずyield return等の制御をC#風に置き換えて表現できるようになっています。

```csharp
void Start()
{
  GetHoge();
}

public async Task GetHoge()
{
  var webRequest = new UnityWebRequest("http://examples.com", "GET"
    , (DownloadHandler) new DownloadHandlerBuffer()
    , (UploadHandler) null);
  
  await  webRequest.SendWebRequest();

   //受信後の処理
}
```
## その他便利な部品(OptimizeSceneforDeviceType)

これ自体はテクニックとして以前にもあったのです。HoloLensとImmersive HMD両方に対応したアプリを作る場合の位置調整機能です。HoloLensは目線の高さがy軸0になりますが、IHMDは床面がy軸0です。このためコンテンツ位置を合わせるための小技が存在します。これは"HolographicSettings.IsDisplayOpaque"というプロパティがtrueの場合はIHMDつまりVR型で動作していることになるので、この時にVR向けの位置調整を行うというものです。
クロスプラットフォームやマルチプレイヤープラットフォームアプリ作るときには参考になるテクニックです。

## まとめ
MRDLのサンプルのPeriodicTableを通して簡単にMRTK V2で追加される"Interactable"についての紹介をしました。MRTK V2のサンプルも含めてコンテンツのHierarchy構成も実は統一されています。
作り方は自由なのですが、わかりやすい構造をしているのでそのあたりも参考にするとよいかと思います。