Unity | C# | VR | TUTORIAL
Locomation Movement in Oculus Rift (Tutorial)
This tutorial, I will be talking about VR locomotion movement in VR. I will be using 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. So the player will move teh direction of where they are looking. We will have forward, backward and side to side movements.
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.
Next, let's add 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 already has a tag as a MainCamera which we will use.
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.
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 moving the thumbstick. You should see the Debug.Log outputs in the console.
Next, let’s write the movement code.
- We will 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);
}
}
This code will translate the player on the Y axis too. You can use this version of the code for a fly movement.
For a walk movement, Let's limit the player only to move in 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;
}
}
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.)
Locomation in VR is not the best solution for the movement problem, this motion gives players motion sicness. To help the users with this problem we will add a sphere that will to limit the peripheral vision.
For that, toggle the mesh renderer on a sphere that is placed around the players head.
I exported a sphere that has reversed normals from Maya. You can download it from here.
Add this to the update function:
void Update()
{
Locomation();
if (moving == true)
{
reversedSphere.GetComponent().enabled = true;
}
if (moving == false)
{
reversedSphere.GetComponent().enabled = false;
}
}
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 the tags of the game objects matchs the tags in the start function.
- Check if the local avatar and the OVRCameraRig are on the same position in the scene.
- Make sure the CenterEye under OVRCameraRig is tagged as MainCamera.
- In the player settings make sure you have VR Supported checked and Oculus SDK selected.