UnityのSteamVR plugin（2.6.1）を使ってVRChatぽい移動を再現（Valve Index）

　Unity上でネイティブVR空間を作ってみようと思いました。ちなみに空間を作り上げてVR体験するだけだったら、[VRChat](https://www.vrchat.com/)か[cluster](https://cluster.mu/)でワールドを作った方が手軽だし共有もしやすいのでおすすめです。

- Unity Editor ... 2019.2.21f1
- SteamVR plugin ... 2.6.1
- 使用デバイス ... Valve Index

# まずはプロジェクト作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/0337df38-6493-37af-2d7d-c12e7ebcd33e.png)

# SteamVR pluginのimport

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/5b30d110-8b84-3711-f1af-d822688b2445.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/7047c6c8-6003-6a9b-d9f5-db2f8c72a09a.png)

　よくわからないけど脳死で「Accept All」を押します。（いいのか？）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/b912bb47-2230-2040-68ba-4911972077a3.png)

　以降、ずっと出てくるこのウィンドウは無視します。特に弊害がないので。（いいのか？）

# カメラプリファブの設置

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/b166c135-6956-d7a3-632b-9c6cbae33513.png)

　`Assets > SteamVR > Prefabs`にある`[Camera Rig]`のプリファブをヒエラルキーに置きます。これがプレイヤーの頭となります。元からあった`Main Camera`は削除します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/d327a0dd-9e02-3cf6-5636-7079be7ce53e.png)

　これは本質的には必要ない作業ですが、地平線だけだと味気ないので、PlaneとSphereを設置しています。

　再生ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/bf19602b-4ceb-b8b7-db18-b54968d712c3.png)

　何も考えずに`Yes`を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/c3c98d9e-e48b-33bb-e112-3c244d8527d0.png)

　何も考えずに`Save and Generate`を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/04ae9e7b-7e61-93d9-2db0-f4b01ef1c499.png)

　`Save`。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/b5e8b5c8-4fa0-c451-a0cb-f903806d979a.png)

　もう一度再生ボタンを押せば、これだけでもうVR空間の中を歩き回れます。しかし、コントローラで歩き回れるようにもしたいです。

# コントローラの設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/9f6a174d-fc96-b5fd-a6d1-dc2b5ff19c1e.png)

　ここで`SteamVR Input`。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/48946c92-0dde-6c50-b1cb-40dba951a7fc.png)

　これらの文字の羅列の意味は後で分かってきますので、とりあえず`Open building UI`。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/097b90bf-0053-3e0d-acb9-c5cf6b3c3da5.png)

　編集。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/b0ff52e8-61b6-081e-cd97-9ca26f75a265.png)

　ここで、先程の文字の羅列が、コントローラの入力と対応したものであったことがわかります。VRChatライク（非Holoport）な移動をするためには、十字スティックの入力を2次元座標として受け取る必要がありますが、デフォルトの配列ではそうはなっていないようなので、追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/223ad64e-b3cf-68b5-a079-4bde13382acb.png)

 `Thumb Stick`の+ボタンを押して「ジョイスティック」を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/bfac8158-3e90-b29e-ed11-eab1af2e1fd3.png)

　ここで「位置」にアクションを割り当てる……と行きたいところですが、何も選択できない状態になっているはずです。位置のデータ形式は`Vector2`ですが、`Vector2`を入力として受け取るアクションが用意されていないためです。という訳で、先程の`SteamVR Input`に戻ってコマンドを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/f5c3f782-3472-571e-1d00-841d4c154384.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/0f358f39-72c4-c804-6723-df177b608736.png)

　+ボタンでアクションを追加し、適当な名前にします（ここでは仮に`Move`としています）。また、`Type`を`vector2`にします（ここ重要）。変更が終わったら`Save and generate`を忘れずに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/a8576c46-7764-1c9a-fbb7-af4e5b92e82f.png)

　今度は選択できるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/1b6cb275-5bbb-cfd6-8d6e-2d56603c55ef.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/f455ed56-571b-d38a-f095-552e3c532242.png)

　割り当てた後は、「デフォルトバインドの置換」から「保存」を忘れずに。

# スクリプトの作成

　下のようなスクリプトを用意します。ここでは仮に`Move.cs`としておきます。

```c#
using UnityEngine;
using Valve.VR;

public class Move : MonoBehaviour
{
    private SteamVR_Action_Vector2 move = SteamVR_Actions._default.Move;
    float MoveSpeed = 0.1f;

    private void Start()
    {
    }

    private void Update()
    {
        Vector2 pos = move.GetLastAxis(SteamVR_Input_Sources.LeftHand);
        transform.position += transform.forward * pos.y * MoveSpeed;
        transform.position += transform.right * pos.x * MoveSpeed / 5;
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/dac775ff-54d7-ca83-821f-789d3585e81f.png)

　`[CameraRig]`にアタッチします。

　これで左コントローラを動かせばその通りにカメラ（＝プレイヤー）が動くようになったと思われますが、少し不自然だと思います。なぜかというと、頭の向きと進行方向が一致していないからです。`[CameraRig]`はあくまで体の向きであり、実際の頭の向きはその下にある`Camera`というオブジェクトだからです。ちゃんと見ている方向を基準にするべく、以下のようなコードにします。

```c#
using UnityEngine;
using Valve.VR;

public class Move : MonoBehaviour
{
    private SteamVR_Action_Vector2 move = SteamVR_Actions._default.Move;
    float MoveSpeed = 0.1f;
    private GameObject Head;

    private void Start()
    {
        Head = GameObject.Find("Camera");
    }

    private void Update()
    {
        Vector2 pos = move.GetLastAxis(SteamVR_Input_Sources.LeftHand);
        this.transform.position += Head.transform.forward * pos.y * MoveSpeed;
        this.transform.position += Head.transform.right * pos.x * MoveSpeed / 5;
    }
}
```

　しかし、これでもまだ片手落ちで、これだと頭が少しでも見上げていたり見下ろしていたりすると空中方向の移動が発生してしまいます。コライダーとか重力の処理の話はおいといて、とりあえずこのサンプルをそれっぽく動かすために、`y`座標は常に`0`にするように追加処理を加えます。

```c#
using UnityEngine;
using Valve.VR;

public class Move : MonoBehaviour
{
    private SteamVR_Action_Vector2 move = SteamVR_Actions._default.Move;
    float MoveSpeed = 0.1f;
    private GameObject Head;

    private void Start()
    {
        Head = GameObject.Find("Camera");
    }

    private void Update()
    {
        Vector2 pos = move.GetLastAxis(SteamVR_Input_Sources.LeftHand);
        transform.position += Head.transform.forward * pos.y * MoveSpeed;
        transform.position += Head.transform.right * pos.x * MoveSpeed / 5;
        Vector3 loc = transform.position;
        loc.y = 0;
        transform.position = loc;
    }
}
```

# 回転方向の刻み

　ジョイスティックだけだと方向転換ができないので、VRChatライクに右コントローラのジョイスティックに回転を割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/10bc29ec-f534-9b36-5efc-47df01939ce8.png)

　ジョイスティックの左入力と右入力についてはデフォルトで設定があるので、それをそのまま使います。以下のようなコードにします。

```c#
using UnityEngine;
using Valve.VR;

public class Move : MonoBehaviour
{
    private SteamVR_Action_Vector2 move = SteamVR_Actions._default.Move;
    private SteamVR_Action_Boolean left = SteamVR_Actions._default.SnapTurnLeft;
    private SteamVR_Action_Boolean right = SteamVR_Actions._default.SnapTurnRight;
    float MoveSpeed = 0.1f;
    private GameObject Head;

    private void Start()
    {
        Head = GameObject.Find("Camera");
    }

    private void Update()
    {
        Vector2 pos = move.GetLastAxis(SteamVR_Input_Sources.LeftHand);
        transform.position += Head.transform.forward * pos.y * MoveSpeed;
        transform.position += Head.transform.right * pos.x * MoveSpeed / 5;
        Vector3 loc = transform.position;
        loc.y = 0;
        transform.position = loc;

        if (left.GetStateDown(SteamVR_Input_Sources.RightHand))
        {
            transform.Rotate(new Vector3(0, -20, 0));
        }

        if (right.GetStateDown(SteamVR_Input_Sources.RightHand))
        {
            transform.Rotate(new Vector3(0, 20, 0));
        }
    }
}
```

　これでだいぶ直感的に動けるようになりました。

# なぜこの記事を書いたか

- 同じことを解説しているページはあったが、ジョイスティックでの移動をシンプルに実装するという点で優位性があると思った
- バージョンが細かく分かれており、2.6.1で書いている記事はなかった
- Indexで書いている記事もなかった

# お世話になった記事

https://framesynthesis.jp/tech/unity/htcvive/
https://qiita.com/sakano/items/d87a9b11c23a9bbe166f