Tutorial written by Amanda End and Circuit Stream.

Welcome to Section Two of the HTC Vive Unity Virtual Reality Development Tutorial. In this section, we’re going to go over how to make virtual reality applications that use the HTC Vive controllers! We’ll create two scripts that will allow you to pick up and throw objects in your scene. We’ll cover the different ways you can accomplish this then deep dive into those methods.

 

Types of Pickup Scripts

 
There are lots of ways you could go about picking up objects in VR. We’ve boiled it down to three basic methods for us to go over. We’ll only be doing a deep dive into one of these methods, but we wanted to show you a couple other methods and their advantages. The code for these other methods will be available for you in the project if you’d like to look into them further.

Parenting the object

Simplest implementation – parenting object to hand on pickup. Its like supergluing the cube to your hand, as the cube cannot be moved from your hand.
Clips through immovable objects

 
Pushes around movable objects, but no differentiation made for mass. (Left cube has less mass)

 
If you have physics objects in scene that are constrained (like a joint), this implementation can break them.

 

Attach to hand with Joint

This is slightly more difficult to implement. It’s like taping the cube to your hand. The cube has some wiggle under strain, and can be broken if you apply a break force.

 
Object is left behind when you try to drag through immovable objects – but it can be a little ungraceful.
More graceful if you apply a break force.

 
Can differentiate between objects of different mass (Left cube has less mass) – cube sinks a little deeper into heavy cube before pushing, but it’s not a huge difference.

 
Break force can be used to make some objects too heavy to move.

 
Will not break constrained physics objects.

 
You can use break force to make a more realistic interaction with constrained physics objects

 

Use Physics with MoveToward + Velocity

This is similar to the method that NewtonVR uses, and similar in many ways to the jointed method. It is also the most complex implementation of the three. It’s like your hand and the cube are magnetized to each other.
Object is left behind when you try to drag through immovable objects – more gracefully than joints.

 
Differentiates between objects of different mass – can push around light cube more easily than heavy cube (Left cube has less mass).

 
Will not break constrained physics objects

 

Throwing Objects

 
For both parenting and jointed methods, you can throw an object by releasing the controller’s hold on it, and then applying the controller’s current velocity and angular velocity to the object. For the physics method, you can simply “let go” – since it already has velocity. We will cover this more next, when we do a deep dive into the parenting pickup script.

Parenting Pickup – Deep Dive

So let’s take a closer look at how to create and implement one of these methods of picking up objects. The parenting method is the simplest, so we’ll cover that one.

Preparing the Controllers

In order for our controllers to be able to interact with objects in the scene, we’re going to need to add colliders. The simplest way to do this is to just create sphere colliders on the Controller objects (Children of the [CameraRig] object) The controller’s IsTrigger property should be set to true.

 

Creating the Script that will go on Interactable Objects

 
We’re going to start with the scripts that you put on any object you want to have some sort of interaction with the controllers. You could put all of this logic on the script that you attach to the vive controllers. I find it’s more flexible to put it on the object and only receive input from the controller.

You could set up an event system on the controller that you could have objects subscribe to, but for now I’m going to do a slightly simpler and faster implementation.

Create the Interactable Item Script

Call it InteractableItem or VRInteractableItem or whatever makes the most sense to you – the one here is called SimpleExample_VRInteractableObject. In you new script, create class variables that keep track of the rigid body, the object’s original parent, and the object’s original kinematic state. Assign the rigid body on Awake, and the original kinematic state and parent. If the kinematic state or parent is likely to change during gameplay, you can assign them on pickup instead.

protected Rigidbody rigidBody; 
protected bool originalKinematicState;
protected Transform originalParent;

void Awake()
{
	rigidBody = GetComponent();

	//Capture object's original parent and kinematic state
	originalParent = transform.parent;
	originalKinematicState = rigidBody.isKinematic;
}

Create the Controller Input Script

We aren’t going to fill it out now, but you need to reference the script that we’re going to put on the controllers in the upcoming methods, so create a second script called ControllerInput or VRControllerInput or whatever makes sense. I’ll be calling mine SimpleExample_VRControllerInput.

Pickup Method

Back to the script that goes on the interactable item, we need a method for picking up the interactable object. It will accept our input script as an argument.

First, we’re setting the object to be kinematic so it is no longer affected by physics forces (like gravity or other objects) and will stay in our hand. Then, we’re setting the object’s parent to be the controller. You could just accept a gameobject here as an argument instead of the controller input script. Just did for consistency (you need it in the release method) in the example.

public void Pickup(SimpleExample_VRControllerInput controller)
{
	//Make object kinematic
	//(Not effected by physics, but still able to effect other objects with physics)
	rigidBody.isKinematic = true;

	//Parent object to hand
	transform.SetParent(controller.gameObject.transform);
}

Release Method

Now we need a method for releasing the object when the user lets go.

public void Release(SimpleExample_VRControllerInput controller)
{
	//Make sure the hand is still the parent. 
	//Could have been transferred to anothr hand.
	if (transform.parent == controller.gameObject.transform)
	{
		//TODO
	}

}

Make sure that the hand triggering the Release method is still the hand holding the object. If you grabbed the cube with your left hand, and then took it from your left hand with your right hand, you’d still have the pickup button on both hands pressed. When you release the button on your left hand, it would send a message to the object to drop, even though it is now in your right hand.

 
This check prevents the cube from falling from your right hand, because the left hand is not the hand holding the object.

public void Release(SimpleExample_VRControllerInput controller)
{
	//Make sure the hand is still the parent. 
	//Could have been transferred to anothr hand.
	if (transform.parent == controller.gameObject.transform)
	{
		//Return previous kinematic state
		rigidBody.isKinematic = originalKinematicState;

		//Set object's parent to its original parent
		if (originalParent != controller.gameObject.transform)
		{
			//Ensure original parent recorded wasn't somehow the controller (failsafe)
			transform.SetParent(originalParent);
		}
		else
		{
			transform.SetParent(null);
		}

		//Throw object
		rigidBody.velocity = controller.device.velocity;
		rigidBody.angularVelocity = controller.device.angularVelocity;
	}
}

Once we have that confirmed, we’re setting the rigid body back to its original kinematic state (just in case it started out as kinematic for whatever reason). Then we’re setting its parent back to the original parent. I put a small line here for making sure the original parent wasn’t the controller. It may not be necessary, but I’m being precautious. Last, we’re giving the object’s rigidbody the same force and angular velocity that the controller has when you let go, which will result in a nice throw.

Getting Input from the Controllers

 
Now that we have a script that we can attach to objects allowing them to be picked up and thrown, we need a way to trigger those methods. We know we want them to happen when the user is touching the object and then pressing a button. Next, we need to set up a script that can get that information from the controllers. We’re going back to that other script you created earlier, and we’re going to fill it out.

Access to the Device

First we need access to the information from the controller device. As discussed before, this is achieved by creating a Device property that gets device information from SteamVR_Controller upon request. The script is going to go on the same hand as the SteamVR_TrackedObject script, so we can just get it from the gameObject on Awake.

//Should only ever be one, but just in case
protected List heldObjects;

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

void Awake()
{
	//Instantiate lists
	trackedObj = GetComponent();
	heldObjects = new List();
}

Also, we’re creating a class variable to hold a reference to the object(s) we’re holding. This way, if you manage to get your controller inside two objects at once and pick them both up, it won’t break our logic.

Detect when touching Interactable Objects

Now we’re going to put some logic inside the OnTriggerStay method, which fires when the hand is inside something else with a collider.

void OnTriggerStay(Collider collider)
{
	//If object is an interactable item
	SimpleExample_VRInteractableObject interactable = collider.GetComponent();
	if (interactable != null)
	{
		//If trigger button is down
		if (device.GetPressDown(EVRButtonId.k_EButton_SteamVR_Trigger))
		{
			//Pick up object
			interactable.Pickup(this);
			heldObjects.Add(interactable);
		}
	}
}

First, we’re checking whether the object we’re touching is interactable. We’re achieving this by checking to see if it has the interactable script that we built attached to it. If it doesn’t we’re not going to do anything. Then we’re checking to see if the trigger button was pressed down on the object this frame. Be sure to use GetPressDown and not GetPress, since GetPressDown only fires on one frame, when the user first presses down. We don’t want this logic to return “true” every frame the user has the button pressed, just the first frame.

Once we’ve confirmed that the controller is touching an interactable object and that the trigger button is down, we’re getting a reference to the script that is on our interactable object and calling the Pickup method that we wrote earlier. We’re also adding that object to a list so we can remember to call it’s Release method when we let go of the trigger button.

Letting Go

We need to trigger the release method on the object when the trigger is released. We could do the same as before and check for GetPressUp in the OnTriggerStay method, but I’ve found that in some rare cases, the object gets parented while outside of the collider and so the OnTriggerStay method stops firing and the object gets stuck. So I prefer to collect the object(s) we’re holding, and then any time after that when the trigger is released, we trigger the Release method on every object in that list. Instead of putting the code inside the OnTriggerStay, I’m going to put it inside the Update function so it’s constantly checking for the trigger to be released as long as there’s something in the list.

void Update()
{
	if (heldObjects.Count > 0)
	{
		//If trigger is releasee
		if (device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Trigger))
		{
			//Release any held objects
			for (int i = 0; i < heldObjects.Count; i++)
			{
				heldObjects[i].Release(this);
			}
			heldObjects = new List();
		}
	}
}

So in this code we’re checking to see if there are any held objects. If there are, we’re checking to see if the trigger button was released on this frame. If so, we’re iterating through each of the held objects and calling their Release method. Then we’re clearing the list so we only call that method once per object.

And there you have it! A simple interaction script that allows you to pick up and throw objects around!

See you in the next section of our introduction to virtual reality development for Unity and the HTC Vive!

Previously: Introduction and Setup.
Next Up: Expanding the Interactables Class.

If you’re new to Unity and want to learn virtual reality development, visit our courses page for structured Unity training and VR classes.