Introduction to VR: Creating a First-Person Player Game for the Oculus Rift*

View PDF [12,544KB]

Introduction

This article introduces virtual reality (VR) concepts and discusses how to integrate a Unity* application with the Oculus Rift*, add an Oculus first-person player character to the game, and teleport the player to the scene. This article is aimed at an existing Unity developer who wants to integrate Oculus Rift into the Unity scene. The assumption is that the reader already has the setup to create a VR game for Oculus: an Oculus-ready PC and the Oculus Rift and touch controllers.

Development tools

  • Unity 5.5 or greater
  • Oculus Rift and touch controllers

Creating a Terrain in Unity

Multiple online resources are available that explain how to create a basic terrain in Unity. I followed the Unity manual. Adding lots of trees and grass details to the scene may have a performance impact, causing the frames per second (FPS) to decrease significantly. Make sure you have the optimal number of trees, if required, and set the min height/max height and min width/max width of the grass as low as possible to lessen the impact on the FPS. In order to improve the VR experience in your game, a minimum of 90 FPS is recommended.

Setting up the Oculus Rift

This section explains how to set up the Oculus Rift, place the Oculus first-person character in the scene, and teleport the player from one scene to another.

To set up Oculus, follow the downloadable instructions from the Oculus website.

Once you complete the setup, make sure that Oculus is integrated with your machine, and then do the following:

  1. Download the Oculus utilities for Unity 5.
  2. Import the Unity package into your Unity project.
  3. Remove the Main Camera object from your scene. It’s unnecessary because the Oculus OVRPlayerController prefab already comes with a custom VR camera.
  4. Navigate to the Assets/OVR/Prefabs folder.
  5. Drag and drop the OVRPlayerController prefab into your scene. You can work with the OVRCameraRig prefab. For a description of these prefabs and an explanation of their differences, go to this link. The sample shown below was implemented using OVRPlayerController.

Adjust the headset for the best fit and so you have clear visibility of the scene. Adjust the settings as necessary and according to the instructions provided while setting up the Oculus Rift. You can click the Stats button to observe the scene’s FPS. If the FPS is less than the recommended 90 FPS, decrease the details in your Unity scene or troubleshoot to find out what parts of the scene are consuming more CPU/GPU and why that is impacting the FPS.

Now let’s look at how we can interact with the objects in your scene with the Oculus touch controllers. Let’s add a shotgun model to the scene so that the player can attack the enemies. You can either create your own model or download it from the Unity Asset Store. I downloaded the model from the Unity store.

  1. Make this model the child of the RightHandAnchor of the OVRPlayerController as shown below.
  2. Adjust the size and orientation of the model so that it fits the scene and your requirements.

Now once you move the right touch controller, you are directly interacting with the shotgun in the scene.

Adding the Code to Work with the Oculus Touch Controller

In the code snippet shown below, we check the OVRInput and based on the button pressed in the Oculus touch controller, we are doing one of three things:

  • If the Primary Index Trigger button (that is, the right controller’s trigger button) is pressed, we call the RayCastShoot function with the Teleport option set to false. This condition lets the player object fire at the enemies and any other targets that we set up in the scene. We are also making sure that we can only fire once within the specified time variable by checking the condition Time.Time > nextfire.
  • If the A button on the controller is pressed, we call the RayCastShoot function, setting the Teleport option to true. This option allows the player to teleport to different points in the terrain. The teleported points can be either predefined points set in the scene, or the player can be teleported directly to the hit point. It is up to the developer to decide, based on the requirements of the game, where in the scene to teleport the player.
  • If the B button of the controller is pressed at any time in the game, the position of the player is reset to its original position.
void Update () {

        if (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && (Time. Time > nextfire))

        {
            //If the Primary Index trigger is pressed on the touch controller we fire at the targets
            nextfire = Time. Time + fireRate;
            audioSource.Play();

            // Teleporting is set to false here
            RayCastShoot(false);        }
        else if (OVRInput.Get(OVRInput.RawButton.A) && (Time.time > nextfire))
        {

            // Teleporting is set to true when Button A is pressed on the controller
            nextfire = Time.time + fireRate;

            RayCastShoot(true);

        }

        else if (OVRInput.Get(OVRInput.RawButton.B) && (Time.time > nextfire))
        {
            // If Button B is pressed on the controller player is reset to his original position

            nextfire = Time.time + fireRate;
            player.transform.position = resetPosition;
			
        }
    }

In the sample below, I added zombies, downloadable from the Unity Asset Store, as the enemies and also added some targets, like rocks and grenades, to add more particle effects, like explosions, rock impacts, and so on, to the scene. I also created a simple animation for the zombie following this tutorial.

Now let’s look at the RayCastshoot function. Physics.Raycast casts a ray from the gunTransform position in a forward direction against the colliders in the scene. The range is specified in the weaponRange variable. If the ray hits something, it is stored in the hit variable.

RaycastHit hit;
        
        if (Physics.Raycast(gunTransform.position, gunTransform.forward, out hit, weaponRange))

The function RayCastshoot takes a Boolean value. If the value is true, the function teleports the player; if the value is false, it checks for any objects in the scene, like zombies, rocks, grenades, and so on, that it collides with and destroys the enemies and the targets.

The first thing we do is for the zombie object. We add the Physics rigid body component and set its kinematic value to true. We also add a small script, which we named Enemy.cs, and attach it to the enemy object. The script shown below takes a function and checks the life of the enemy. Each call to the enemyhit function (that is, whenever we fire at the enemy) reduces the enemy’s life by one. After the enemy is shot five times, it is destroyed.

In the RayCastshoot function we call this function to get the handle to the Zombie object and determine if we are actually firing at the zombie.

Enemy enemy = hit.collider.GetComponentInParent<Enemy>();​

If the enemy object is not null, we call the enemyhit function to reduce its life by one. We also instantiate the blood effect prefab, as shown below, each time the zombie is hit. We check the full life of the enemy, and if it less than zero we destroy the zombie object.

//Enemy.cs
public class Enemy : MonoBehaviour {

    //public GameObject explosionPrefab;
    public int fullLife = 5;


    public void enemyhit(int life)
    {
        //subtract life  when Damage function is called
        fullLife -= life;        

        //Check if full life has fallen below zero
        if (fullLife <= 0)
        {
            //Destroy the enemy if the full life is less than or equal to zero
            Destroy(gameObject);
            
        }
    }
	
}

// if the hit object is the enemy
//Raycastexample.cs from where we are calling the enemyhit function
if (enemy != null)

            {
                enemy.enemyhit(1);
                
                //Checks the health of the enemy and resets to  max again
                //Instantiates the blood effect prefab for each hit

                var bloodEffect = Instantiate(bloodPrefab);
                bloodEffect.transform.position = hit.point;

                if (enemy.fullLife <= 0)
                {

                    enemy.fullLife = 5;                    
                }
            }

If the object we hit is anything other than the zombie, we can access the object by adding a tag to the different objects in the scene. For example, we added a tag called “Mud” for the ground, a tag called “Rock” for rocks, and so on. As shown in the code sample below, we can compare the tags to objects that we hit, then instantiate the respective prefab effects for those objects.

//If the hit targets are the targets other than the enemy like the mud, Rocks , Grenades on the terrain
else
            {
                var impactEffect = Instantiate(impactEffectPrefab);
                impactEffect.transform.position = hit.point;
                Destroy(impactEffect, 4);

                // If the Target is the ground 
                if ((hit.collider.gameObject.CompareTag("Mud")))
                {
                    
                    var mudeffect = Instantiate(mudPrefab);
                    mudeffect.transform.position = hit.point;
                   

                }

                // If the Target is  Rocks 
                else if ((hit.collider.gameObject.CompareTag("Rock")))
                {

                    var rockeffect = Instantiate(rockPrefab);
                    rockeffect.transform.position = hit.point;
                }

                // If the Target is the Grenades 

                else if ((hit.collider.gameObject.CompareTag("Grenade")))
                {

                    var grenadeEffect = Instantiate(explosionPrefab);
                    grenadeEffect.transform.position = hit.point;
                    Destroy(grenadeEffect, 4);

                }
            }           
            
        }

Teleporting

Teleporting is an important aspect in VR games that is recommended so that the user can avoid nausea when moving around the scene. The example shown below implements a simple teleporting mechanism in Unity. In the code we can either teleport the player to the “hit” point or we can create multiple points in the terrain where the player can be teleported.

  1. Create an empty game object and name it “Teleport.”

  2. Create a tag called “Teleport.”

  3. Assign the Teleport tag to the teleport object as shown below.

  4. Press CTRL+D and duplicate these points to create more teleport points in the scene. Adjust the positions of the points so that they span the terrain. I set the y position the same as my OVR player prefab so that the y value in the points are the same as my camera position.

As per the code below, if the teleport is set to true, we get the array of all the points in the teleportPoints variable, and we randomly pick one of these points for the player to teleport.

var newPosition = teleportPoints[Random.Range(0, teleportPoints.Length)];

Finally, we set the player’s transform position to the new position.

player.transform.position = newPosition.transform.position;
if (teleport)
            {
                //If the player needs to be teleported to the hit point 
                // Vector3 newposition = hit.point; 
                //player.transform.position = new Vector3(newposition.x, player.transform.position.y, newposition.z);


                //If the player needs to be teleported to the teleport points that are created in the Unity scene. Below code teleports the player
                // to one of the points randomly
               
                var teleportPoints = GameObject.FindGameObjectsWithTag("Teleport");
                var newPosition = teleportPoints[Random.Range(0, teleportPoints.Length)];

                
                player.transform.position = newPosition.transform.position;               

                return;
            }

Building Settings and Deploying the VR Application

After you are finished with the game, deploy your VR application for PCs.

  1. Go to File > Build Settings, and then for Target Platform, select Windows.

  2. Go to Edit > Project Settings > Player, and then click the Inspector tab.
  3. Click Other Settings, and then select the Virtual Reality Supported check box.

  4. Compile and then build to get the final VR application.

Conclusion

Creating a VR game is a lot of fun, but it also requires preciseness. If you are an existing Unity developer and have a game that is not specific to VR, you can also integrate it with Oculus Rift and port it as a VR game. At the end of this article, we list a number of references that focus on best practices in VR.

Below is the complete script for the sample scene discussed in this article.

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

public class Raycastexample : MonoBehaviour {


    //Audio clip to play
    public AudioClip clip;
    public AudioSource audioSource;   

    //rate of firing at the targets
    public float fireRate = .25f;
    // Range to which Raycast will detect the collision
    public float weaponRange = 300f;
    
    //Prefab for Impacts at the target
    public GameObject impactEffectPrefab;
    //Prefab for Impacts for grenade explosions
    public GameObject explosionPrefab;

    //Prefab at gun transform position
    public GameObject GunfirePrefab;

    //Prefab if the target is the terrain
    public GameObject mudPrefab;
    // Prefab when hits the Zombie
    public GameObject bloodPrefab; 
    
    // prefabs when hits the rocks       
    public GameObject rockPrefab;

    // Player transform that is used in teleporting
    public Transform player;   
    private float nextfire;

    //transform at the Gun end to show some muzzle effects when firing
    public Transform gunTransform;
    // Position to reset the player to its original position when "B" is pressed on the touch controller
    private Vector3 resetPosition;

    // Use this for initialization
    void Start () {

        // Play the Audio clip while firing
        audioSource = GetComponent();
        audioSource.clip = clip;
        // Reset position after teleporting to set the position to his original position
        resetPosition = transform.position;

    }
	
	// Update is called once per frame
	void Update () {
        //If the Primary Index trigger is pressed on the touch controller we fire at the targets
        if (OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger) && (Time.time > nextfire))
        {            
            nextfire = Time.time + fireRate;
            audioSource.Play();
            // Teleporting is set to false here
            RayCastShoot(false);
        }
        else if (OVRInput.Get(OVRInput.RawButton.A) && (Time.time > nextfire))
        {

            // Teleporting is set to true when Button A is pressed on the controller
            nextfire = Time.time + fireRate;

            RayCastShoot(true);

        }

        else if (OVRInput.Get(OVRInput.RawButton.B) && (Time.time > nextfire))
        {
            // If Button B is pressed on the controller player is reset to his original position

            nextfire = Time.time + fireRate;
            player.transform.position = resetPosition;

        }

    }

    private void RayCastShoot(bool teleport)
    {
        RaycastHit hit;
        //Casts a ray against the targets in the scene and returns the "hit" object.
        if (Physics.Raycast(gunTransform.position, gunTransform.forward, out hit, weaponRange))
        {

            if (teleport)
            {
                //If the player needs to be teleported to the hit point 
                // Vector3 newposition = hit.point; 
                //player.transform.position = new Vector3(newposition.x, player.transform.position.y, newposition.z);


                //If the player needs to be teleported to the teleport points that are created in the Unity scene. Below code teleports the player
                // to one of the points randomly
               
                var teleportPoints = GameObject.FindGameObjectsWithTag("Teleport");
                var newPosition = teleportPoints[Random.Range(0, teleportPoints.Length)];

                
                player.transform.position = newPosition.transform.position;

                

                return;
            }

            //Attach the Enemy script as component to the enemy
            
            Enemy enemy = hit.collider.GetComponentInParent();

            // Muzzle effects of the Gun and its tranfrom poisiton is the Gun

            var GunEffect = Instantiate(GunfirePrefab);
            GunfirePrefab.transform.position = gunTransform.position;



            // if the hit object is the enemy
            
            if (enemy != null)

            {
                enemy.enemyhit(1);
                
                //Checks the health of the enemy and resets to  max again
                //Instantiates the blood effect prefab for each hit

                var bloodEffect = Instantiate(bloodPrefab);
                bloodEffect.transform.position = hit.point;

                if (enemy.fullLife <= 0)
                {

                    enemy.fullLife = 5;                    
                }
            }

            //If the hit targets are the targets other than the enemy like the mud, Rocks , Grenades on the terrain

            else
            {
                var impactEffect = Instantiate(impactEffectPrefab);
                impactEffect.transform.position = hit.point;
                Destroy(impactEffect, 4);

                // If the Target is the groud 
                if ((hit.collider.gameObject.CompareTag("Mud")))
                {
                    Debug.Log(hit.collider.name + ", " + hit.collider.tag);
                    var mudeffect = Instantiate(mudPrefab);
                    mudeffect.transform.position = hit.point;
                   

                }

                // If the Target is  Rocks 
                else if ((hit.collider.gameObject.CompareTag("Rock")))
                {

                    var rockeffect = Instantiate(rockPrefab);
                    rockeffect.transform.position = hit.point;
                }

                // If the Target is the Grenades 

                else if ((hit.collider.gameObject.CompareTag("Grenade")))
                {

                    var grenadeEffect = Instantiate(explosionPrefab);
                    grenadeEffect.transform.position = hit.point;
                    Destroy(grenadeEffect, 4);

                }
            }           
            
        }
    }        
}
//Enemy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour {

    //public GameObject explosionPrefab;
    public int fullLife = 5;

    // Use this for initialization
    void Start () {
		
	}


    public void enemyhit(int life)
    {
        //subtract life  when Damage function is called
        fullLife -= life;        

        //Check if full life has fallen below zero
        if (fullLife <= 0)
        {
            //Destroy the enemy if the full life is less than or equal to zero
            Destroy(gameObject);
            
        }
    }

    
}

References for VR on the Intel® Developer Zone

Virtual Reality User Experience Tips from VRMonkey: https://software.intel.com/en-us/articles/virtual-reality-user-experience-tips-from-vrmonkey

Presence, Reality, and the Art of Astonishment in Arizona Sunshine: https://software.intel.com/en-us/blogs/2016/12/01/presence-reality-and-the-art-of-astonishment-in-arizona-sunshine

Combating VR Sickness with User Experience Design: https://software.intel.com/en-us/articles/combating-vr-sickness-with-user-experience-design

Interview with Well Told Entertainment about their Virtual Reality Escape Room Game: https://software.intel.com/en-us/blogs/2016/11/30/interview-with-well-told-entertainment-about-their-virtual-reality-escape-room-game

What is the Next Leap in VR Experiences?: https://software.intel.com/en-us/videos/what-is-the-next-leap-in-vr-experiences

VR Optimization Tips from Underminer Studios: https://software.intel.com/en-us/articles/vr-optimization-tips-from-underminer-studios

VR Optimizations with Intel® Graphics Performance Analyzers: https://software.intel.com/en-us/videos/vr-optimizations-with-intel-graphics-performance-analyzers

Creating Immersive Virtual Worlds Within Reach of Current-Generation CPUs: https://software.intel.com/en-us/articles/creating-immersive-virtual-worlds-within-reach-of-current-generation-cpus

About the Author

Praveen Kundurthy works in the Intel® Software and Services Group. His main focus is on mobile technologies, Microsoft Windows*, virtual reality, and game development.

For more complete information about compiler optimizations, see our Optimization Notice.