HTC Vive Warm-Up Tutorial

In this tutorial you will learn how to setup a Unity project that works with SteamVR and that can interface with an HTC Vive.


Download the Starter Unity Pack Here

Setting up a scene in Unity rendered on the Vive

Open Unity and start a new project.

Here you can download a starting Unity package with the asset of room, just to have an environment to start.

Go to Unity, Assets > Import Package > Custom Package... and select the one you just downloaded. Then click on Import.

You will see a new folder below the Assets one. Open it, then go to Prefabs. Drag the Room prefab into the scene. This is just to facilitate this tutorial, as this prefab only contains the rendering of a room, with a table and a few boxes.

Remove the "Directional Light" from the scene, as the Room prefabs contains lights.

Go the Assets Store tab in Unity, search and Download "SteamVR Plugin".

Once downloaded, click on Import. Make sure to import all the assests (click on "all", then on "import").

If you get the "API Update Required" message, click on "I made a backup. Go Ahead!".

Once imported you will see the SteamVR folder inside the Assets folder under the Project tab in Unity.

The SteamVR/Prefab folder contains the relevat assets that will allow you to render the camera and the Vive controllers in the scene.

Select the SteamVR prefab and drag it into the scene. This plug in handles a few things while playing, related to the interface with the SteamVR player menu.

Also drag the CameraRig prefab in the scene.

Remove the Main Camera that was already present in the Scene, as it will interfere with the CameraRig prefab.

Move the CameraRig where you would like the player to start. For example behind the table. To move the CameraRig, use the Transform propertin in the Inspector, othewise click "W" and the drag the arrows on the object.

Turn on the Controllers and hit Play! You should see the room rendering from the Vive, as well as the controllers.

Understanding the Controllers Input

In this part you will try to interact with the boxes in the scene. You want the controller to have the ability to grab objects. Create a new folder called "Scripts", and then right click in it and select Create C# Script. Call the script "ControllerGrabObject" and open it with your favorite text editor.
1
private SteamVR_TrackedObject trackedObj;
The "trackedObj" is the object being tracked: the Vive controller. Add the following to the update function:
1
2
3
4
void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}
In this way the trackedObj is set when Awake is called (at the beginning of the scene). In the Update function, add the code below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SteamVR_Controller.Device Controller = SteamVR_Controller.Input((int)trackedObj.index);

if (Controller.GetAxis() != Vector2.zero)
{
    Debug.Log(gameObject.name + Controller.GetAxis());
}

if (Controller.GetHairTriggerDown())
{
    Debug.Log(gameObject.name + " Trigger Press");
}

if (Controller.GetHairTriggerUp())
{
    Debug.Log(gameObject.name + " Trigger Release");
}

if (Controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Press");
}

if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Release");
}
In the code above, we first set the SteamVR_Controller.Device Controller, which allows us to control the input.

Then we check if the trackpad is touched, of if the trigger is pressed or released. If so, we print a debug message.

Select both controllers in the Scene (under CameraRig), then drag and drop the script in the Inspector. In this way the script gets attached to the controllers. Hit play and monitor the Console while you start playing with the controller.

Grabbing Objects

In this part of the tutorial you will understand how to grab objects from the scene.

First of all select both controllers in the Scene, then go in the Inspector, click on "Add Component", and search for Rigidbody. In the Rigidbody properties, check the "Is Kinematic" box, and not the "Use Gravity" one. We don't want to have any gravitational force applied on them.

In orded to have object collide with each other or to understand their intersection in Unity, the objects must have a collider component attached. As you have done for the Rigidbody one, serach and add the "Box Collider" component.

Hit Play and then click on the Scene tab. Select the Controller in the hierarchy and resize the collider box so that it wraps the end circular part of the controller only. This will allow us to have a boundary around the controller, that we can use to understand if other objects are crossing that boundary or not.

Now open the same script as before. Add this two additional variables just after the trackedObj one:
1
2
3
private SteamVR_TrackedObject trackedObj;
private GameObject collidingObject; 
private GameObject objectInHand;
"collidingObject" will store the object we are colliding with (if any), and "objectInHand" the object we have in our hands (if any).

Now we want to listen to the trigger. If we are intersecting with an object and the trigger is pressed, we want to grab that object. Add the following lines after the Awake method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void OnTriggerEnter(Collider collider)
{
    SetCollidingObject(collider);
}

public void OnTriggerStay(Collider collider)
{
    SetCollidingObject(collider);
}

public void OnTriggerExit(Collider collider)
{
    if (!collidingObject)
    {
        return;
    }

    collidingObject = null;
}

private void SetCollidingObject(Collider collider)
{
    if (collidingObject || !collider.GetComponent<Rigidbody>())
    {
        return;
    }
    collidingObject = collider.gameObject;
}
"OnTriggerEnter", "OnTriggerStay", "OnTriggerExit" are called automatically every time the collider collides with another one. You can refer to the Unity Documentation.

Now we need to add the code to actually grab an object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void GrabObject()
{
    objectInHand = collidingObject;
    collidingObject = null;

    var joint = AddFixedJoint();
    joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
}

private FixedJoint AddFixedJoint()
{
    FixedJoint fx = gameObject.AddComponent<FixedJoint>();
    fx.breakForce = 20000;
    fx.breakTorque = 20000;
    return fx;
}

private void ReleaseObject()
{
    if (GetComponent<FixedJoint>())
    {
        GetComponent<FixedJoint>().connectedBody = null;
        Destroy(GetComponent<FixedJoint>());

        objectInHand.GetComponent<Rigidbody>().velocity = Controller.velocity;
        objectInHand.GetComponent<Rigidbody>().angularVelocity = Controller.angularVelocity;
    }

    objectInHand = null;
}
Finally we can update the "Update" method to grab an object when the controller trigger is pressed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (Controller.GetHairTriggerDown())
{
    if (collidingObject)
    {
        GrabObject();
    }
}

if (Controller.GetHairTriggerUp())
{
    if (objectInHand)
    {
        ReleaseObject();
    }
}

Pointing to Objects with a Laser

In this part of the tutorial you create a laser pointer to point to objects in the Scene.

In the Prefab folder of the tutorial package you can find the "Laser" prefab.

Make a new script in the Scripts folder and name it "LaserPointer".

Write the following code:
1
2
3
4
5
6
7
8
9
10
11
private SteamVR_TrackedObject trackedObj;

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}

void Update()
{
    SteamVR_Controller.Device Controller = SteamVR_Controller.Input((int)trackedObj.index);
}
Now also add these variables just after the trackedObj:
public GameObject laserPrefab;
private GameObject laser;
private Transform laserTransform;
private Vector3 hitPoint; 
Add this method to show the laser:
1
2
3
4
5
6
7
private void ShowLaser(RaycastHit hit)
{
    laser.SetActive(true);
    laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, .5f);
    laserTransform.LookAt(hitPoint); 
    laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance);
}
This method takes a RaycastHit as a parameter because it contains the position of the hit and the distance it traveled.

In the Start method, add the following:
laser = Instantiate(laserPrefab);
laserTransform = laser.transform;
Finally, add this in the Update method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad))
{
    RaycastHit hit;

    if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))
    {
        hitPoint = hit.point;
        ShowLaser(hit);
    }
}
else 
{
    laser.SetActive(false);
}
Drag the script and drop it in the Inspector, with both controllers selected. You can see that one of the properties is the Laser Prefab. Drag the Laser prefab that you find in the Prefab folder, and drop it into that field.

Hit Play! You should see a red laser coming out of the controller when you click the trackpad.

Teleporting in the Scene

In this part of the tutorial you will learn how to teleport in the scene.

Open the LaserPointer script, and add the following variables at the beginning:
1
2
3
4
5
6
7
8
public Transform cameraRigTransform; 
public GameObject teleportReticlePrefab;
private GameObject reticle;
private Transform teleportReticleTransform; 
public Transform headTransform; 
public Vector3 teleportReticleOffset; 
public LayerMask teleportMask; 
private bool shouldTeleport; 
In general we want to teleport only to particular objects in the scene, not to all of them. Open the Room prefab in the Hierarchy, and click on the Floor prefab. In the Unity Inspector you will see a dropdown meanu called "Layer". Open it and click on "Add Layer". Write a new name for the Layer ("Can Teleport") and hit "Enter".

We want to be able to teleport to all objects that have the "Can Teleport" layer. Add this layer to the objects you want to be able to teleport to (table, bed, ...).

Then go back to the script. In the line "if (Physics.Raycast(trackedObj.transform.position...", add the "teleportMask" variable at the end, inside the "Physics.Raycast()" call. This will allow to Raycast only to objects with a specific teleportMask.

In the Update method, add:
1
2
3
reticle.SetActive(true);
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
shouldTeleport = true;
While still in the Update() method, find laser.SetActive(false); and add the following line underneath it:\
1
reticle.SetActive(false);
Also add this code to handle the teleporting:
1
2
3
4
5
6
7
8
private void Teleport()
{
    shouldTeleport = false;
    reticle.SetActive(false);
    Vector3 difference = cameraRigTransform.position - headTransform.position;
    difference.y = 0;
    cameraRigTransform.position = hitPoint + difference;
}
Add this inside Update, at the end:
1
2
3
4
if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport)
{
    Teleport();
}
And finally, add this in the Start method:
1
2
reticle = Instantiate(teleportReticlePrefab);
teleportReticleTransform = reticle.transform;
Go back to Unity and with both controllers selected drag the CameraRig, CameraHead and TeleportReticle in the appropriate files in the Laser Point script attached to the controllers.

Hit Play and test it!