Balloon Inflator

Tutorial

·

intermediate

·

+10XP

·

60 mins

·

Unity Technologies

Balloon Inflator

The next object you’ll work on – the balloon inflator – will adjust the size of the balloon based on the precise trigger press value. The harder the trigger is pressed, the more the balloon will inflate. In order to accomplish this more complex, unique interaction, you will need to dive deeper into the API and its documentation.

In this tutorial, you will reference online documentation and existing code samples to program a complex, custom VR interaction.

1. Overview

When you were programming the scanner, you used the API to trigger functionality on single events, like when the object was selected or when the trigger was pressed.

In this tutorial, you are going to create more controlled interactions and events by detecting and responding to the pressure value of a trigger press. You’ll use this precise value to make a balloon inflator object, which makes a balloon grow and shrink along with the trigger pressure.

It will look something like this:

2. Set up a basic grabbable object

We’ve created a balloon inflator Prefab for you in the project, which is just a simple mesh Prefab. Your task is to get the inflating features working.

Before you worry about any fancy functionality, follow the steps below to give it basic grabbable properties:

1. From the _VRPathway > Assets > Prefabs folder, drag the balloon inflator prefab into the scene.

2. In the _VRPathway > Scripts folder, create a new C# script called “BalloonInflator.cs”. Then attach the script to the Inflator GameObject and open the script.

3. Just like before, you need to make sure that the script has access to the XR Interaction Toolkit API. Add “UnityEngine.XR.Interaction.Tooklit” to the list of namespaces.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;  // add this line

4. Replace Monobehaviour with XRGrabInteractable to inherit from that class and get all the default grabbable object behaviors.

public class BalloonInflator : XRGrabInteractable

Test your app and grab the balloon inflator. It should now behave like a typical grabbable object, allowing you to pick it up and put it down.

3. Instantiate and destroy the balloon

Before you code the inflating feature, you first need to make the balloon appear and disappear when the inflator is picked up and dropped, respectively. To do this, complete the following steps:

1. In your script, under a new “Balloon Data” header, then create two new public variables: one for the point where this balloon GameObject will appear on the inflator, and one for the balloon itself.

[Header("Balloon Data")]
public Transform attachPoint;
public Balloon balloonPrefab;

We’ve provided you with a simple Balloon Prefab and script that makes it float upward and has a “Detach” method.

2. Save your script, go back to the Unity Editor, and assign those two new variables in the Inspector. You’ll need to drag the attach point from the Prefab in the Hierarchy, and the balloon Prefab from the Project Explorer window.

3. You will need more than one balloon in your scene. To enable this, create a new private variable to hold new instances of the balloon Prefab.

[Header("Balloon Data")]
public Transform attachPoint;
public Balloon balloonPrefab;

private Balloon m_BalloonInstance; // new variable

For now, you are just going to add some basic functionality to make sure everything is set up properly. Let’s instantiate the balloon at the attach point when the inflator is picked up.

4. Override the OnSelectEntered method and use the Instantiate method to create a new instance of the balloon. You used this same technique to open the scanner screen when it was picked up. Remember to also call the base method!

protected override void OnSelectEntered(SelectEnterEventArgs args)
{
  base.OnSelectEntered(args);
      
  m_BalloonInstance = Instantiate(balloonPrefab, attachPoint);
}

Next, make the balloon disappear again when the inflator is dropped.

5. Override the OnSelectExited method and use the Destroy method to remove the balloon instance.

protected override void OnSelectExited(SelectExitEventArgs args)
{
  base.OnSelectExited(args);
  Destroy(m_BalloonInstance.gameObject);
}

Test your app and notice that the basic functionality now works. The small balloon appears when the inflator is picked up and disappears when it’s dropped.

In the next step, you’ll move onto more advanced functionality.

4. Plan the approach to your code

The task here is to inflate the balloon as the trigger is pressed, so the next thing you need is a reference to the controller that picked up the balloon inflator.

You can’t just detect a trigger press on any controller; it has to be on the controller that actually picked up the object.

So in pseudo-code, you want something like this:

  • When the inflator object is selected, detect which controller has picked it up: left or right.
  • Every frame while the object is selected, set the scale of the balloon to be proportional to the pressed value of the trigger on that specific controller.

With this code, the harder the trigger is pressed, the bigger the balloon will be.

First, let’s investigate how we can detect which controller is activated.

Search the documentation for the OnSelectEntered method. Notice that the one parameter passed to this method, called “args”, is described as “Event data containing the Interactor that is initiating the selection.” That means that when the object is picked up, this parameter can tell us which interactor initiated the selection.

Next, you need a way to change the balloon’s scale based on the trigger press. In XRI 3.x, this input comes from the Interactor that grabbed the object rather than a controller class. When the inflator is selected, you can cache the interactor as an XRBaseInputInteractor. This interactor has an Activate Input reader, and calling activateInput.ReadValue() gives you a float between 0 and 1 that represents how far the trigger is pressed. You can use that value directly to drive the balloon’s scale.

If you want to see all of this code in action, you can check out the InteractionAnimator script, which is attached to the megaphone in the example scene. This script controls how the trigger of the megaphone slowly moves in and out along with the controller trigger press.

5. Get a reference to the correct controller

Now that you understand the approach to the code, follow the steps below to first detect which controller actually picked up the inflator:

1. Create a member variable for the interactor.

This will give you access to the input values you need, including the trigger (Activate Input).

private XRBaseInputInteractor m_Controller;

2. In the OnSelectEntered method, declare a new local var controllerInteractor. To try and get access to that interactor, enter “args. “ and read through the available options from the autocomplete menu. You will see that the interactorObject is available. Use the code below to assign your variable to that value.

protected override void OnSelectEntered(SelectEnterEventArgs args)
{
    base.OnSelectEntered(args);
    // Spawn the balloon
    m_BalloonInstance = Instantiate(balloonPrefab, attachPoint);
    // Save the grabbing interactor
    m_Controller = args.interactorObject // add this line
}

If you hover over interactorObject, you’ll see that this is of type IXRSelectInteractor, which is actually an Interface. You need access to the actual controller interactor. By casting it, you turn it into the real type we need (XRBaseInputInteractor), which gives you access to trigger input and haptics.

3. Use the as operator to convert the Interface to the compatible type: XRBaseControllerInteractor.

m_Controller = args.interactorObject as XRBaseInputInteractor; 
// edit this line

4. Add one more line to set your m_Controller variable to the actual controller using the interactor.

var controllerInteractor = args.interactorObject as XRBaseControllerInteractor;
m_Controller = controllerInteractor.xrController; // add this line

If you hover over “xrController”, you’ll see that it is of type XRBaseController, which “interprets feature values on a tracked input controller device into XR Interaction states.” That sounds like exactly what we want!

5. To test this, you can send a vibration (haptic impulse) to the controller that grabbed the inflator. Only that hand should buzz. You can also log that m_Controller value to the console using the code below:

protected override void OnSelectEntered(SelectEnterEventArgs args)
{
    base.OnSelectEntered(args);
    m_BalloonInstance = Instantiate(balloonPrefab, attachPoint);
    m_Controller = args.interactorObject as XRBaseInputInteractor;
    m_Controller = controllerInteractor.xrController;
    m_Controller.SendHapticImpulse(1f, 0.5f); // add this line
    Debug.Log(m_Controller);// or add this line
}

If you have successfully referenced the controller that picked up the object, you will feel it only in that hand. If you used a debug message, you’ll see if it’s the LeftHand or RightHand.

6. Scale the balloon based on the trigger value

Now that you have a reference to the correct interactor, the last job is to make the balloon change size when the trigger is pressed. To do this, you’ll override the ProcessInteractable method, which runs every frame while the object exists. Start by creating the method and calling the base version inside it:

1. Override the ProcessInteractable method and call the base method.

public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
   base.ProcessInteractable(updatePhase);
}

2. To avoid crashes, you need to check that the object is actually selected and that the controller is not null. Add the following if-statement to do that:

public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
{
    base.ProcessInteractable(updatePhase);
    if (isSelected && m_Controller != null) // add this if-statement
    {
    }
}

To scale the balloon as the trigger is pressed, you can Lerp between the minimum and maximum value of the trigger press. Lerp is short for Linear interpolation, which is very helpful for smoothly going between values. If you’ve never used this function before, you might want to review its documentation.

3. Set the balloon instance’s local scale to Lerp between two values, proportional to the activeInteractionState value of the trigger.

if (isSelected && m_Controller != null)
{
   m_BalloonInstance.transform.localScale = Vector3.one * Mathf.Lerp(
       1.0f, 4.0f, m_Controller.activateInput.ReadValue()); //add this line
}

The activateInput.ReadValue returns a float between 0 and 1, depending on how much the trigger is pressed.

Your balloon inflator should now work perfectly!

7. More things to try

If you want to further develop your skills, explore new concepts, or improve your project, check out some optional activities below. Each one is tagged as either [Easy], [Medium], or [Difficult] so you can choose the level of challenge.

These activities are entirely optional, so if you’re not interested, no problem – just skip this step.

We do recommend attempting at least one of them in order to get the most out of this learning experience. Good luck!

[Easy] Add dynamic haptics as you’re inflating and deflating

You could trigger constant haptics while the balloon is inflating or deflating. For an additional challenge, you could also change the haptic feedback depending on how quickly the trigger is pressed or released: more intense haptics for quicker inflation/deflation and more subtle haptics for slower inflation/deflation.

[Medium] Shake off the balloon

To release the balloon, you could allow the user to shake their hand – and if they shake hard enough, the balloon will float into the air. As a hint for how to do this, you could look at the OnVelocity script from the Create with VR script library.

[Difficult] Animate the trigger

If you look carefully at the other example objects, such as the megaphone or the launcher, you’ll notice that their triggers animate as they’re pressed by the user. This uses very similar code to the code you wrote for detecting how hard the trigger was pressed to control the size of the balloon. This is accomplished using a Playable Director component and a custom Interaction Animator script. See if you can replicate this functionality on your balloon inflator.

8. Next steps

You have successfully used the API to create your own interactions, and refine those interactions by detecting the active controller and adding functionality based on a trigger press value.

That means you are officially done with this example project! There’s much more to explore and tinker with in this scene if you want, including some ideas below.

When you’re ready to move on, in the next project you will apply what you learned here to a new project brief, where you must create functionality for part of an escape room.

Complete this tutorial