Tutorial written by Amanda End and Circuit Stream.

Welcome to Section Three of the HTC Vive Unity Virtual Reality Development Tutorial. In this section, we’re going to learn how to create VR applications that use input from Vive controller. We’ll take the interactable classes we made in our previous segment and expand on them to create new types of interactions.

We’re now able to pick up and throw an object – but what if I wanted to build a different script whose methods our controller could trigger? You could achieve this several ways, including setting up an event system. In this lesson we’ll instead expand on what we already have by creating an interactable item base class that our controller script can access. Therefore, we have access to any child scripts that we want to create.

We have two scripts: the controller input script and the interactable object script. The controller input script directly calls the Pickup and Release methods on the interactable script. Since we want to create more methods that have similar methods, we’re going to create a middle man called a base class.

This base class, named VRInteractableObject, will be the parent of any class that we create to receive input from the controller, just like the pickup script we created earlier. This script will have several methods on it. Iinstead of calling them Pickup and Release, we’re going to call them something more general, like ButtonPressDown and ButtonPressUp. We can then override those in our child classes to do various things like our previously covered pickup and release methods, and also things like fire gun or push button methods.

Script Setup

 
You can either start with shiny new scripts or rename the scripts we just built and expand from there. As previously mentioned, we need three scripts for this system:

  • The script that will be attached to the controller (I named mine VRControllerInput)
  • The base class for our pickup scripts (I named mine VRInteractableObject)
  • Our parented pickup script (I named mine VRIO_Parented)

You can leave them all blank for now, but it’s good to get them created and or named properly before we start, since they reference each other.

Base Class for Interactables

 

HTC Vive Expanding Interactables Base Class

 

As previously mentioned, I want our child classes to do more than just pickup and release. I want them to be to be triggered by multiple buttons. Starting with two methods: ButtonPressDown and ButtonPressUp. Be sure that your method declarations include the virtual keyword in them, as they are below. I’m also making the button’s ID and the controller input script parameters for these, as that is information our interactable scripts will probably need.

If you aren’t familiar with inheritance or base classes, the advantage to creating this is so that our controller input method doesn’t have to look for a bunch of different interactable scripts and call specific methods based on the script we find. For example, if we had a pickup script (like our parented one above) and say a button script that turned off the lights when you pulled the trigger, your controller script would have to look for each of those scripts, and then act accordingly depending on which one it found.

Setting Up for Optional Parameters

 
For those of you who are familiar and end up wondering why I didn’t go with an interface instead of a base class. I did consider it, especially since all of the base class methods in this setup end up being blank. But I liked the flexibility of optional parameters. I wanted to leave the option open to add logic into the base class if it came up later. However, I did consider it, and it’d probably be a decent path to take if you prefer them over a base class.

We want all of our interactable scripts to have the exact same methods, for example, ButtonPressDown and ButtonPressUp. Then just use them differently in each class, then it shouldn’t matter to the controller input script which kind of script it is. And that is what the base class is for. Instead of searching for all of your scripts, you just search for their base class.

More On Our Scripts

 
No matter what the script is, it will be returned when you use GetComponent() as long as it is a child (or derived) class of the VRInteractableObject class. Then, once you have a reference to that script, you can call any method that is in the base class. But, if that method is virtual and there is the same method in the child class that is set to override, the method on the child class will be called instead of the method on the base class.

///
/// Called when button is pressed down while controller is inside object
///

/// public virtual void ButtonPressDown(EVRButtonId button, VRControllerInput controller) { //Empty. Overriden method only. } ///

/// Called when button is released after an object has been "grabbed".
///

/// public virtual void ButtonPressUp(EVRButtonId button, VRControllerInput controller) { //Empty. Overriden method only. }

Now, just because the base class’s method doesn’t get called, doesn’t mean that code can’t be executed. In the overriding child method, you can call base.ButtonPressDown() to call whatever code is in the base class. If you wanted something to happen for every child class, instead of copying it to every single one. You could put it in the base class and then call that code from your child method.

And with that, our base class is completed! A lot of explanation for such a little bit of code. If you’re still confused by base classes, I would recommend looking up Inheritance for C#. It is a very useful tool to understand, and there are a lot of things like properties and abstract classes and interfaces! We didn’t cover abstract classes and interfaces, but they are useful in creating various mechanics for virtual reality in Unity.

The Controller Input Script

 
Now that we have our Interactable base class, we’re going to move on to our controller script. In our previous lesson with the pickup script, we were only really watching for the trigger button to be pressed and released. We want to expand that to handle multiple buttons, so we can work with more than just the trigger. We would need to be able to do so in order to pull off something like a gun. Which, we would want to pick up using the grip buttons and fire with trigger button.

HTC Vive gun firing

 

If you created a new script, you might notice that a lot of the code below is the same as our old controller input script, so you can copy and paste if you want that as a starting point, or you can just write everything from scratch. However, I won’t be going over the stuff we already discussed.

List to Dictionary

 
We still want to keep a list of all the objects we have a button “down” on so we can trigger the “release” methods. We need a way to store them in separate lists for each button. We’ll know which button release is tied to which interactable script. The way we’re going to implement this is to have a Dictionary with keys being the button enum, and the values being a list of interactable objects (like we had before). This way, we have a list for each button, and can ask for a list of interactable objects based on the button in question.

//Should only ever be one, but just in case one gets stuck
protected Dictionary<EVRButtonId, List> pressDownObjects;

//Controller References
protected SteamVR_TrackedObject trackedObj;
public SteamVR_Controller.Device device
{
	get
	{
		return SteamVR_Controller.Input((int)trackedObj.index);
	}
}

public delegate void TouchpadPress();
public static event TouchpadPress OnTouchpadPress;

void Awake()
{
	trackedObj = GetComponent();

	//Instantiate lists
	pressDownObjects = new Dictionary<EVRButtonId, List>();
}

It has been pointed out for me that using an enum as a key for Dictionaries can lead to some memory issues, as discussed here. I’m going to leave it as an enum in this course for simplicity’s sake, but if you’re looking for optimizations, this may be worth reading into.

Watch for Multiple Buttons

 

Expanding Interactables Controller Buttons

 

We want to use device.GetPressDown() to check for multiple buttons instead of just the trigger. First we’re going to create a list of buttons that we want to track.

protected List buttonsTracked;

void Awake()
{
	trackedObj = GetComponent();

	//Instantiate lists
	pressDownObjects = new Dictionary<EVRButtonId, List>();

	//List the buttons you send inputs to the controller for
	buttonsTracked = new List()
	{
		EVRButtonId.k_EButton_SteamVR_Trigger,
		EVRButtonId.k_EButton_Grip
	};
}

Then, in OnTriggerStay, we’re going to iterate through that list and check if any of those buttons have been pressed. After finding one that is pressed, we don’t want to just add the reference to our interactable objects. Instead, we want to add it to a list that is specific to that button, stored in our new dictionary. So we’re going to add our reference to the list in the dictionary that is keyed to our button (checking if it exists, first, and creating it if not).

void OnTriggerStay(Collider collider)
{
	//If rigidbody's object has interactable item scripts, iterate through them
	VRInteractableObject interactable = collider.GetComponent();
	if (interactable != null)
	{
		//Check through all desried buttons to see if any are pressed
		for (int b = 0; b < buttonsTracked.Count; b++)
		{
			//If a tracked button is pressed
			EVRButtonId button = buttonsTracked[b];
			if (device.GetPressDown(button))
			{
				//Send button press through to interactable script
				interactable.ButtonPressDown(button, this);

				//Add interactable script to a dictionary flagging it to recieve notice
				//when that same button is released
				if (!pressDownObjects.ContainsKey(button))
					pressDownObjects.Add(button, new List());

				pressDownObjects[button].Add(interactable);
			}
		}
	}
}

We’re also changing our interactable check to look for our new base class (VRInteractableObject) instead of our pickup script, and changing the method we’re calling from Pickup to ButtonPressDown.

Multiple Interactable object scripts

 
In the last step, we were only looking for one interactable script on the object. Since we want to create multiple scripts to go on one object (for example, the gun we’re creating later will have a pickup script and a gun controller script). We’re going to expand a little bit further on this method. By getting a list of all interactables using GetComponents instead of GetComponent and trigger the button methods for each one.

void OnTriggerStay(Collider collider)
{
	//If rigidbody's object has interactable item scripts, iterate through them
	VRInteractableObject[] interactables = collider.GetComponentsInChildren();
	for (int i = 0; i < interactables.Length; i++)
	{
		VRInteractableObject interactable = interactables[i];
		for (int b = 0; b < buttonsTracked.Count; b++)
		{
			//If a tracked button is pressed
			EVRButtonId button = buttonsTracked[b];
			if (device.GetPressDown(button))
			{
				//Send button press through to interactable script
				interactable.ButtonPressDown(button, this);

				//Add interactable script to a dictionary flagging it to recieve notice
				//when that same button is released
				if (!pressDownObjects.ContainsKey(button))
					pressDownObjects.Add(button, new List());

				pressDownObjects[button].Add(interactable);
			}
		}
	}
}

 

Using the attachedRigidBody

 
Currently, we’re looking for our interactable script on the same Game Object as our collider. However, they may not always be located in the same place. If, for example, we have a complex object with multiple colliders, we would want to put our interactable script somewhere higher in the hierarchy. All colliders report to the same script. Let’s look at our gun example again, below you will see that the gun is made up of three primitives. If we want a close fitting collider, we’re going to have to use three box colliders on each of these primitives instead of one box collider. However, we only want one script controlling our gun (otherwise when we fired, we’d fire three times!).

Expanding Interactables Gun Colliders Vive

 

Luckily, Unity already has a system that we can take advantage of for this. Each collider reports to a RigidBody. If there isn’t a RigidBody component on the same GameObject as the collider, it will travel up the hierarchy and report to the first one it finds. This way, multiple colliders can be reporting information to the same RigidBody. We’re going to put our script in the same place as the rigid body (somewhere higher up the hierarchy – in the above example, on the “Gun” GameObject). And, then use Collider.attachedRigidBody to find our RigidBody, and subsequently, our interactable script.

void OnTriggerStay(Collider collider)
{
	//If collider has a rigid body to report to
	if (collider.attachedRigidbody != null)
	{
		//If rigidbody's object has interactable item scripts, iterate through them
		VRInteractableObject[] interactables = collider.attachedRigidbody.GetComponents();
		for (int i = 0; i < interactables.Length; i++)
		{
			VRInteractableObject interactable = interactables[i];
			for (int b = 0; b < buttonsTracked.Count; b++)
			{
				//If a tracked button is pressed
				EVRButtonId button = buttonsTracked[b];
				if (device.GetPressDown(button))
				{ 
					//Send button press through to interactable script
					interactable.ButtonPressDown(button, this);

					//Add interactable script to a dictionary flagging it to recieve notice
					//when that same button is released
					if (!pressDownObjects.ContainsKey(button))
						pressDownObjects.Add(button, new List());

					pressDownObjects[button].Add(interactable);
				}
			}
		}
	}
}

 

Using Our List of Tracked Interactables

 
Okay – one last problem to overcome. If your controller is inside multiple colliders that all report to the same rigid body, then the interactable class with the rigid body is going to get multiple calls per press. You can overcome this by making sure that your list of tracked interactable scripts doesn’t already contain the script we’ve found. This will ensure that it has not already been notified of the button press.

void OnTriggerStay(Collider collider)
{
	//If collider has a rigid body to report to
	if (collider.attachedRigidbody != null)
	{
		//If rigidbody's object has interactable item scripts, iterate through them
		VRInteractableObject[] interactables = collider.attachedRigidbody.GetComponents();
		for (int i = 0; i < interactables.Length; i++)
		{
			VRInteractableObject interactable = interactables[i];
			for (int b = 0; b < buttonsTracked.Count; b++)
			{
				//If a tracked button is pressed
				EVRButtonId button = buttonsTracked[b];
				if (device.GetPressDown(button))
				{
					//If we haven't already sent the button press message to this interactable
					//Safeguard against objects that have multiple colliders for one interactable script
					if (!pressDownObjects.ContainsKey(button) || !pressDownObjects[button].Contains(interactable))
					{
						//Send button press through to interactable script
						interactable.ButtonPressDown(button, this);

						//Add interactable script to a dictionary flagging it to recieve notice
						//when that same button is released
						if (!pressDownObjects.ContainsKey(button))
							pressDownObjects.Add(button, new List());

						pressDownObjects[button].Add(interactable);
					}
				}
			}
		}
	}
}

We also need to make sure that list even exists by checking if pressDownObjects contains the button’s key. Before we make sure that the button’s list contains the interactable – otherwise we’re likely to get a NullReferenceException.

Update the Release Method

 
For release, we need to make a couple of the same updates that we made to the pickup method.

void Update()
{
	//Check through all desired buttons to see if any have been released
	EVRButtonId[] pressKeys = pressDownObjects.Keys.ToArray();
	for (int i = 0; i < pressKeys.Length; i++)
	{
		//If tracked button is released
		if (device.GetPressUp(pressKeys[i]))
		{
			//Get all tracked objects in that button's "pressed" list
			List releaseObjects = pressDownObjects[pressKeys[i]];
			for (int j = 0; j < releaseObjects.Count; j++)
			{
				//Send button release through to interactable script
				releaseObjects[j].ButtonPressUp(pressKeys[i], this);
			}

			//Clear 
			pressDownObjects[pressKeys[i]].Clear();
		}
	}
}

So we’re iterating through that list of buttons we used before, and checking GetPressUp for each of them. If the button was released, we’re going to use that button to get the list of interactables from the dictionary that are waiting for the ButtonPressUp method from that button, and calling it. Then we’re clearing the list.

The Pickup Script

 
Luckily, pickup script won’t need a lot of updates to get it working again.
Previously, our pickup script inherited from MonoBehaviour, but now we want it to inherit from our VRInteractableObject base class.

public class VRIO_Parented : VRInteractableObject
{

 

The Right Button

 
Since we’re also now triggering ButtonPressDown for any button, we want to make sure that it’s the trigger button being used. So let’s make a variable to store the trigger button in so we can check. We can also make it be public so that if you want to change which button to use for pickup, you can later in the inspector.

public class VRIO_Parented : VRInteractableObject
{
	public EVRButtonId pickupButton = EVRButtonId.k_EButton_SteamVR_Trigger;

 

New Methods

 
Utilize your new methods, we need to create ButtonPressDown and ButtonPressUp methods that are marked with the override keyword. In them, we’re checking to make sure that the right button was pressed. Then, calling our old Pickup and Release methods that should exist in the same class.

public override void ButtonPressDown(EVRButtonId button, VRControllerInput controller)
{
	//If pickup button was pressed
	if (button == pickupButton)
		Pickup(controller);
}

public override void ButtonPressUp(EVRButtonId button, VRControllerInput controller)
{
	//If pickup button was released
	if (button == pickupButton)
		Release(controller);
}

That’s all we have to do to get our pickup script working with our new upgraded system! In the next section of our introduction to VR development for the HTC Vive. We’ll learn how to make a VR game in Unity with a Button, Lever and Gun!

For structured Unity training, checkout our courses page. It’s the best Unity course online for getting 1 on 1 help creating a virtual reality or augmented reality application.

Previously: Pickup Interactions.
Next: Interactable Examples