Unity＋Viveで開発する

##UnityでViveを使って動かしてみます。
とりあえずサンプルが動かし、超基礎的なVRコンテンツを実装するまでやります。

Viveのセットアップは終わっていることを想定しています。

では始めます。

##1.SteamVRプラグインをインストール
Asset StoreからSteamVRをDL＋Importします。
https://www.assetstore.unity3d.com/jp/#!/content/32647
![SnapCrab_NoName_2016-7-23_22-52-17_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/5177a0ab-7e6c-5262-572c-b67f24e148c4.png)

（約７MBのはずなのにえらくDLに時間がかかった）

##2.SteamVRをセットアップ
AssetStoreからのDL+Importが終わると、自動的にSteamVRセットアップWindowが開きます。
もしSteamVRセットアップWindowga開かない/誤って閉じてしまった場合は、ImportしたPluginsフォルダとSteamVRフォルダを削除し、再度Importしましょう。
![SnapCrab_SteamVR_Settings_2016-7-23_22-59-43_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/156a8460-9c87-5b57-9e00-8c9bdb53246f.png)
これは、Unityプロジェクトの設定をVR特化に一括変更してくれます。
特にこだわりがなければ `Accept All` を実行しましょう。


##3.Exampleシーンを見てみる
Assets/SteamVR/Scenesにexampleシーンが用意されています。見てみましょう
![SnapCrab_NoName_2016-7-23_23-11-57_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/7f135993-556e-c6cf-6ebd-5e2204eab5c8.png)

こんな感じのシーンが開きます。
![SnapCrab_Unity (64bit) - exampleunity - ViveFirstTest - PC Mac & Linux Standalone DX11_2016-7-23_23-12-38_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/a5fcf74f-1b61-3332-af76-ed2cdc951ab9.png)

Unityでこのシーンを再生すると、
- ViveHMDとシーンカメラの同期
- Viveコントローラーの同期
- ベースステーションの同期
が確認できます。

Hierarchyに存在する
- Main Camera (origin)
- [SteamVR]
このどちらかのオブジェクトを削除したり、InActiveにすると、ViveがUnityから切り離され、ホーム（This is Real）に戻されます。

また、シーン再生中にコントローラーを顔に近づけるとコントローラーが見えなくなります。
ホームやThe Labでは近づけてもコントローラーが消えることはなかったので、UnityでViveコンテンツを作る際はひと手間加える必要がありそうです。


##3.用意されているPrefabを見てみる
- Main Camera (origin)
- [SteamVR]
がサンプルシーンの中で使われており、何やら重要そうなので軽く見ていこうと思います。

###Main Camera (origin)
- SteamVR_ControllerManagerコンポーネント
Controllerを管理するためのコンポーネントなのだろうけど、これをInActiveにしてもコントローラーは動くのでこのサンプルでは特に何もしていないように思える。

内部にこんな感じでリスナーに登録しているので参考にはなる。
しかしEventが文字列でキャメルやスネークが入り混じるのは気持ち悪いな・・・。ミスが多発すること間違いなしやわ・・・

```
		SteamVR_Utils.Event.Listen("input_focus", OnInputFocus);
		SteamVR_Utils.Event.Listen("device_connected", OnDeviceConnected);
		SteamVR_Utils.Event.Listen("TrackedDeviceRoleChanged", OnTrackedDeviceRoleChanged);
```


- SteamVR_PlayAreaコンポーネント
SceneやGameWinsow上にGizmo（補助線）を書き込むためのコンポーネントだと思います。
PlayAreaコンポーネントのパラメータにSize項目があり、2m × 1.5m や4m x 3m 等のプリセットが用意されています。このサイズに合わせてGizmoが描かれるのでステージ作りの参考になると思います。
MainCamera (origin)にAddされているMesh RendererやMeshFilderはPlayAreaコンポーネントによって使われていました。
ベースステーションから取得できる実際のPlayAreaサイズは、ゲーム実行時にViveから取得していました（たぶん）

###Controller (left)
Main Camera (origin)の子要素にあるコントローラー。right用のGameObjectもあるけどとりあえずLeftだけ見てみる。
SteamVR_TrackedObjectコンポーネントがAddされている。このコンポーネントはコントローラー専用のコンポーネントというわけではなく、HMDやそのほかのDeviceにも使える。
IndexパラメータとしてHmd、Device1~15が選べる。
Device1~15まで用意されているということは、今はベースステーションで読み取れる要素がコントローラー2個とHmdの計3個しかないけど、今後はもっと増えることを示唆しているのかも。（あれ、今でも自由にトラッキングできるオブジェクトを追加できるんだっけ・・・？）
このサンプルでは、LeftコントローラーはDevice1、RightコントローラーDevice2が設定される（これはゲーム再生直後に設定される。ゲーム再生前のInspectorではIndexはNoneが設定されている）
あー、使えるコントローラーが４個くらいになってみんなでバトルロワイヤルできるといいなぁ・・・

このコンポーネントではベースステーションから拾ってきた座標や角度の情報をViveが指定した更新タイミングに合わせて更新している。このコンポーネントでDeviceタイプを設定しているわけではないのが注意しなきゃいけないところだ。外部からSetDeviceIndex関数が呼ばれて「LeftコントローラーはDevice1使ってね~」みたいな感じで使われていると思う。
「Viveが指定した更新タイミング」は `SteamVR_Utils.Event.Listen("new_poses", OnNewPoses);` でイベント購読できる。

コントローラーGameObjectの子要素にはModelがあるけどこれは何もやっていないと思う。削除して実行しても何も変わらない・・・
ただModelには SteamVR_RenderModelコンポーネントがくっついており、Tracked Devicesオブジェクト以下にあるDevice1~15のオブジェクトたちにくっついているものと同じだ。
コントローラーの子要素に３Dモデルを付けようとしたけど、Tracked Deviceとして管理したほうが良いよねって変更されたから削除し忘れたのかな・・・完全な憶測だけど

コントローラーの実態はTracked Devicesオブジェクト以下にある各Deviceオブジェクトであることを忘れないようにしよう。
ただしこれはあくまで３Dモデルを表示するだけのオブジェクトで、位置や角度の同期は、Controller オブジェクトのSteamVR_TrackedObjectコンポーネントが行っているので注意！
（このサンプルではDevice1と2がコントローラーのモデルになってる）


と、ここまで書いてきて気が付いたけど、
Tracked DevicesオブジェクトのDevice1とDevice2にSteamVR_TrackedObjectコンポーネントとSteamVR_RenderModelコンポーネントがAddされていた。
つまり、コントローラーの実態は、Controller (left)オブジェクトではなく、Tracked DevicesオブジェクトのDevice1とDevice2だった。
Controller (left)も一応は位置や角度同期をしている。
Controller (left)とDevice1の意味合いが重なっているので、一見無意味で紛らわしいことこの上ないように見えるけど、
これは「Indexさえ指定すればいつでもどこからでもそのトラッキングオブジェクトの位置や角度等の情報が取ってこれるんだよ」というViveさんのありがたい教えなのだと思っておこう。

###Main Camera(head)
Main Camera(origin)の子要素。実際にカメラを持っているのはこの子。
SteamVR_GameViewコンポーネントとIndexにHMDを指定したSteamVR_TrackedObjectコンポーネントを持っている。

SteamVR_GameViewコンポーネントは、カメラがシーンのレンダリングが終わった後に呼ばれるOnPostRender関数でGLをいじっている。
あくまでUnity上で開発確認できるGameWindowの描画をいじるためのものなのかもしれない。

実際にViveHMDとUnityのカメラをつなげているコンポーネントが見当たらないのが不思議だ・・・


###[SteamVR]
[SteamVR]オブジェクトにはSteamVR_RenderコンポーネントがAddされている。
これはUnityの映像をViveHMDへレンダリングするというよりは、Unityから主力された映像のうち、どのレイヤ
ーをViveHMDに流すか決められるコンポーネントだ。
左目と右目それぞれにLayerMaskをかけることもできるので使いようによっては面白いオブジェクトができるのかもしれない。
一人はPCの画面を見て、もうひとりはHMDをかぶる。PCの画面だけに移るオブジェクトを用意してあげてPC画面を見ている人とHMDを被ってる人が連携して進むゲームとか？

とりあえずSteamVR_TrackedObjectコンポーネントが一番大切だということが分かった。

##3.オリジナルコンテンツを作ってみる
作る内容は
- コントローラーのトリガーを押すと、コントローラーの位置にCubeが生成される
になります。

###3-1新規シーンの作成
新規シーンを作成します。
新規なのでMain Cameraとライトしかありません。
![SnapCrab_NoName_2016-7-24_1-36-42_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/b0b10657-2f72-d8cb-1fae-9e29edf07c6f.png)

Main Cameraは削除しておきましょう。

###3-2専用のカメラとコントローラーの追加
Assets/SteamVR/Prefabsに[CameraRig]Prefabがあるのでそれをシーンに追加する。
![SnapCrab_NoName_2016-7-24_1-43-51_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/67575/9763119b-2943-4876-c5e4-8ba32438cd75.png)

これだけでゲームを再生すると、カメラとコントローラーが同期します。

###3-3コントローラーのトリガーイベントを拾ってくる

こんな感じのコードでイベントを拾ってくることができる。
参考させていただいたサイト（https://framesynthesis.jp/tech/unity/htcvive/）

```

    public SteamVR_TrackedObject trackedObject;
    // Use this for initialization
    void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
        var device = SteamVR_Controller.Input((int)trackedObject.index);
        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger))
        {
            Debug.Log("トリガーを深く引いた");
        }
    }
```

ゲーム再生直後は、何度か `IndexOutOfRangeException: Array index is out of range.` が発生するが、これはDevice(コントローラー)の登録・認識がされる前にDeviceにアクセスしようとしているからだと思う。

何はともあれこれでトリガーを引いた時のEventがとれるようになった。

###3-4 Cubeを発生させる。
トリガーのEventはとれるようになったので、あとは座標を知るだけだ。
`device.transform.pos` で知りたいデバイスのワールド座標を取得できる

最終的なコードはこのようになった。

```
	public GameObject cube;
	public SteamVR_TrackedObject trackedObject;
	// Use this for initialization
	void Start ()
	{
	
	}
	
	// Update is called once per frame
	void Update ()
	{
		var device = SteamVR_Controller.Input ((int)trackedObject.index);
		if (device.GetPressDown (SteamVR_Controller.ButtonMask.Trigger)) {
			Debug.Log ("トリガーを深く引いた");
			var newCube = Instantiate (cube, device.transform.pos, Quaternion.identity);
		}
	}
```

コントローラーの位置にCubeが発生するだけだけど、３Dの世界でこれをやるだけでも楽しい。
今後のVRコンテンツ開発が楽しみだ。　：）