UnityでOVRHeadsetEmulatorに前進移動を追加する

# はじめに

[UnityでOculus向けの環境で壊れたOVRHeadsetEmulatorを直す](https://qiita.com/shiena/items/ed96bb1ec14d45ae38c4)で`OVRHeadsetEmulator`が動くようになり回転と上下移動ができるようになりました。Oculus Questで6DoFな移動をしたいので前進移動を追加してみました。


# OVRHeadsetEmulatorHelper

以下のスクリプトを保存して`OVRHeadsetEmulator`をアタッチしたコンポーネントに追加するだけです。
https://gist.github.com/shiena/8e1908194144792a2495177389ea053e にも同じソースを用意しています。

`OVRHeadsetEmulator`のActivate Keysを押したままマウスの右ボタンを押しっぱなしにすると前進に移動します。
前進は`w`、後退は`s`も検討したのですがCtrlと組み合わせると保存のショートカットとかぶってしまうので苦肉の策でマウスの右ボタンにしています。
他のキーに変更したい場合は`MoveForward`メソッドを書き換えてください。


```cs:OVRHeadsetEmulatorHelper.cs
/************************************************************************************
Copyright (c) 2019 KOGA Mitsuhiro

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
************************************************************************************/

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(OVRHeadsetEmulator))]
public class OVRHeadsetEmulatorHelper : MonoBehaviour
{
    [SerializeField] private OVRHeadsetEmulator emulator;
    [SerializeField] private Transform head;
    [SerializeField] private float forwardScale = 1f;

    private OVRManager _manager;
    private bool _emulatorHasInitialized = false;

    private void Reset()
    {
        emulator = GetComponent<OVRHeadsetEmulator>();
        head = FindMainCamera().transform;
    }

    private bool MoveForward()
    {
        return Input.GetMouseButton(1);
    }

    private void Update()
    {
        if (!_emulatorHasInitialized)
        {
            if (OVRManager.OVRManagerinitialized)
            {
                _manager = OVRManager.instance;
                _emulatorHasInitialized = true;
            }
            else
            {
                return;
            }
        }

        bool emulationActivated = IsEmulationActivated();
        if (emulationActivated)
        {
            if (emulator.resetHmdPoseByMiddleMouseButton && Input.GetMouseButton(2))
            {
                return;
            }

            Vector3 emulatedTranslation = _manager.headPoseRelativeOffsetTranslation;
            Vector3 forward = head.forward.normalized * (Time.deltaTime * forwardScale);
            forward.y = 0;

            if (MoveForward())
            {
                // move forward
                emulatedTranslation -= forward;
                _manager.headPoseRelativeOffsetTranslation = emulatedTranslation;
            }
        }
    }

    private bool IsEmulationActivated()
    {
        if (emulator.opMode == OVRHeadsetEmulator.OpMode.Off)
        {
            return false;
        }

        if (emulator.opMode == OVRHeadsetEmulator.OpMode.EditorOnly && !Application.isEditor)
        {
            return false;
        }

        foreach (KeyCode key in emulator.activateKeys)
        {
            if (Input.GetKey(key))
                return true;
        }

        return false;
        }

    private Camera FindMainCamera()
    {
        GameObject[] objects = GameObject.FindGameObjectsWithTag("MainCamera");
        List<Camera> cameras = new List<Camera>(4);
        foreach (GameObject obj in objects)
        {
            Camera camera = obj.GetComponent<Camera>();
            if (camera != null && camera.enabled)
            {
                OVRCameraRig cameraRig = camera.GetComponentInParent<OVRCameraRig>();
                if (cameraRig != null && cameraRig.trackingSpace != null)
                {
                    cameras.Add(camera);
                }
            }
        }

        if (cameras.Count == 0)
        {
            return Camera.main; // pick one of the cameras which tagged as "MainCamera"
        }
        else if (cameras.Count == 1)
        {
            return cameras[0];
        }
        else
        {
            // return the camera with least depth
            cameras.Sort((Camera c0, Camera c1) =>
            {
                return c0.depth < c1.depth ? -1 : (c0.depth > c1.depth ? 1 : 0);
            });
            return cameras[0];
        }
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8995/4d0d9ef9-dbc3-980f-b520-bb17057e1673.png)

# OVRPlayerController.prefabも悪くない

`Assets/Oculus/VR/Prefabs/OVRPlayerController.prefab`は`Character Controller`と`OVRPlayerController`が組込まれているのでWASD、十字キー、Oculus Touchコントローラーのアナログスティックで前後移動できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8995/78e29275-9322-b432-c88b-28512dfb8136.png)