Unity Tutorials Beta

Learn how to create games in Unity, a powerful and popular game engine.

Veggie Saber – Introduction to Unity Development with the Oculus Quest

In this tutorial, you’ll learn how to develop and deploy a game for Oculus Quest in Unity by developing Veggie Saber, a Saber VR game featuring tasty veggies!

5/5 2 Ratings

Version

  • C# 7.3, Unity 2019.2, Unity

Launched in 2019, the Oculus Quest provides a polished, standalone VR experience. It has two crisp 1600×1440 displays, a pair of Oculus Touch controllers and, best of all – no cords! It’s time to develop for untethered VR!

Oculus Quest

In this tutorial, you’ll implement “Veggie Saber”, a Saber VR game featuring tasty veggies. This tutorial guides you through creating this quick game in Unity with the Oculus SDK and the Unity XR API.

Getting Started

Before you dive into this tutorial, make sure you have the following:

Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial. Unzip the file and open the starter folder in Unity. It has all the assets you’ll need to build Veggie Saber.

The starter project has three important things:

  • Models and materials for the vegetables, the lightsaber and the virtual hands.
  • Oculus, OpenVR, TextMesh Pro and XR Legacy Input Helpers packages.
  • Preconfigured project settings for VR rendering.

Building Your Virtual Body

In virtual reality, you’re represented by your headset and your hand controllers. The first thing you need to do to make a good game is to convert the headset’s movement to the virtual world.

Tracking the Player’s Head Movements

Start by adding an empty GameObject called Player Rig, which will hold GameObjects representing the player’s head and hands.

Rename Main Camera to Head and move it under Player Rig. This GameObject will represent your headset. Set its Position to X:0.0, Y:1.75, Z:0.0.

Select the Camera component inside the Head GameObject and change Clipping Planes to 0.02. This will enable you to see objects much closer to your eyes. Next, switch the Background Type to Solid Color and set the Background to a solid black.

Camera skybox

Add a Tracked Pose Driver component to Head. Click on the Tracked Pose Driver component and set the ‘Device’ field as Generic XR Device, then set Pose Source as Head. Disable Use Relative Transform.

This will now move the Head GameObject to have six degrees of freedom in its movement, tracking your real-world head position.

Player Rig

Setting Up the Right Hand

Now, to set up the hands, create an empty GameObject under Player Rig and name it Hand R. Again, add the Tracked Pose Driver, but now set the ‘Device’ as Generic XR Controller and the ‘Pose Source’ as Right Controller. Again, Disable Use Relative Transform.

Drag and drop the RW/Prefabs/Hand model as a child of the Hand R GameObject. This adds a model hand, which will be the visual part of your VR hand.

Select the Hand GameObject, and set the Rotation to X:0.0, Y:0.0, Z:-90.0 to orient the model. In the Animator component, set the Controller to Assets/RW/Animations/Hand to provide animations for the hands.

Oculus Controller

OculusHandAnimation.cs, in RW/Scripts, animates your interaction with controllers. Open it and take a quick look at what it does:

public XRNode HandType;
public Animator HandAnimator;

private void Update()
{
	bool grip = false;
	bool trigger = false;
	bool primaryAxisTouch = false;
	bool primaryTouch = false;
	bool secondaryTouch = false;
	float triggerDown = 0;

	//1. Collect controller data
	InputDevice hand = InputDevices.GetDeviceAtXRNode(handType);
	hand.TryGetFeatureValue(CommonUsages.gripButton, out grip);
	hand.TryGetFeatureValue(CommonUsages.triggerButton, out trigger);
	hand.TryGetFeatureValue(CommonUsages.primary2DAxisTouch, out primaryAxisTouch);
	hand.TryGetFeatureValue(CommonUsages.primaryTouch, out primaryTouch);
	hand.TryGetFeatureValue(CommonUsages.secondaryTouch, out secondaryTouch);
	hand.TryGetFeatureValue(CommonUsages.trigger, out triggerDown);

	bool thumbDown = primaryAxisTouch || primaryTouch || secondaryTouch;

	//2. Trigger down
	float triggerTotal = 0f;
	if (trigger)
	{
		triggerTotal = 0.1f;
	}
	if (triggerDown > 0.1f)
	{
		triggerTotal = triggerDown;
	}

	//3. Set animations
	HandAnimator.SetBool(“GrabbingGrip”, grip);
	HandAnimator.SetBool(“ThumbUp”, !thumbDown);
	HandAnimator.SetFloat(“TriggerDown”, triggerTotal);
}

Here, this code:

  1. Accesses controller data so that you know its current state.
  2. Gives a minimum cutoff of 0.1f for the trigger; this prevents touchy activation of the trigger button.
  3. Using data obtained, sets animations for the virtual hand.

Next, add a Oculus Hand Animation component to Hand R. Set the field Hand Type to Right Hand and drag and drop the Hand GameObject into Hand Animator. This will put Animator inside Hand to trigger animations.

Right Hand

Duplicating the Process for the Left Hand

For the left hand, all you need to do is duplicate Hand R by right-clicking inside Inspector and clicking on duplicate, then changing some variables.

First, change its name to Hand L. In the Tracked Pose Driver component, switch Pose Source to Left Controller. Then, in the Oculus Hand Animation component, switch the Hand Type to Left Hand.

Hands

At this point, both hands look exactly the same, but this is easy to fix. Find the child GameObject Hand L/Hand and set the transform ‘Scale Y’ to -1. This mirrors the model and makes it left-handed.

Now, you’ll set the transforms of the Hand R and Hand L to position the hands slightly apart. Set the Hand R Transform’s Position to be X:0.1, Y:1.0, Z:0.0. Set the Hand L Transform’s Position to be X:-0.1, Y:1.0, Z:0.0.

Congratulations, you now have visual representations of the hands and head for any VR game. The hand models have animated fingers that respond to the controller input for grab and trigger inputs giving the player more physical presence in the VR game.

Positioning Objects Relative to Your Floor

VR headsets start in two different modes: Stationary or RoomScale. Now, you’ll set the mode to RoomScale at start so that both desktop VR headsets and the Quest put objects relative to the same floor height.

Edit RW/Scripts/PlayerRigSetup.cs and set the tracking type at the start by adding this code:

 
void Start()
{
    bool success = XRDevice.SetTrackingSpaceType(TrackingSpaceType.RoomScale);
}

To try it out, add a Player Rig Setup component to PlayerRig. If you play the scene in the Unity Editor with a PC VR headset, you can now see the following animations:

Hands Movie

Save your Scene as VeggieSaber and continue to the next section.

Grabbing Objects

It’s time to grab your lightsabers!
Start by opening the stubbed script, RW/Scripts/GrabItem.cs. Add the following to Update:

void Update()
{
    bool gripDown = false;
    InputDevice hand = InputDevices.GetDeviceAtXRNode(handType);
    hand.TryGetFeatureValue(CommonUsages.gripButton, out gripDown);

    // 1.
    if (gripDown)
    {
        // 2.
        Collider[] overlaps = Physics.OverlapSphere(transform.position, 0.2f);

        foreach (Collider c in overlaps)
        {
            GameObject other = c.gameObject;

	    // 3.
            if (other.GetComponent())
            {
                if (other.gameObject.transform.parent == null)
                {
                    other.transform.SetParent(transform);
                }
            }
        }
    }
}

The script behaves as follows:

  1. If the player presses the grip button, it will try to grab a colliding object.
  2. It uses an overlap sphere to find any overlapping colliders within a short 0.2 meter snap range.
  3. You can attach any GameObject with a Grabbable script to the hands.
  4. Grabbing works by setting the hand as the parent transform for the object, linking the hands to the saber movements.

Select both Hand R and Hand L inside the Player Rig. Next, add a Sphere Collider component with Radius of 0.1, then add the GrabItem component that was just completed. Now, set the Hand Type to be either Left Hand or Right Hand for each individual Hand R or Hand L GameObject.

Sphere Grab

Next, you just need something to grab, so drag the RW/Prefabs/SaberRed and RW/Prefabs/SaberBlue prefabs into the scene. Select both of these GameObjects, then add a BoxCollider and RW/Scripts/Grabbable.cs as components. Grabbable is an empty MonoBehaviour script that turns its GameObject to grabbable with Grab Item components.

Select SaberBlue and set its position to X:-0.25, Y:1.0, Z:0.25; likewise, set SaberRed‘s position as X:0.25, Y:1.0, Z:0.25. The scene should like like this:

Hands and Sabers

Now, if you have a VR headset for your PC, you can test the hand animations by grabbing sabers and swinging them around.

Grab Movie

Saber Slicing

Things are about to get a bit dicey, because you need to implement veggie slicing with your sabers! You want your players to be able to slice a vegetable in half with a satisfying sound effect.

Triggering the Slice

Select SaberBlue and SaberRed in the scene and add a RigidBody component to both. Enable the field IsKinematic. This will allow another colliding RigidBody to trigger events, such as the slicing event.

To implement that slicing effect, add a Slicer component to both sabers. Open its script to fill OnTriggerEnter as follows:

private void OnTriggerEnter(Collider other)
{
    SplitMesh(other.gameObject);
    Destroy(other.gameObject);
}

Slicing the Vegetables in Half

Other GameObjects that are “triggers” can collide with the saber to begin a slicing event. That slicing event splits a mesh in two and removes the original object.

Fill in SplitMesh(..) like so:

    
void SplitMesh(GameObject go)
{
    // 1.
    GameObject leftHalf = MakeHalf(go, true);
    GameObject rightHalf = MakeHalf(go, false);
    // 2.
    GetComponent().Play();
}

This is what the code is doing here:

  1. Splitting makes two copies of the mesh. This will give the illusion of slicing through the mesh.
  2. Slicing triggers a snare hit sound for feedback.

Now, complete MakeHalf(..):

GameObject MakeHalf(GameObject go, bool isLeft)
{
    // 1.
    float sign = isLeft ? -1 : 1;
    GameObject half = Instantiate(go);
    MeshFilter filter = half.GetComponent();

    // 2.
    Plane cuttingPlane = GetPlane(go);
    filter.mesh = CloneMesh(cuttingPlane, filter.mesh, isLeft);

	// 3.
    half.transform.position = go.transform.position + transform.rotation * new Vector3(sign * 0.05f, 0, 0);
    half.GetComponent().isKinematic = false;
    half.GetComponent().useGravity = true;

	// 4.
    half.GetComponent().isTrigger = false;
    Destroy(half, 2);
	return half;
}

Walking through this code:

  1. This creates a copy of the source GameObject using Instantiate.
  2. Next, it determines a cutting plane that matches the angle of the saber blade. It then clones the mesh using this cutting plane as a slice, and assigns the new mesh to the copy of the GameObject.
  3. The halves separate slightly, then you reenable physics by disabling isKinematic and enabling useGravity.
  4. You disable further sub-slicing by turning isTrigger to false. This prevents rapid cloning of too many sub-slices, which could easily crash the game.

So… how do you determine the cutting plane? With the following code:

private Plane GetPlane(GameObject go)
{
    // 1.
    Vector3 pt1 = transform.rotation * new Vector3(0, 0, 0);
    Vector3 pt2 = transform.rotation * new Vector3(0, 1, 0);
    Vector3 pt3 = transform.rotation * new Vector3(0, 0, 1);

    // 2.
    Plane rv = new Plane();
    rv.Set3Points(pt1, pt2, pt3);
    return rv;
}
  1. Unity has an easy helper function that creates a plane from three points that are on the plane. By multiplying two unit vectors (0, 1, 0) and (0, 0, 1) by the Quaternion rotation of the saber, you find the points that represent the slice in the matching saber cut.
  2. Now, you create a plane and use Set3Points(pt1, pt2, pt3) to turn it into an idealized cutting plane.

Flattening the Mesh

The last bit of magic lets you use the plane to flatten a mesh in a cheap slicing effect. You do this in CloneMesh:

private Mesh CloneMesh(Plane p, Mesh oMesh, bool halve)
{
    // 1.
    Mesh cMesh = new Mesh();
    cMesh.name = "slicedMesh";
    Vector3[] vertices = oMesh.vertices;
    for (int i = 0; i < vertices.Length; i++)
    {
        bool side = p.GetSide(vertices[i]);

        if (side == halve)
        {
            vertices[i] = p.ClosestPointOnPlane(vertices[i]);
        }
    }

    // 2.
    cMesh.vertices = vertices;
    cMesh.triangles = oMesh.triangles;
    cMesh.normals = oMesh.normals;
    cMesh.uv = oMesh.uv;
    return cMesh;
}

Here’s what this code does:

  1. Creates a new Mesh object, then passes over each vertex of the original Mesh. With Plane.GetSide, you check to determine which side the vertex falls on. If it’s not on the side of the half you want, you map it back to the cutting plane with Plane.ClosestPointOnPlane. This results in a new Mesh that appears to represent only half of the object.
  2. Reassigns the cloned mesh vertices, then triangles, normals and UV(s). Returns this mesh as the half.

Adding Sound Effects to the Slice

Now, it’s time to add that satisfying snare effect when the player slices a veggie. So select both SaberBlue and SaberRed and add an AudioSource component. Set the AudioClip as RW/Sounds/SnareHit.wav and disable Play On Awake.

If you have a PC VR headset, you can now test chopping by adding RW/Prefabs/ChoppingTable into the scene hierarchy below the two sabers. Press play on the Unity Editor, put on your headset and grab your sabers. It time to chop ’em up!

Chop it up

Adding Controller Haptics

Providing visual and haptic feedback during the experience will add to the player’s immersion. When the player’s saber slices the veggie, you can make them feel the contact by rumbling their Oculus controllers.

To start adding this feature, open RW/Scripts/ControllerHaptics.cs and add:

public void HapticEvent()
{
	// 1.
	InputDevice device = InputDevices.GetDeviceAtXRNode(hand);
	// 2.
	HapticCapabilities capabilities;
	if (device.TryGetHapticCapabilities(out capabilities))
	{
		if (capabilities.supportsImpulse)
		{
			uint channel = 0;
			float amplitude = 0.5f;
			float duration = 0.1f;
			// 3.
			device.SendHapticImpulse(channel, amplitude, duration);
		}
	}
}

Walking through this script:

  1. This script provides a haptic feedback event to either the left or right hand’s controller. Configure this by setting the property of the InputHand in the inspector.
  2. Before sending an event, check to make sure the device supports haptic rumbles.
  3. Use SendHapticImpulse with an amplitude and duration to change the feeling of the haptic event. You might use short and intense bumps when hovering over menus, like in the Oculus Home screen. But here, you use longer and less intense rumbles to feel like a distant hit between the saber and a veggie.

Attach the ControllerHaptics to each saber and be sure to set whether the event is for the left or right InputHand in the inspector.

Edit RW/Scripts/Slicer.cs and insert the following at the top of OnTriggerEnter:

// 1. Generate haptic event
ControllerHaptics haptics = GetComponentInParent();
if (haptics)
{
    haptics.HapticEvent();
}

Now, if you play the scene again in the Unity editor, you’ll get a haptic feedback response when you hit a veggie with your saber.

Look Out for Flying Veggies!

Just chopping veggies on a table is boring. Instead, you want the veggies fly towards the player in time with some great music.

To get started, inactivate the test asset ChoppingTable to hide it in the scene. Then, open RW/Scripts/VeggieBehaviour.cs in your editor and fill in Update() as follows:

void Update()
{
    // 1 If not using physics, this will update the movements of the veggies.
    if (rb.isKinematic)
    {
        // 2 Move from the origin to the position for the note in the first second.
        dTime += Time.deltaTime;

        if (dTime < 1.0f)
        {
            // 3 Lerp along the X/Y axis to the correct lane.
            Vector3 position = transform.position;
            float z = position.z;
            position.z = destination.z;

            position = Vector3.Lerp(position, destination, dTime);
            position.z = z;
            transform.position = position;
        }

		// 4 Move along Z
        transform.Translate(movement * Time.deltaTime);
    }
}

This script controls the movements of veggies before and after collisions:

  1. Each veggie GameObject has a RigidBody component. When the RigidBody sets isKinematic==true, it ignores the forces of physics. Instead, the GameObject’s movements are controlled by this simple script.
  2. The veggie starts out at the origin position of the VeggieGenerator, then moves sideways into the lane of the music.
  3. A lerp from the origin X/Y to the lane’s X/Y causes the veggies to move smoothly into their lane.
  4. Move the veggies along the Z axis towards the player every frame.

Making the Veggies Move With the Music

Next, you need to add a script that generates veggies that match the tempo of a background song. The provided song runs at 172 beats per minute, and with each beat, there’s a chance to create a veggie.

To do this, open RW/Scripts/VeggieGenerator.cs and add this to Update:

void Update()
{
    counter += Time.deltaTime;
    float beatInterval = 60.0f / BPM;

    // 1. Veggies appear with beats
    if (counter > beatInterval)
    {
        counter = 0f;
        if (Random.Range(0.0f, 1.0f) < cutoff)
        {
            CreateVeggie();
        }

        // 2. Move veggies as game progresses
        cutoff += 0.01f;
    }
}

Briefly, this script will:

  1. Attempt to create a veggie with each beat. There’s an infrequent chance that it will randomly create a veggie at the start.
  2. Increase the cutoff, which makes it more likely that it will create a veggie.

Creating the Veggies

Creating a veggie happens by instantiating GameObjects:

void CreateVeggie()
{
    if (veggies.Length == 0) return;

    // 1. Instantiate a random veggie model.
    int randomVeggie = Random.Range(0, veggies.Length - 1);
    GameObject veggie = Instantiate(veggies[randomVeggie]);
    veggie.transform.position = transform.position;

    // 2. Choose the lane the veggie will run in.
    int pos = Random.Range(0, 5);
    Vector3 destination = transform.position + new Vector3(startPositions[pos, 0], startPositions[pos, 1], startPositions[pos, 2]);

    // 3. Add a VeggieBehaviour component.
    VeggieBehaviour comp = (VeggieBehaviour) veggie.AddComponent(typeof(VeggieBehaviour));
    comp.movement = new Vector3(0, 0, -6);
    comp.destination = destination;
}
  1. Instantiate a random veggie from a list of possible veggies.
  2. Set the lane that the veggie will run in.
  3. Add the VeggieBehaviour component then set a speed and destination.

Create an empty GameObject in the scene named Level to hold any components of the music level. Add an empty child GameObject to it named VeggieSource. Set its transform position to X:0, Y:1.25, Z:12 to move the VeggieSource in front of the player. Add the VeggieGenerator component to VeggieSource.

Drag all veggies from RW/Prefabs/Veggies into the Veggies field of the VeggieGenerator to provide the models that will be randomly thrown at the player.

VeggieSource

What Happens When the Player Misses?

You can play the scene at this point, but if the player misses some veggies, there aren’t any consequences yet.

To fix this, add another empty child GameObject to Level called EndWall. Set its transform position as X:0, Y:0, and Z:-2 to put it behind the player. Add a RigidBody component and enable Is Kinematic. Then add a Box Collider component and set its size as X:5, Y:5, Z:1 to make a large colliding plane to catch the veggies that pass the player.

Now open the RW/Scripts/TrapMisses.cs script and fill in OnTriggerEnter:

void OnTriggerEnter(Collider other)
{
    // 1. Create a message
    GameObject textMessage = Instantiate(quickMessage);
    textMessage.transform.position = gameObject.transform.position;
    textMessage.GetComponent().text = "Missed!";

    // 2. Destroy the missed object
    Destroy(other.gameObject);
}

When a VeggieObject collides with the EndWall, it will trigger this method that:

  1. Creates a TextMeshPro message saying “Missed!”
  2. Destroys the colliding VeggieObject to clear it from the scene.

You also want to be able to track how often a player misses, so attach TrapMisses to EndWall . Next, drag RW/Prefabs/QuickMessage into Quick Message in Trap Misses.

Note: If a TMP Importer panel didn’t appear, go to Window ► TextMeshPro ► Import TMP Essential Resources. You need to do this for TextMeshPro to behave correctly.

Adding Some Awesome Music

Finally, the level needs some rocking music! Attach an AudioSource component to the Level GameObject and set its AudioClip to RW/Sounds/FeelGood.wav. Enable Loop on the audio clip so that the song will repeat.

You can now play the scene from the Unity Editor.

Slice 'em up

Congratulations, you’ve achieved the basic Veggie Saber game!

GameManager

The last steps you need to take are to keep track of scores and to show a dialog screen. Start by opening RW/Scripts/GameManager.cs and adding the following to Update():

public void Update()
{
	if (gameState == State.Menu && sabers[0].transform.parent && sabers[1].transform.parent)
	{
		ChangeState(State.Level);
	}
	if (misses > maxMisses)
	{
		if (score > highScore)
		{
			highScore = score;
		}
		ChangeState(State.Menu);
	}
}

Then fill in the ChangeState(State state) method:

public void ChangeState(State state)
{
	gameState = state;
	if (state == State.Menu)
	{
		menu.SetActive(true);
		level.SetActive(false);
	}
	if (state == State.Level)
	{
		score = 0;
		misses = 0;
		menu.SetActive(false);
		level.SetActive(true);
	}
}

Drag the prefab RW/Prefabs/Menu into the scene to add a basic menu UI. The menu prefab provides a canvas that displays the latest and highest scores.

The Game Manager should exit the level after too many misses and switch to this menu screen. Create an empty GameObject in the scene and name it Game Manager, then attach RW/Scripts/GameManager.cs to it.

In the Inspector view:

  1. Drag the Menu GameObject into the menu slot.
  2. Then drag the Level GameObject into level.
  3. Last, add both SaberBlue and SaberRed into sabers.

Updating the Scores

Next, you need to connect the Sabers to add points to the GameManager. Select both SaberBlue and SaberRed, find the component Slicer and drag the scene’s GameManager into the GameManager field.

Edit RW/Scripts/Slicer.cs and add the following to OnTriggerEnter(Collider other):

if (gameManager)
{
    gameManager.GetComponent().score += 100;
}

Now, slicing a veggie adds to the game score.

Select the Level/EndWall GameObject and drag the scene’s GameManager into the GameManager field in Trap Misses.

Edit RW/Scripts/TrapMisses and add the following to OnTriggerEnter(Collider other):

// 3. Add to the tally
if (gameManager)
{
	gameManager.GetComponent().misses += 1;
}

Likewise, misses are now tracked in GameManager.

Scores will update from RW/Scripts/MenuSetup.cs. Open this script and complete OnEnable:

public void OnEnable()
{
    // 1.
    SetSaberLocation(sabers[0], leftStart);
    SetSaberLocation(sabers[1], rightStart);

    // 2.
    SetScores();
}

Here, you’re:

  1. Setting the sabers back to start positions.
  2. Setting the score texts to show the high and last scores.

Next, add the following method to update the score texts:

public void SetScores()
{
    // 1.
    TextMeshProUGUI highscore = highScoreText.GetComponent();
    highscore.text = gameManager.GetComponent().highScore.ToString();

    // 2. 
    TextMeshProUGUI score = scoreText.GetComponent();
    score.text = gameManager.GetComponent().score.ToString();
}

This sets the TextMeshProUGUI text to match the game’s scores.

Start menu

Finally, fill in the SetSaberLocation(..) method:

private void SetSaberLocation(GameObject saber, Vector3 position)
{
    if (saber)
    {
        saber.transform.SetParent(null);
        saber.transform.position = transform.position;
        saber.transform.localPosition = position;
        saber.transform.localRotation = Quaternion.identity;
    }
}

Now select the Menu GameObject and expand the Menu Setup component. Drag SaberBlue and SaberRed into sabers. Drag the GameManager into Game Manager, High Score into High Score Text and also Score into Score Text to finish wiring up connections between the GameObjects in the scene.

Great! Save your scene then play it. Now, a menu should be visible before grabbing the sabers. Hitting objects scores points until the player misses too many veggies and the round ends.

Deploying on the Oculus Quest

So your VR game works on your PC, but you want to get it onto your untethered VR device. Here are the steps you need to take to deploy your game on the Oculus Quest.

Enabling Developer Mode via App

To create apps for the Oculus Quest, you need to register an account with Oculus, then turn on the developer mode on the Quest as follows:

  1. Create a developer account and setup an organization to enable developer mode on the device. To do this, visit https://developer.oculus.com/ and create a new account via ‘login’.
  2. Install the Oculus app for an Android or iPhone to pair with the Quest device.
  3. From the Oculus app, go to Settings ► Advanced Settings. On this page, enable developer settings.

Building the APK Package

Your next step is to get your APK package up and running. To do it, take the following steps:

  1. Go to File ► Build Settings.
  2. Make sure that VeggieSaber is the only scene.
  3. Select Android as your platform.
  4. Click Switch Platform to update the project before building.
  5. Switch Texture Compression to ASTC. ASTC compression is recommended for the Oculus Go and Quest, but ETC2 is another option.
  6. Select Run Device and change to Oculus Quest.
  7. Click on Player settings, then go to Settings for Android and set the Splash Image, Virtual Reality Splash Image to RayWenderlichLogo.png to provide a splash screen while loading.
  8. Under Player settings ► Other settings in Graphics API, remove the Vulcan API and make sure that OpenGLES3 is the first option.
  9. Find Package Name and set this to com.rw.veggiesaber. This is what the Java package for the program will show when running on the Oculus Quest.
  10. In the Player Settings, under Other Settings, set the Minimum API Level to Android 6.0 Marshmallow, API Level 23.
  11. Make sure that Player Settings ► XR Settings still has Virtual Reality Supported enabled and that you’ve included the Virtual Reality SDK (Oculus).
  12. Under the Oculus entry, enable the setting “V2 Signing(Quest)”, which is needed to create the AndroidManifest.xml correctly to support both Quest controllers. If this setting is missing, make sure that you have switched to the Android build platform and then update the Oculus(Android) package to version 1.38.4 or later by going to Window->Package Manager and downloading the latest Oculus(Android) package.
  13. Return to the Build Settings, then select Build. When asked, save the build output as VeggieSaber.apk.

Sideload the App to the Quest

Connect the Oculus Quest with the USB-C cable to your computer. If your computer does not have a USB-C port, you’ll need to get a USB-C to USB-A cable.

Download the Android [Platform-Tools] package and unzip it.

Using Terminal (for Mac) or Command Prompt (for Windows), navigate to the opened directory containing the executable ‘adb’ using the following command:

cd ADB_Directory

Replace ADB_Directory with the path to your adb folder.
The Android Debug Bridge (adb) provides the way to deploy the built software to the Oculus Quest device. With your Oculus Quest connected, you can now type:

adb devices

Which should display a message such as ‘1PASXXXXXH9114 unauthorized’. Now, put on the Oculus Quest headset and it will ask whether to trust this computer. Accept and select always allow for this PC.

Now, when you type adb devices, the message will change to:

‘1PASXXXXXH9114 device’

It’s now ready to accept your deployed software.

Install Veggie Saber on the Oculus Quest

While still in the Console or Command Prompt, enter the following command and press enter:

‘adb.exe install [path to VeggieSaber.apk]

When this command completes… congratulations! You’ve installed your first game on the Oculus Quest!

You can start playing by putting on the headset, navigating to Library ► Unknown Sources and selecting com.rw.veggiesaber to launch the app.

Where to Go From Here?

If you’d like to know more about developing experiences for VR using the latest in Unity features and tools, here are some links:

  1. Oculus Integration for Unity provides the Oculus OVRPlugin and scripts for controller and headset input, and helper utilities such as setting the floor height. Get more details here: Oculus Integration.
  2. Learn more about building custom effects with the Shader Graph and Lightweight Rendering Pipeline: Unity Shader graph.
  3. Learn about optimizing for VR experiences with Unity VR and AR by Tutorials.

Thanks for reading through this tutorial. If you have any questions or comments, feel free to ask in the forums or to leave a comment below!

Average Rating

5/5

Add a rating for this content

2 ratings

Contributors

Comments