SteamVR Unity Plugin v2.2.0でのインプット

SteamVR2.0で大幅に変わり2.2でまたいくらか変わったInputのメモです。

以下について書いてあります。必要な部分だけ見てください。

- 最初にプラグインを入れて[サンプルシーンを動かすまでの手順](#サンプルシーンを動かす手順)
- 次に[ハンドコントローラの入力を扱う方法](#ハンドコントローラの入力を扱う方法)
- 最後に[Viveトラッカーを使う方法](#viveトラッカーを使う方法)

テスト環境は以下になります。

- Unity 2018.3.9f1
- SteamVR Unity Plugin v2.2.0
- HTC Vive / HTC VIVE Tracker 2018
- SteamVR 1.2.10

# サンプルシーンを動かす手順
1. SteamVR Unity Plugin v2.2.0を[ダウンロード](https://github.com/ValveSoftware/steamvr_unity_plugin/releases)します。
2. Unityで新規プロジェクトを作成してダウンロードしたunitypackageをインポートします。
3. 以下のようなウィンドウが出てきます。  
![Unity 2018.3.9f1 Personal - Interactions_Example.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 7_51_39.png](https://qiita-image-store.s3.amazonaws.com/0/31952/6b26dbf9-13ff-a0e5-588d-40cab0d893ef.png)
4. ColorSpaceをLinearにしろ、といったことが書いてあるのでAccept Allボタンを押します。
5. SteamVR\InteractionSystem\SamplesにあるInteractions_Exampleシーンを開きます。
6. Playボタンを押してシーンを再生します。
7. 以下のようなダイアログが出てきます。Yesボタンを押します。  
![Unity 2018.3.9f1 Personal - Interactions_Example.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 7_46_57.png](https://qiita-image-store.s3.amazonaws.com/0/31952/33cc5c42-6dcc-6231-95b9-36d36041f90e.png)
8. 以下のようなダイアログが出てきます。Yesボタンを押します。  
![Unity 2018.3.9f1 Personal - Interactions_Example.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 7_49_03.png](https://qiita-image-store.s3.amazonaws.com/0/31952/5248594b-63fd-879d-4f82-2caf468e1a2b.png)  
プロジェクトのルートディレクトリにactions.jsonやbinding_vive.jsonといったファイルが生成されます。
9. SteamVR Inputウィンドウが表示されます。このウィンドウはUnityのWindow>SteamVR Inputメニュからも開けます。  
10. Save and generateボタンを押します。
11. SteamVR_Inputフォルダとその中身が生成されます。  
これらのファイルのパスはSteamVR\ResourcesフォルダのSteamVR_Settingsで変更できます。
12. 再びPlayボタンを押してシーンを再生します。

SteamVR\Extrasにももう少しシンプルなサンプルシーンがあります。

# ハンドコントローラの入力を扱う方法
## ポジショントラッキングするだけのシーンを作っておく
1. 新しいシーンを作成します。
2. 初期配置されているMain Cameraを削除します。
3. SteamVR\Prefabsにある[CameraRig]をシーンにドラッグ＆ドロップで配置します。
4. Playボタンを押してシーンを再生すると動きます。  
HMDを被るとちゃんと向いている方向を見られます。
コントローラの位置もトラッキングされています。

## コントローラの入力を検出してみる
1. 新しいC#スクリプトを作成してVRTestという名前にします。
2. [CameraRig]にVRTestをドラッグ＆ドロップしてスクリプトを追加します。
3. VRTestの中身を以下に編集します。

```C#:VRTest.cs
using UnityEngine;
using Valve.VR;

public class VRTest : MonoBehaviour
{
    private SteamVR_Action_Boolean actionToHaptic = SteamVR_Actions._default.InteractUI;
    private SteamVR_Action_Vibration haptic = SteamVR_Actions._default.Haptic;

    private void Update()
    {
        if (actionToHaptic.GetStateDown(SteamVR_Input_Sources.LeftHand)) {
            haptic.Execute(0, 0.005f, 0.005f, 1, SteamVR_Input_Sources.LeftHand);
        }
    }
}
```
左手のコントローラのトリガーを引くとコントローラが振動するようになります。

## Actionについて
### Actionとは
SteamVR Unity Plugin v2.0からアプリケーションごとにActionを定義しなければならなくなりました。これはactions.jsonに定義されます。

実作業上ではactions.jsonを直接編集せずともSteamVR Inputウィンドウで設定できます。
サンプルのactions.jsonでは以下のような状態になっています。InteractUIやTeleportなどが定義されたActionです。

![Unity 2018.3.9f1 Personal - Interactions_Example.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 8_28_35.png](https://qiita-image-store.s3.amazonaws.com/0/31952/efee2f36-0d1c-52f1-e5a5-a0a84e4f44ae.png)

### Actionを追加してみる
自分でActionを追加してみます。

1. UnityのメニューのWindow>SteamVR InputでSteamVR Inputウィンドウを開きます。  
2. Inの右下にある+ボタンを押します。
3. 以下のようにNewActionが追加されます。  
![Unity 2018.3.9f1 Personal - Interactions_Example.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 8_46_01.png](https://qiita-image-store.s3.amazonaws.com/0/31952/5cd563b6-2625-649e-0a66-fde23854b456.png)
4. Name欄のNewActionになっている部分をMenuに変更します。他はそのままにしておきます。
5. Save and Generateボタンを押します。

以上でMenuというActionが定義されました。

Menuが入力されたらコントローラが振動するように、先に作ったVRTest.csの中身を以下に変えてみます。

```C#:VRTest.cs
using UnityEngine;
using Valve.VR;

public class VRTest : MonoBehaviour
{
    private SteamVR_Action_Boolean actionToHaptic = SteamVR_Actions._default.Menu;
    private SteamVR_Action_Vibration haptic = SteamVR_Actions._default.Haptic;

    private void Update()
    {
        if (actionToHaptic.GetStateDown(SteamVR_Input_Sources.LeftHand)) {
            haptic.Execute(0, 0.1f, 60, 1, SteamVR_Input_Sources.LeftHand);
        }
    }
}
```

変わったのは以下の1行だけです。
変更前: `private SteamVR_Action_Boolean actionToHaptic = SteamVR_Actions._default.InteractUI;`
変更後: `private SteamVR_Action_Boolean actionToHaptic = SteamVR_Actions._default.Menu;`

しかしこれだけでは動作しません。
定義したActionと実際のコントローラのボタンを紐付ける必要があります。これはHTC ViveやOculus Riftなど対応する機器ごとに設定する必要があります。この設定はbindings_vive_controller.jsonやbindings_oculus_touch.jsonなどに保存されます。
Actionとコントローラの紐付けはユーザー側でも設定できます。なので開発者が設定していない機器でもプレイヤーが設定して動かせます。

### Actionにコントローラを紐付ける
1. 再びSteamVR Inputウィンドウを開いてOpen binding UIボタンを押します。
2. ブラウザで以下のような画面が開きます。  
![SteamVR - Google Chrome 2019_3_21 9_00_01.png](https://qiita-image-store.s3.amazonaws.com/0/31952/d4b3a71c-f89b-8325-fe73-25efba2adfe2.png)
3. Editボタンを押します。  
![SteamVR - Google Chrome 2019_3_21 9_09_50.png](https://qiita-image-store.s3.amazonaws.com/0/31952/049852e7-ba52-17bc-94e5-cd066bb9fec5.png)
4. Menuの右にある+ボタンを押します。  
![SteamVR - Google Chrome 2019_3_21 9_11_56.png](https://qiita-image-store.s3.amazonaws.com/0/31952/08e4066f-7657-05be-b9ef-3c2fdfeed565.png)
5. Buttonを選択します。  
![SteamVR - Google Chrome 2019_3_21 9_12_00.png](https://qiita-image-store.s3.amazonaws.com/0/31952/452627a1-5118-c964-3ff0-ccd48f0979a9.png)
6. 追加されたClickの右のNoneを押します。  
![SteamVR - Google Chrome 2019_3_21 9_13_19.png](https://qiita-image-store.s3.amazonaws.com/0/31952/8f8cb3f0-091a-3751-abd3-03b50f8505c0.png)
7. menuを選択します。  
![SteamVR - Google Chrome 2019_3_21 9_13_48.png](https://qiita-image-store.s3.amazonaws.com/0/31952/5ae76398-44f0-6f42-a5a5-9389f12d4010.png)
8. 右下のReplace Default Bindingを押します。
9. 出てきたウィンドウでSaveを押します。

以上でMenuボタンとMenu Actionが紐付けられました。

シーンを再生してMenuボタンを押すとコントローラが振動します。

### Actionの定義をもう少し詳しく
前述のとおりSteamVR InputウィンドウでActionを定義できます。

![Unity 2018.3.9f1 Personal - New Scene.unity - VRTest - PC, Mac & Linux Standalone _DX11_ 2019_3_21 9_28_17.png](https://qiita-image-store.s3.amazonaws.com/0/31952/1e46affa-11c0-c372-f738-9966d84bdfd8.png)

- Nameはプログラム中で使われる名前です。
- Typeは入力の種類です。以下のような物があります。
    - boolean : ボタンのオンオフなどbool1つで表せる入力を検出できます。  
      プログラム中ではSteamVR_Action_Booleanで表されます。
    - vector1 : トリガーがどれくらい引かれているかなどfloat1つで表せる入力を検出できます。  
      プログラム中ではSteamVR_Action_Singleで表されます。
    - vector2 : ジョイスティックなどfloat2つで表せる入力を検出できます。
      プログラム中ではSteamVR_Action_Vector2で表されます。
    - vector3 : float3つで表せる入力を検出できます。  
      プログラム中ではSteamVR_Action_Vector3で表されます。
    - pose : ヘッドセットやトラッカーの位置・回転・速度・角速度を検出できます。
      プログラム中ではSteamVR_Action_Poseで表されます。
    - skeleton: 手の位置や回転、指の曲がり具合などを検出できます。  
      プログラム中ではSteamVR_Action_Skeletonで表されます。
- RequiredはそのActionが必須かどうかです。
    - optionalの場合はあってもなくてもいいActionです。
    - suggestedは推奨のActionです。コントローラと紐付けるときに設定されていないと警告が出るようになります。
    - mandatoryは必須のActionです。 コントローラと紐付けるときに設定されていないとその設定は保存できません。
- LocalizationとLocalized Stringは紐付けるときの設定画面で表示される名前です。  
  現状のUIが多言語対応していないようなのでjaなどで日本語を設定しても使われません。

## いろいろな入力の取り方
Actionから入力を取り出す方法は色々あります。

以下の2行は同じ意味です。
`actionToHaptic.GetStateDown(SteamVR_Input_Sources.LeftHand)`
`actionToHaptic[SteamVR_Input_Sources.LeftHand].stateDown`

以下の2行も同じ意味です。SteamVR_Input_Sources.Anyは左右どちらの手でもいいときに使います。
`actionToHaptic.GetStateDown(SteamVR_Input_Sources.Any)`
`actionToHaptic.stateDown`

以下のようにeventでも取れます。

```c#

actionToHaptic.onStateDown += (SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) => {
    /* 入力されたときの処理 */
};
```

stateDownの代わりにstateUpでボタンが離されたか、lastStateDownで1フレーム前で押されたか、lastStateUpで1フレーム前に離されたか、などが取れます。

## ActionSetについて
ActionSetはいくつかのActionをひとまとめにしたものです。
プログラムからActionSetごとに有効・無効を切り替えられます。
ゲームの状態に応じて入力方法を切り替えられます。

以下でdefaultのActionSetだけを有効にします。最初はこの状態です。

```c#

SteamVR_Actions._default.Activate(activateForSource: SteamVR_Input_Sources.Any, priority: 0, disableAllOtherActionSets: true);
```

以下は左手でplatformer、右手でbuggyのActionSetを有効にする例です。

```c#

SteamVR_Actions._default.Deactivate(SteamVR_Input_Sources.Any);
SteamVR_Actions.platformer.Activate(SteamVR_Input_Sources.LeftHand, priority: 0, disableAllOtherActionSets: false);
SteamVR_Actions.buggy.Activate(SteamVR_Input_Sources.RightHand, priority: 0, disableAllOtherActionSets: false);
```

priorityで優先度を付けて複数のActionSetを同時に使うこともできます。


# Viveトラッカーを使う方法

## トラッカーをボディトラッキングに使う場合
トラッカーそれぞれを身体の部分として使うように設定できるようになっています。

まずはトラッカーをそれぞれ腰や足などに紐付けます。
これはアプリを使うユーザーごとにそれぞれやってもらう必要があります。

1. SteamVRのパネルでトラッカーのアイコンを右クリックしてManageViveTrackersをクリックします。  
2. 以下のようなウィンドウが出てきます。  
![Manage Vive Trackers 2019_3_21 10_39_03.png](https://qiita-image-store.s3.amazonaws.com/0/31952/c13e7ffa-7daf-e08b-18a3-fa39cf2813ae.png)
3. Select Roleでそれぞれトラッキングしたい位置を設定します。ここではRight Foot, Left Foot, Waistを設定しています。

次にActionとトラッカーを紐付けます。

1. SteamVR InputウィンドウでSteamVR Settingsボタンを押します。
2. Current ControllerのVive Controllerの部分を押してVive Tracker on Left Footに切り替えます。
3. Create Bindingボタンを押します。
4. Add Action Poseボタンを押します。
5. Unusedを押してPoseを選択します。
6. Replace Default Bindingを押してSaveします。

同じようにRight FootとWaistのトラッカーもそれぞれPoseと紐付けます。

Unityに戻ってトラッカーの位置と連動するCubeを作ってみます。

1. 新しくCubeを作成します。
2. Steam VR_Behaviour_Pose というコンポーネントを追加します。
3. Pose Actionを\actions\default\Poseに設定します
4. Input SourceをWaistに設定します。

以上でこのCubeはWaistのトラッカーの位置と連動するようになります。
InputSourceの部分を変えれば右足や左足とも連動させられます。

## トラッカーをインデックスで扱う場合
こちらの場合はトラッカーやActionの設定は必要ありません。
Unity上で設定するだけで動作します。

1. Unityで新しくCubeを作成します。
2. UnityでSteam VR_Tracked Objectというコンポーネントを追加します。
3. トラッカーを動かしながらIndexを適当に変えて正しい物を設定します。

以上でCubeとトラッカーの位置が連動するようになります。
Indexのなかには手のコントローラなども入っているので注意してください。

## プログラムからトラッカーの位置を扱う場合
ボディトラッキングの場合は通常のActionを扱うのと同じように使えます。

インデックスで扱う場合はSteam VR_Tracked Objectのソースを見てください。
SteamVR_Eventsから位置と角度を引っ張ってこれるようです。

### インデックスからシリアルナンバーを取得する
インデックスは変動することがあるらしいのでデバイスごとのシリアルナンバーを取得して判別します。

以下のGetDeviceSerialNumberメソッドのような形でシリアルナンバーを取得できます。

```c#
using Valve.VR;

public static class OpenVRUtils
{
    private static CVRSystem openVRSystem => _openVRSystem ?? (_openVRSystem = OpenVR.System);
    private static CVRSystem _openVRSystem;

    public static string GetDeviceSerialNumber(uint deviceIndex)
    {
        var error = default(ETrackedPropertyError);
        int capacity = 512;
        var buffer = new System.Text.StringBuilder(capacity);
        openVRSystem.GetStringTrackedDeviceProperty(deviceIndex, ETrackedDeviceProperty.Prop_SerialNumber_String, buffer, (uint)capacity, ref error);
        if (error != ETrackedPropertyError.TrackedProp_Success) {
            return null;
        }
        return buffer.ToString();
    }
}

```