Building Your First Vr Game

May, 2021


Hello!!! 👋 After developing 2D games for 2 years, I finally decided to add a 3rd dimension. And instead of making a simple 3D game, I decided I should make a VR Game! This was a great idea


Nonetheless, here's how I made my first VR Game using the Unity Game Engine! In tutorial form, so you can follow along! 😄

PS: (This article assumes you have basic Unity 3D knowledge)
PPS: (
Here is the finished game we are building)

By @nampoh on Unsplash.com

🔌 Project Setup 🔌

After creating a new 3D Unity project, your gonna want to head over to the Unity Asset Store and find the Oculus Integration package. Click the blue “Add to My Assets” button on the right, and head back over to your unity project.

Back in Unity, open the package manager window and select: “My Assets” in the drop-down menu.

The Oculus Integration package should now be visible.

If you navigate over to the Oculus Integration in the top left of the Package Manager and select it. You can import it into your project by pressing the “Import” button in the bottom right corner of the window.

Once it is imported, search in the project tab for “OVRCameraRig” and drag it into the hierarchy and reset its position to (0, 0, 0). The Oculus Integration automatically handles all the position and rotation calculations between the real headset and the virtual camera rig. Your project is now set up and we are ready to go!

🎼 Game Scripting 🎼

Adding Swords đŸ”Ș

first step is finding sword models to add to your game! I found these weapon models itch.io for free, but you can find yours anywhere, as long as the models are in OBJ format.

Import your sword models into Unity, and add each sword under their corresponding hand anchors under the OVRCameraRig game object.

Give both of these swords a Mesh Collider and check off Convex and Is Trigger as options. This will allow collision detection further on!

Create a new tag for your swords called “Sword” and add it to each one.

The final step is to add a new script named: Sword to both your swords. The Sword script will handle playing a sound whenever we slice a cube.

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

public class Sword : MonoBehaviour {
    public AudioSource sliceAudio;

    public void PlaySliceSound() {
        sliceAudio.pitch = Random.Range(0.7f, 1.5f); // randomize pitch
        sliceAudio.Play(); // play sound
    }
}

After the script is added, add an AudioSource component to the swords, and drag in any audio clip. The clip will be played every time a cube is sliced.

Score Management 🏅

Create a new UI>Text-TextMeshPro in the Hierarchy, and import the TMPro essentials package from the pop-up.

Select your newly created text, and create a new script called: “Score Manager”

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

public class ScoreManager : MonoBehaviour {
    public TextMeshProUGUI scoreText; // reference to self
    public static int score; // static to make it easy to access accross all scripts & scenes

    void Start() {
        scoreText = GetComponent<TextMeshProUGUI>(); // self reference
        score = 0; // reset score
    }

    void Update() {
        scoreText.text = "Score: " + score; // update score display every frame
    }
}

Select the newly created Canvas, and change the render mode from “Screen Space - Overlay” to “World Space”. Move it to a new position in front of the player to make it easier to read.

Death Platform 💀

Create a new 3D Object>Plane and a new tag called “Die”. Tag your new plane with Die, check Convex & Is Trigger on the Mesh Collider and add a Rigidbody component with Is Kinematic checked.

This platform will destroy any cubes that come in contact with it, so reset its position, and move it down to -3 on the y axis.

Sliceable Cubes 🧊

Create a new 3D cube object in the Hierarchy, and drag it into the project view to create a new prefab. Add a Rigidbody component to it, and create a new script called “Shape”

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

public class Shape : MonoBehaviour {
    public bool sliced; // used to see if the object has been cut at all
    public bool cut; // used to make sure if you hit the same shape with 2 swords at the same time, it doesn't summon double the amount of shapes
    public int score = 5; // base score at a full size
    public float size = 1; // base start size
    public float sizeDivider = 2; // how much should the size of the shape be divided by every time a player slices it
    public int scoreMultiplier = 2; // how many more points should a player get for slicing smaller cubes

    void Start() {
        GetComponent<Rigidbody>().mass = size; // assign objects mass equal to it's size
        cut = false;
    }

    // Update is called once per frame
    void Update() {
        transform.localScale = new Vector3(size, size, size); // reset size
        if(size < 0.125f) { // destroy the game object if it gets too small to slice
            MinSizeReached();
        }
    }

    private void OnTriggerEnter(Collider other) {
        if (other.CompareTag("Sword")) { // check if we collide with an object with a sword tag
            if(cut == false) { // check if it's been cut before? This stops it from summoning more shapes than it should
                other.GetComponent<Sword>().PlaySliceSound(); // play the sound from the sword script
                Sliced();
            }
            cut = true;
        }
        else if(other.CompareTag("Die")) { // check if we collide with an object with a die tag
            if(sliced == true) { // if the cube has been cut at all, then nothing happens and we can just destroy the cube
                MinSizeReached();
            } else { // if not, the player has missed a cube, and must restart the level
                SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
            }
        }
    }

    void Sliced() {
        sliced = true;
        ScoreManager.score += score; // add score
        GameObject offcut1 = Instantiate(this.gameObject, transform.position, Quaternion.identity); // create smaller versions
        SetOffcutVariables(offcut1); // set variables
        GameObject offcut2 = Instantiate(this.gameObject, transform.position, Quaternion.identity); // repeat
        SetOffcutVariables(offcut2);
        Destroy(this.gameObject);
    }

    void SetOffcutVariables(GameObject offcut) { // apply all the multipliers
        offcut.GetComponent<Shape>().size = size / sizeDivider;
        offcut.GetComponent<Shape>().score *= scoreMultiplier;
        offcut.GetComponent<Rigidbody>().velocity = new Vector3(Random.Range(-1, 1 + 1), Random.Range(3, 7 + 1), Random.Range(-1, 1 + 1)); // makes the offcuts go in a slightly random direction
        offcut.GetComponent<Shape>().sliced = true;
    }

    void MinSizeReached() {
        Destroy(this.gameObject);
    }
}

Shape Spawner đŸ’Ÿ

Create a new Empty GameObject and set its location right above the death platform. Create a new script called “Spawner”

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

public class Spawner : MonoBehaviour {
    public GameObject[] shapes; // list of shapes the spawner can spawn
    public float startTimeBtwSpawns = 3;
    float timeBtwSpawns; // current time before another shape

    [Header("Start Velocities")]
    public float XminStartVelocity = -0.2f;
    public float XmaxStartVelocity = 0.2f;
    [Space]
    public float YminStartVelocity = 7;
    public float YmaxStartVelocity = 10;
    [Space]
    public float ZminStartVelocity = -0.2f;
    public float ZmaxStartVelocity = -2;

    private void Start() {
        timeBtwSpawns = startTimeBtwSpawns; // reset time at start
    }

    void Update() {
        timeBtwSpawns -= Time.deltaTime; // remove time from timer
        if(timeBtwSpawns <= 0) {
            timeBtwSpawns = startTimeBtwSpawns;
            SummonObject();
        }
    }

    void SummonObject() {
        startTimeBtwSpawns *= 0.99f; // shorten time btw spawns to make game harder over time
        GameObject curOBJ = Instantiate(shapes[Random.Range(0, shapes.Length)], transform.position, Quaternion.identity); // summon object
        curOBJ.GetComponent<Rigidbody>().velocity = new Vector3(Random.Range(XminStartVelocity, XmaxStartVelocity+1), Random.Range(YminStartVelocity, YmaxStartVelocity+1), Random.Range(ZminStartVelocity, ZmaxStartVelocity+1)); // make it go in a random direction
    }
}

Environment đŸŒČ

You can download forest environment models off of itch.io or the unity asset store to build a nicer-looking environment. As a bare minimum, I recommend creating a new 3D Object>Plane above the spawner as a barrier between the player and the appearing cubes. This will make it look as if the cubes are passing right through the ground.

đŸ§± Building Your Game đŸ§±

Open your build settings under File>Build Setting and switch to an android build. Plugin your headset to your computer through a USB cable, and with developer mode enabled on the headset (check out this article if developer mode isn’t enabled), press build and run, and play your game! You’re done! Good job!

Hopefully, this article helped you build your first VR Game! 🎉 If you have any questions or feedback for this article, please contact me by commenting on this article! See you soon with another project! 👋