Search⌘ K
AI Features

Interaction Advanced II: Custom State Management

Explore how to implement advanced interaction techniques in Unity VR development by managing custom states for interactable objects. Learn to add immersive audio, haptic feedback, and dynamic visual effects to create engaging VR experiences with Meta Quest 2.

Interactable custom state management

In this lesson, we’ll focus on adding audio to our saber. To make the experience more immersive, we want to play two different sounds: one while the saber glides toward the user and the other when the user finally grabs it. To achieve this, we’ll need to maintain different states for the Interactable, because simply relying on Interactable events alone may not suffice. So, let’s dive in and learn how to create an even more engaging experience with audio!

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class SaberEventHandler : MonoBehaviour
{
public GameObject HoverVFX, SelectVFX;
public AudioSource SaberGlidingAudio, SaberGrabAudio;
public Transform HandleTransform;
public float GrabRange;
private Transform InteractorTransform;
private bool StateIsGliding, StateInHand;
private void Start()
{
InteractorScript = null;
StateIsGliding = false;
StateInHand = false;
}
private void Update()
{
if (StateIsGliding)
{
if ((InteractorTransform.position - HandleTransform.position).magnitude <= GrabRange)
{
SaberGlidingAudio.Stop();
SaberGrabAudio.Play();
StateIsGliding = false;
StateInHand = true;
}
}
}
public void OnSelectEntered(SelectEnterEventArgs args)
{
InteractorTransform = args.interactableObject.transform;
StateIsGliding = true;
StateInHand = false;
SaberGlidingAudio.Play();
}
public void OnSelectExited(SelectExitEventArgs args)
{
InteractorTransform = null;
StateIsGliding = false;
StateInHand = false;
}
public void SpawnHoverVFX()
{
Instantiate(HoverVFX, transform).SetActive(true);
}
public void SpawnSelectVFX()
{
Instantiate(HoverVFX, transform).SetActive(true);
}
}

To see this in action, attach the OnSelectEntered and OnSelectExited event handlers with the “First Select Entered” and “Last Select Exited” events, respectively.

You can hear the audios triggered on gliding and grabbing in the video below:

Task: Remove line visuals when the saber is grabbed

Lastly, we’d also want that once we’ve selected the saber, the line visuals turn off, but they turn back on whenever we drop the saber.

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class SaberEventHandler : MonoBehaviour
{
public GameObject HoverVFX, SelectVFX;
public AudioSource SaberGlidingAudio, SaberGrabAudio;
public Transform HandleTransform;
private XRInteractorLineVisual LineVisualScript;
public void OnSelectEntered(SelectEnterEventArgs args)
{
LineVisualScript = args.interactorObject.transform.GetComponentInChildren<XRInteractorLineVisual>();
LineVisualScript.enabled = false;
}
public void OnSelectExited(SelectExitEventArgs args)
{
LineVisualScript = args.interactorObject.transform.GetComponentInChildren<XRInteractorLineVisual>();
LineVisualScript.enabled = true;
}
public void SpawnHoverVFX(HoverEnterEventArgs args)
{
Instantiate(HoverVFX, HandleTransform).SetActive(true);
}
public void SpawnSelectVFX()
{
Instantiate(SelectVFX, HandleTransform).SetActive(true);
}
}
  • In the OnSelectEntered method, invoke the GetComponent method twice (once for XRInteractorLineVisual and once for LineRenderer). Then, simply call SetActive(false) over these components to deactivate them.

  • In the OnSelectExited method, invoke the GetComponent method twice (once for XRInteractorLineVisual and once for LineRenderer). Then, simply call SetActive(true) over these components to enable them.

The animation below shows what the saber looks like in action:

Haptic feedback via scripting

Apart from initiating haptic feedback via the “Haptic Events” interface in the Interactor, we can also use the SendHapticImpulse method defined over the ActionBasedController.

SendHapticImpulse(float amplitude, float duration)
Haptic feedback method

Task: Add haptic feedback on grab

Add a minor haptic feedback when the saber comes into the user’s hand.

Note: You need a reference to the ActionBasedController object of the controller that’s currently interacting with the Interactable object.

Task: Switch saber Movement Type

As discussed earlier, we’d like our saber’s Movement Type attribute to be changed to Kinematic from Velocity Tracking. Doing this would result in a much smoother movement of the saber when the user has grabbed it. Try implementing this in the SaberEventHandler script.

We’ve attached the complete code for the SaberEventHandler script below for your convenience.

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class SaberEventHandler : MonoBehaviour
{
public GameObject HoverVFX, SelectVFX;
public AudioSource SaberGlidingAudio, SaberGrabAudio;
public Transform HandleTransform;
public XRGrabInteractable InteractableScript;
public float GrabRange;
private ActionBasedController InteractorScript;
private Transform InteractorTransform;
private bool StateIsGliding, StateInHand;
private XRInteractorLineVisual LineVisualScript;
private void Start()
{
InteractorScript = null;
StateIsGliding = false;
StateInHand = false;
}
private void Update()
{
if (StateIsGliding)
{
if ((InteractorTransform.position - HandleTransform.position).magnitude <= GrabRange)
{
InteractableScript.movementType = XRBaseInteractable.MovementType.Kinematic;
SaberGlidingAudio.Stop();
SaberGrabAudio.Play();
InteractorScript.SendHapticImpulse(0.5f, 0.15f);
StateIsGliding = false;
StateInHand = true;
}
}
}
public void OnSelectEntered(SelectEnterEventArgs args)
{
InteractorTransform = args.interactableObject.transform;
InteractorScript = InteractorTransform.GetComponent<ActionBasedController>();
LineVisualScript = args.interactorObject.transform.GetComponentInChildren<XRInteractorLineVisual>();
LineVisualScript.enabled = false;
StateIsGliding = true;
StateInHand = false;
SaberGlidingAudio.Play();
}
public void OnSelectExited(SelectExitEventArgs args)
{
InteractableScript.movementType = XRBaseInteractable.MovementType.VelocityTracking;
InteractableScript.velocityDamping = 1;
InteractableScript.velocityScale = 0.1f;
LineVisualScript = args.interactorObject.transform.GetComponentInChildren<XRInteractorLineVisual>();
LineVisualScript.enabled = true;
InteractorTransform = null;
InteractorScript = null;
StateIsGliding = false;
StateInHand = false;
}
public void SpawnHoverVFX()
{
Instantiate(HoverVFX, transform).SetActive(true);
}
public void SpawnSelectVFX()
{
Instantiate(HoverVFX, transform).SetActive(true);
}
}

Interactable events: Activate

The Interactable events for the Activate state are listed below in the table:

Interactable Event Types

Event Type

Description

Args Type

Activated

This event is fired when the selecting Interactor activates the Interactable game object, e.g., activating a saber.

ActivateEventArgs

Deactivated

This event is fired when the selecting Interactor deactivates the Interactable.

DeactivateEventArgs

We’ll use the Activate state in two different ways for our game. In the case of the saber, we’ll sheathe and unsheathe it, whereas, for the orb, we’ll fire a projectile.

However, before implementing this, let’s first adjust the Handle Transform of both our Interactables. To do this, follow the steps below:

  1. Simply add an empty GameObject under both the saber and the orb.

  2. Position and orient them as you see fit.

  3. Attach these GameObjects with the Handle Transform attribute of their respective XR Grab Interactable scripts.

Add effects via Activate events

We’ve provided all the necessary components for the activation and deactivation of the saber in the package below. Feel free to use custom components if you like.

SaberAnimations.unitypackage

Task: Activate saber

In this task, use the Activated event to handle saber activation. You need to ensure the following things:

  • Saber open animation is played.

  • Saber unsheathe sound is played.

  • Saber persist sound is played after the saber unsheathe sound has stopped playing.

Task: Deactivate saber

In this task, use the Deactivated event to handle saber deactivation. You need to ensure the following things:

  • Saber close animation is played.

  • Saber sheathe sound is played.

  • Saber persist sound is stopped.

VR Not Connected
Experience in VR
Connect your VR headset to get started.
Demo: saber activation/deactivation, onHover, and onSelect

In this lesson, we dove deep into the Interactable events: Hover, Select, and Activate. We also learned about custom state management. Congratulations on covering another milestone!