UnityでVRに物理的ボタンを作る（VR Beginner: The Escape Roomのコードを読んでみる）

# この記事について
タイトルだけを読むと「何言ってるんだ？」と思われるかもしれませんが、Unity上で「ボタン」というとUIボタンの記事が出てきてしまうので「物理的なボタン」と書かせていただきました。

この記事で実装したいのは以下のようなものです。
![スクリーンショット 2022-01-30 23.43.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296240/1cedf067-5ace-f515-c848-51fec8a2f26c.png)

３Dオブジェクトがボタンとして存在して、それをコントローラーで一定上押すと入力処理が走るというのが今回作成するものです。
これが作りたかったんや！

[Twitterにあげた動画](https://twitter.com/yokotate_it/status/1486683261095264258?s=20&t=TeINYder3TXWPynCDVqb2Q)

# 参考プロジェクト
この記事で使用しているソースコードはUnityのチュートリアルプロジェクトの「VR Beginner: The Escape Room」のものを利用しております。
[VR Beginner: The Escape Room](https://assetstore.unity.com/packages/templates/tutorials/vr-beginner-the-escape-room-163264)

# 環境
Mac
unity 2020.3.25

# 実装！
オブジェクトの作成については省略します。
キューブを作って配置しただけです。

今回の中で最小の単位は３Dオブジェクトが一つだけで大丈夫です。
![スクリーンショット 2022-01-30 23.51.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296240/8e739a36-3b9b-22ac-9698-9bec82c8b5b6.png)

失礼、あとはプレイヤーも必要ですが、今回の実装ではスルーします。
がんばれ。

そしてこのオブジェクトに対してスクリプトをくっつけます。
いきなり完成形を貼り付けます。中に書いてあるコメントがそのまま説明になるかと思います。

```C#:ButtonManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;


public class ButtonManager : MonoBehaviour
{
    [System.Serializable]
    public class ButtonPressedEvent : UnityEvent { }
    public ButtonPressedEvent OnButtonPressed;

    public Vector3 Axis = new Vector3(0,-1,0 );
    public float MaxDistance;
    public float ReturnSpeed = 10.0f;
    
    Vector3 m_StartPosition;
    Rigidbody m_Rigidbody;
    Collider m_Collider;

    bool m_Pressed = false;

    void Start()
    {
        m_Rigidbody = GetComponent<Rigidbody>();
        m_Collider = GetComponentInChildren<Collider>();
        m_StartPosition = transform.position;
    }

    void FixedUpdate()
    {
        // 押し込む方向の指定（自身の方向をワールド空間内での方向へ変換する）
        Vector3 worldAxis = transform.TransformDirection(Axis);
        // MAxDistanceを利用して最大移動地点を指定する
        Vector3 end = transform.position + worldAxis * MaxDistance;
        
        // スタート時点の位置からの現在までの移動量を測定（平方根）
        float m_CurrentDistance = (transform.position - m_StartPosition).magnitude;
        RaycastHit info;

        float move = 0.0f;

        // 戻る方向に物体がなければ元の位置に戻る処理。あったら逆方向（押し込まれる方向）へ移動する。
        if (m_Rigidbody.SweepTest(-worldAxis, out info, ReturnSpeed * Time.deltaTime + 0.005f))
        {//hitting something, if the contact is < mean we are pressed, move downward
            move = (ReturnSpeed * Time.deltaTime) - info.distance;
        }
        else
        {
            move -= ReturnSpeed * Time.deltaTime;
        }

        // 初期地点から最大移動距離の間に収まる値に位置が変換される
        float newDistance = Mathf.Clamp(m_CurrentDistance + move, 0, MaxDistance);

        // 新しい位置を設定する
        m_Rigidbody.position = m_StartPosition + worldAxis * newDistance;

        // ボタンが押されていない状態 & 新しく移動する予定の場所までの距離と最大移動距離が近侍であるならIF内処理
        // ボタンが押されている状態 & しく移動する予定の場所までの距離と最大移動距離が近侍でないならELSEIF内処理
        if (!m_Pressed && Mathf.Approximately(newDistance, MaxDistance))
        {//was just pressed
            m_Pressed = true;
            OnButtonPressed.Invoke();
        }
        else if (m_Pressed && !Mathf.Approximately(newDistance, MaxDistance))
        {//was just released
            m_Pressed = false;
        }
    }
}
```

あとはオブジェクトにRigidbodyをアタッチして、is Kinematicにチェックを入れて重力を使用するのチェックを外します。
![スクリーンショット 2022-01-30 23.57.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296240/085f483e-ae16-f479-b57d-b44c352e0a5f.png)

アタッチしたスクリプトの変数に値を設定するのですが、設定値は以下の通りです。

| 項目名 | 内容 |
|:-:|:-:|
|  On Button Pressed | ボタンが押された時の処理  |
|  軸 | ボタンが押される方向  |
|  最大距離 | どれだけ押されたらイベントが発生するか  |
|  戻るスピード | ボタンが抑えれる際にオブジェクトが認識される距離  |

![スクリーンショット 2022-01-30 23.57.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296240/41bf5991-0975-8c46-4a39-4434fb546538.png)

これだけです！
あとはご自由にここに発動したい処理を入れて実行してみましょう！

終了！

# おすすめ
[VR Beginner: The Escape Room](https://assetstore.unity.com/packages/templates/tutorials/vr-beginner-the-escape-room-163264)
* おそらくやりがいことが全て載ってる神プロジェクト
* VRゲームをUnityで作りたいならこのプロジェクトを読めば大体載ってそう
* UI関連は載っていないので注意
* XR Interaction Toolkit対応
* Device BaseなのでActionBaseでゲームを作りたい人は注意

[XR Interaction Toolkit Examples](https://github.com/Unity-Technologies/XR-Interaction-Toolkit-Examples)
* XR Interaction Toolkitのサンプルプロジェクト
* こちらはUI関連のサポートもあるし各オブジェクトの動作やテレポートなんかも載っているがちょっと難しく感じる
* 上が初心者向けならこっちは慣れてきた時に触ると良いかな？
* こちらはDeviceBaseとActionBaseが両方とも触れる