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! By Matt Larson.

Leave a rating/review
Download materials
Save for later
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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)

        // 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.


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<TextMeshPro>().text = "Missed!";

    // 2. Destroy the missed object

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!


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)
	if (misses > maxMisses)
		if (score > highScore)
			highScore = score;

Then fill in the ChangeState(State state) method:

public void ChangeState(State state)
	gameState = state;
	if (state == State.Menu)
	if (state == State.Level)
		score = 0;
		misses = 0;

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<GameManager>().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<GameManager>().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.

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<TextMeshProUGUI>();
    highscore.text = gameManager.GetComponent<GameManager>().highScore.ToString();

    // 2. 
    TextMeshProUGUI score = scoreText.GetComponent<TextMeshProUGUI>();
    score.text = gameManager.GetComponent<GameManager>().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.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.