Locomotion Movement in Oculus Rift


In this tutorial, I am going to be talking about VR locomotion movement with Oculus Rift.

Some resources to check out before start designing movement in VR:
https://unity3d.com/learn/tutorials/topics/virtual-reality/movement-vr

Let’s start, from Oculus touch controllers we will use the thumbstick to move around freely in the game. The forward movement is going to be linked to the players head movement. So the player will move where they are looking. We will have forward, backward and side to side movement.

I will show you two different ways to move, one is to make the character walk freely and the other will make the player fly.

So let's start with creating a new project. I am using Unity version 2017.3.1f1.

Next, let’s download Oculus Utilities and Oculus Avatar from https://developer.oculus.com/downloads/unity/.

After opening your project In Unity;
- Import both of the Unity packages into your project.
- In the project, window locate OVR > Prefabs > OVRCameraRig and place this into the hierarchy panel.MAke sure the position is 0,0,0.
- Next, locate OvrAvatar > Prefabs > LocalAvatar and place this into the hierarchy panel. Make sure the position is also 0,0,0.
- Now create a plane for the player to move around.

You should have something like this in your Unity Window:

Next, let's write the C# code for the movement.
- Right click to Project Panel > Create > C# script name it InputManager.
- First create references to the LocalAvatar, OVRCameraRig and to the CenterEye object under OVRCameraRig to get the forward vector of the player.
- I am going to feed these to the script in the Start() function using tags.
- I tagged the OVRCameraRig as Player, LocalAvatar as localAvatar, the CenterEye object is already has a tag as a MainCamera which we will use that.

						
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class InputSystem : MonoBehaviour
{

	//Keyboard inputs
	private GameObject player;
	private GameObject centerEye;
	private GameObject localAvatar;

	// Use this for initialization
	void Start()
	{
		player = GameObject.FindGameObjectWithTag("Player");
		centerEye = GameObject.FindGameObjectWithTag("MainCamera");
		localAvatar = GameObject.FindGameObjectWithTag("localAvatar");

	}
}

						
						

Next, I’m going to create the inputs for the touch controllers. We are going to use the thumbstick so I will look at the proper name from the following diagram.

Title of Image

						
void Locomotion()
{
	Vector3 forward = centerEye.transform.forward;
	Vector3 right = centerEye.transform.right;

	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp))
	{
		Debug.Log(“Go forward”);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickDown))
	{
		Debug.Log(“Go backward”);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickRight))
	{
		Debug.Log(“Go right”);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickLeft))
	{
		   Debug.Log(“Go left”);
	}
}

						
						
						
					

- If the oculus is connected to your computer you can test your progress by clicking the play button and if you move the thumbstick you see the Debug.Log on the console.

Next, let’s write the movement code.

- We are going to use the translate method and move both LocalAvatar and OVRCameraRig the same amount.

						
void Locomotion()
{
	Vector3 forward = centerEye.transform.forward;
	Vector3 right = centerEye.transform.right;

	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp))
	{
		player.transform.Translate(forward * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(forward * Time.deltaTime * m_Speed);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickDown))
	{
		player.transform.Translate(-forward * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(-forward * Time.deltaTime * m_Speed);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickRight))
	{
		player.transform.Translate(right * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(right * Time.deltaTime * m_Speed);
	}
	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickLeft))
	{
		player.transform.Translate(-right * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(-right * Time.deltaTime * m_Speed);
	}
}


						
					

If you try this code, it will translate the player on the Y axis too. You can use this version of the code for a fly movement.
Let's limit the movement to only the X and Z axis

						
 void Locomation()
{
	Vector3 forward = centerEye.transform.forward;
	Vector3 tempF = new Vector3(forward.x, 0f, forward.z);

	Vector3 right = centerEye.transform.right;
	Vector3 tempR = new Vector3(right.x, 0f, right.z);


	if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp))
	{
		moving = true;

		player.transform.Translate(tempF * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(tempF * Time.deltaTime * m_Speed);

	}
	else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickDown))
	{

		moving = true;

		player.transform.Translate(-tempF * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(-tempF * Time.deltaTime * m_Speed);

	}
	else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickRight))
	{
		moving = true;

		player.transform.Translate(tempR * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(tempR * Time.deltaTime * m_Speed);

		moving = true;
	}
	else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickLeft))
	{
		moving = true;

		player.transform.Translate(-tempR * Time.deltaTime * m_Speed);
		localAvatar.transform.Translate(-tempR * Time.deltaTime * m_Speed);

		moving = true;
	}
	else
	{
		moving = false;
	}

						
					

And add this to the update function:

						
void Update()
{
	Locomation();

	if (moving == true)
	{
		reversedSphere.GetComponent().enabled = true;
	}
	if (moving == false)
	{
		reversedSphere.GetComponent().enabled = false ;
	}
}

						
					

Now you should be moving in your scene.

This code has same limitations the walk won't work if the Y value of the terrain changes. The player will look like it is floating, rather than moving. You can fix it by raycasting to the ground and calculating the distance between the train and the player. Also, it doesn't have a solution for intersecting with other objects(buildings, rocks, trees etc.)

We will add one more thing to the code which is a reversed sphere with a texture on top. We will use this to limit the peripheral vision.

For that, we are going to enable the mesh renderer on the reversed sphere when the player is moving.

I exported the reversed sphere from Maya you can download the one I’m using here.

And this is the finished code with the sphere. This is the full code you can copy paste this to an empty game object in your scene;

						
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class InputSystem : MonoBehaviour
{

    //Keyboard inputs
    private float mRange = 1000.0f;
    private GameObject player;
    private GameObject centerEye;
    private GameObject localAvatar;
    private GameObject reversedSphere;

    private Vector3 playerInitialPosition;
    private Vector3 playerPos;


    [SerializeField]
    private float m_Speed = 5f;

    private Vector2 LeftAxisValue;
    private Vector2 RightAxisValue;

    private bool moving;

    // Use this for initialization
    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
        centerEye = GameObject.FindGameObjectWithTag("MainCamera");
        localAvatar = GameObject.FindGameObjectWithTag("localAvatar");
        reversedSphere = GameObject.FindGameObjectWithTag("reversedSphere");

        playerInitialPosition = playerPos;
        playerPos =  player.transform.position;

        reversedSphere.GetComponent().enabled = false;
    }

	void Update()
    {
        Locomation();

        if (moving == true)
        {
            reversedSphere.GetComponent().enabled = true;
        }
        if (moving == false)
        {
            reversedSphere.GetComponent().enabled = false ;
        }
    }

    void Locomation()
    {
        Vector3 forward = centerEye.transform.forward;
        Vector3 tempF = new Vector3(forward.x, 0f, forward.z);

        Vector3 right = centerEye.transform.right;
        Vector3 tempR = new Vector3(right.x, 0f, right.z);


        if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp))
        {
            moving = true;

            player.transform.Translate(tempF * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(tempF * Time.deltaTime * m_Speed);

        }
        else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickDown))
        {

            moving = true;

            player.transform.Translate(-tempF * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(-tempF * Time.deltaTime * m_Speed);

        }
        else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickRight))
        {
            moving = true;

            player.transform.Translate(tempR * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(tempR * Time.deltaTime * m_Speed);

            moving = true;
        }
        else if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickLeft))
        {
            moving = true;

            player.transform.Translate(-tempR * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(-tempR * Time.deltaTime * m_Speed);

            moving = true;
        }
        else
        {
            moving = false;
        }
       
    }

    void Flying()
    {
        Vector3 forward = centerEye.transform.forward;
        Vector3 right = centerEye.transform.right;

        if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp))
        {
            player.transform.Translate(forward * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(forward * Time.deltaTime * m_Speed);
        }
        if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickDown))
        {
            player.transform.Translate(-forward * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(-forward * Time.deltaTime * m_Speed);
        }
        if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickRight))
        {
            player.transform.Translate(right * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(right * Time.deltaTime * m_Speed);
        }
        if (OVRInput.Get(OVRInput.Button.PrimaryThumbstickLeft))
        {
            player.transform.Translate(-right * Time.deltaTime * m_Speed);
            localAvatar.transform.Translate(-right * Time.deltaTime * m_Speed);
        }
    }
}
					
						
					


Things to consider if it is not working;
- Make sure you tag the game object the same in the start function.
- Check if the local avatar and the OVRCameraRig are on the same position.
- Make sure the CenterEye under OVRCameraRig is tagged MainCamera
- Make sure you have VR Supported checked and you have the Oculus SDK selected in the player settings.