Advanced iOS Summer Bundle

3 brand-new books on SwiftUI, Combine and Catalyst — $99.99 for a limited time!

HTC Vive Tutorial for Unity

Learn how to use the HTC Vive with Unity! Grab and throw objects, shoot lasers and teleport around an area.

4/5 7 Ratings

Version

  • C#4, Unity 2018.2, Unity

unity-htc-vive

Note: Eric Van de Kerckhove updated this tutorial for Unity 2018.3 and SteamVR 2.0.

The HTC Vive is a virtual reality headset developed by HTC and Valve Corporation. It lets you step into virtual worlds and experience it as yourself instead of through an on-screen avatar.

If you’re a Unity developer, making virtual reality games with the HTC Vive into your own games is easy — you might even say the HTC Vive and Unity are a match made in heaven.

In this HTC Vive Tutorial, you’ll learn how to integrate the HTC Vive into your own Unity games. Specifically, you’ll learn how to:

  • Download and configure SteamVR.
  • Handle the controller’s input.
  • Interact with physics objects in VR.
  • Make a laser pointer.
  • Teleport around an area.

At the end of this HTC Vive tutorial, you’ll have a sandbox that’s primed for further experimentation. It’s time to get started!

Note: Everyone reacts differently to movement and rotation while wearing a head-mounted display. If it’s your first time, go easy on yourself and take a break if you feel nauseous or uncomfortable. Most people get used to VR quickly. Don’t fret if you’re not feeling well the first few times — it’ll probably pass.

Getting Started

Before you dive into this tutorial, make sure you have the following:

Download the materials for the project using the Download Materials button at the top or bottom of this tutorial, unzip it somewhere and open the starter folder inside Unity. Look at the contents of RW in the Project window:

AssetFolders

Each folder serves as home base for specific assets:

  • Materials: Materials used for the scene including the blue bouncy balls.
  • Models: All the models.
  • Physics Materials: Bouncy ball physics material.
  • Prefabs: Loose object prefabs.
  • Scenes: The game scene is in here.
  • Scripts: All the scripts.
  • Textures: The single texture shared by all objects in the scene.

Open the Game scene. Look at the scene view and click play to try the game:

FirstSceneView

At the moment, not much is happening because there’s no VR rig in the scene yet. You’ll need to add SteamVR to the project to connect the Vive to Unity.

Setting Up SteamVR

The SteamVR SDK is an official library made by Valve that makes it easier to develop for all major VR headsets. It’s currently free on the Asset Store and supports the Oculus Rift and the HTC Vive to name a few.

Open the Asset Store by selecting Window > General > Asset Store in the top bar:

Once the store loads, type SteamVR in the search field at the top and click Enter. Scroll down a bit to see a selection of assets. Click SteamVR Plugin to open its store page:

Click Download and give it a moment. Once it’s done, click Import to open the package import dialog. Click Import at the bottom-right corner of this window to import the package:

ImportPackage

Note: At the end of the import, you may see the following message depending on your version of Unity:

ApiUpdate

Click the button that reads I Made a Backup to let the editor update and recompile the scripts.

You’ll get this window after a few seconds:

SteamVrSettings

This is part of the SteamVR plugin; it shows which editor settings you can improve to maximize performance and compatibility.

When you open a fresh project and import SteamVR, you’ll see quite a few entries. Since the starter project is already well optimized, the only recommendation here is to disable the resolution dialog.

Click Accept All to perform all of the recommended changes. Close the Asset Store and switch back to the Scene view. You’ll now have a new folder named SteamVR in your Project window:

Open the SteamVR folder and look at the folders inside. You’ll add some essential VR GameObjects from the Prefabs folder to the scene next.

SteamVrPrefabsFolder

Select both [CameraRig] and [SteamVR] and drag them to the Hierarchy:

DragVRPrefabs

[SteamVR] has a Steam VR_Render component attached that handles the rendering of all VR cameras.

[CameraRig] is more interesting because it controls the Vive’s headset and controllers. Select [CameraRig] and set its Position to (X:0, Y:0, Z:-1.1) in the Inspector to slide the whole rig just behind the table.

Delete the Main Camera from the Hierarchy because it’ll interfere with [CameraRig] and its embedded camera.

Put your HMD in reach, turn on the controllers and start the scene. Take both controllers and swing them around a bit. You’ll notice that you see the virtual controllers waving around in the Scene view:

ControllerMoveScene

Once the SteamVR plugin detects the controllers, it creates these virtual versions. The controllers are mapped to the two Controller children of [CameraRig]:

Now, while still running the scene, select Camera in the Hierarchy and carefully pick up your head-mounted display by the top strap. Move and rotate it a bit and watch the Scene view:

HeadMove

Camera is linked to the head-mounted display and precisely tracks every real-life movement and rotation.

Now, put the head-mounted display on your head, grab the controllers and look and walk around a bit to get a feel for the room.

If you try interacting with the objects, you’ll be disappointed because nothing happens. To add functionality beyond movement tracking, you need to do some scripting.

Handling Input

Take one of the controllers in your hand and give it a proper look. Each controller has the following inputs:

ViveControllerButtons

Besides the inputs above, each controller also has a velocity and rotational velocity as you move and rotate them; this proves especially handy when interacting with physics objects.

Defining Actions

Since version 2.0 of SteamVR, Valve has replaced the traditional 1:1 hardware mapping of the input system with an action system. This adds a layer of abstraction between the hardware and Unity.
The action system allows you to think of user actions instead of what buttons or triggers need to be polled for input. SteamVR figures out what inputs to use for a given action.

Open the SteamVR Input window by selecting Window > SteamVR Input in the top menu.

The window below appears asking you if you want to copy some example files to the project. Select Yes:

This creates several JSON files in the root of the project:

The actions.json file contains all actions, action sets and references to the default bindings. The other JSON files contain the default bindings for each action. Luckily, you won’t have to edit these manually.

Look at the SteamVR Input window; this contains all of the action sets and actions:

This window contains a few important sections and buttons:

  1. The action sets: the buttons here act like tabs to switch between the sets.
  2. Actions: The default set of actions, both input and output. You can add, edit and delete the actions here.
  3. This button saves all action sets and actions to JSON and generates lots of helper classes for easy access to the actions.
  4. The button opens a locally hosted web page that allows you to bind the actions to hardware.

You can define multiple actions sets here, each containing a list of actions. You can categorize every input action as one of the following types:

  • Boolean: Actions that are either true or false, on or off. Example use: grab.
  • Single / Vector1: Actions that have a value between 0 and 1. Example use: movement speed.
  • Vector2: Actions that have an X and Y value between 0 and 1. Example use: direction.
  • Vector3: Actions that have an X, Y and Z value between 0 and 1. These actions are very uncommon.
  • Pose: Actions that represent a position and rotation in 3D space. These actions are used to track VR controllers and the HMD.
  • Skeleton: Actions that use the SteamVR Skeleton Input system to estimate where the player’s fingers are when holding the VR controller. This provides joint positions and rotations for every finger, regardless of the controller’s tracking fidelity.

Lastly, there’s also a single output action: vibration. You can use this action to make a controller vibrate.

Time to edit the actions!

Click platformer button and the minus button next to the mirrored drop-down to delete the platformer action set:

When the confirmation window appears, select Delete.

Do the same for the buggy action set; you only need the default action set for this tutorial.

Next, clean up the actions list by deleting the following actions. Select them one-by-one and click the minus button at the bottom right:

  • InteractUI
  • GrabPinch
  • Pose
  • Squeeze

Next, select the GrabGrip action and rename it to Grab by editing its Name field:

With all of the actions created, click Save and generate on the bottom-left to save the actions and generate a bunch of helper scripts and scriptable objects for easy access.

After a short while, the input system finishes and a new SteamVR_Input folder is added to the Assets folder:

This folder contains the files created by the generator. More specifically, scripts and scriptable objects.

Binding Actions

Now that you defined the actions, you can create the default hardware bindings for them.
Make sure SteamVR is running and your Vive’s controllers are turned on. Next, click Open binding UI on the bottom-right. A locally hosted web page opens in your default browser:

To start, if you have a gamepad or other compatible hardware connected, you’ll need to switch to the Vive Controller binding first. Click Gamepad, for example, and select Vive Controller to switch to the controller setup.

Next, below the Current Binding header, click Edit to enter the binding menu.

The binding editor now shows up on screen. Take a quick look around. You can visually bind the actions to the hardware here.
Thanks to mirroring, you only need to set up the left controller, and those settings will copy over to the other side. You can change this by unchecking the Mirror Mode checkbox at the bottom of the screen. Leave it mirrored for the sake of this tutorial though.

If you look at the left side of the screen and scroll down, you’ll notice there’s already some inputs linked to actions:

To start with a clean slate, hover over every defined input and click the trashcan icon to delete it. When a confirmation prompt appears, select Delete.

This is the result:

Now, define a new Button binding for the Grip by clicking the big plus button next to its name and selecting BUTTON in the window that pops up.

This lets you use the grip like a button (which it is). Now, click on None next to the Click event to link an action to it. A window pops up with a list of possible actions. Choose grab to link it.

That’s it when it comes to defining new inputs! Now, every time you press the grip button when the game is running, the grab action gets triggered. Pretty neat, right?

Click Apply on the bottom-left of the input entry to save it to the action set.

Now do the same for the Trigger and Trackpad: Create a new Button input for each, link the Trigger’s Click event to grab and the Trackpad’s Click event to Teleport. Be sure to save these when you’re done.

The result looks like this:

That’s it! The bindings are set up. Click Replace Default Binding to apply these settings to the default configuration file.

In the next window, click Save on the lower-left to save the default binding to disk.
When players start your game, this binding gets applied. If there are no bindings for the player’s specific hardware, they’ll be prompted to set these up themselves.

Close the Binding UI tab or window, return to the Unity editor and close the SteamVR Input window. Time to put this binding to good use!

Using Actions

With the actions created and bound to hardware inputs, you can now finally use these in your game!

Start with selecting the Pose Actions for the controllers; this links the physical controllers’ position and rotation to the virtual ones.
Expand [CameraRig] in the Hierarchy and select Controller (left).

Change its Pose Action to \actions\default\in\SkeletonLeftHand to link it to the left controller.

Now do the same with Controller (right), but change its Pose Action to \actions\default\in\SkeletonRightHand instead.

Next, create a new C# script in RW \ Scripts, name it ActionsTest and open it in your favorite code editor.

Remove the Start() method and add the following to the top of the file:

using Valve.VR;

This references the namespace needed to access the VR input classes.

Next, add the following right above Update():

public SteamVR_Input_Sources handType; // 1
public SteamVR_Action_Boolean teleportAction; // 2
public SteamVR_Action_Boolean grabAction; // 3

With this code, you made:

  1. The type of hand(s) to poll for input. These are either All, Left or Right.
  2. Reference to the Teleport action.
  3. Reference to the Grab action.

Now, add the following methods right below Update():

public bool GetTeleportDown() // 1
{
    return teleportAction.GetStateDown(handType);
}

public bool GetGrab() // 2
{
    return grabAction.GetState(handType);
}

Here’s what these are for:

  1. Poll if the Teleport action was just activated and return true if this is the case.
  2. Poll if the Grab action is currently activated.

Finally, add the following if-statements to Update():

if (GetTeleportDown())
{
    print("Teleport " + handType);
}

if (GetGrab())
{
    print("Grab " + handType);
}

These statements check the methods you created and print a message to the console when these return true.

You’re ready to test the sript. Save it and return to the Unity editor.

Select both Controller GameObjects and add an Actions Test component to both by clicking Add Component, typing “Act” and selecting the top option.

Open the Teleport Action drop-down and change its value to \actions\default\in\Teleport. Now, do the same for Grab Action and change it to \actions\default\in\Grab.

Finally, select only Controller (left) and change its Hand Type to Left Hand. Do the same for Controller (right), but choose Right Hand this time.

Run the game again, take both controllers in your hands, press some buttons and squeeze the triggers. Now look at the Console line at the bottom of the screen:

Notice, every action gets registered in the console.

That’s it for basic input config. Now you have the power to manipulate the virtual world at your fingertips — literally!

Using The Controllers With Physics Objects

VR affords users many opportunities that are not possible in the real world, including picking objects up, examining them and throwing them around without having to clean up afterward.

You can create this carefree virtual experience by employing some trigger colliders and doing a bit of scripting.

Select both controllers in the Hierarchy and add a Rigidbody component to them. (Add Component > Physics > Rigidbody)

Check the Is Kinematic checkbox and uncheck Use Gravity:

Now, add a Box Collider (Add Component > Physics > Box Collider) to both controllers and check Is Trigger.

The default collider is huge, so you’ll need to resize and reposition it. Set Center to (X:0, Y:-0.04, Z:0.02) and Size to (X:0.14, Y:0.07, Z:0.05). In this case, you require these kinds of precise values because even a hundredth of a unit affects where the collider ends up.

Run the game again. Select a controller in the Hierarchy and pick up the real controller. Look at the Scene view and focus on the controller you’re holding (press F). The collider goes right over the top part of the controller, which is the part you use to pick up objects.

ControllerColliderScene

Without a script, this collider is little more than a useless cube. Create a new C# script in RW \ Scripts, name it ControllerGrabObject and open it.

Add this to the top of the script:

using Valve.VR;

Like the previous script, this allows you easy access to the SteamVR classes.
Now, remove the Start() method and add this familiar code in its place:

public SteamVR_Input_Sources handType;
public SteamVR_Behaviour_Pose controllerPose;
public SteamVR_Action_Boolean grabAction;

This is the same code you used for the input test script. It stores references to the hand type and the actions.
Add these variables below the ones you just added:

private GameObject collidingObject; // 1
private GameObject objectInHand; // 2

Each variable has a purpose:

  1. Stores the GameObject that the trigger is currently colliding with, so you have the ability to grab the object.
  2. Serves as a reference to the GameObject that the player is currently grabbing.

Next, add this method above Update():

private void SetCollidingObject(Collider col)
{
    // 1
    if (collidingObject || !col.GetComponent<Rigidbody>())
    {
        return;
    }
    // 2
    collidingObject = col.gameObject;
}

This method accepts a collider as a parameter and uses its GameObject as the collidingObject for grabbing and releasing. Moreover, it:

  1. Doesn’t make the GameObject a potential grab target if the player is already holding something or the object has no rigidbody.
  2. Assigns the object as a potential grab target.

Add these trigger methods:

// 1
public void OnTriggerEnter(Collider other)
{
    SetCollidingObject(other);
}

// 2
public void OnTriggerStay(Collider other)
{
    SetCollidingObject(other);
}

// 3
public void OnTriggerExit(Collider other)
{
    if (!collidingObject)
    {
        return;
    }

    collidingObject = null;
}

These methods handle what should happen when the trigger collider enters and exits another collider.

  1. When the trigger collider enters another, this sets up the other collider as a potential grab target.
  2. Similar to section one (// 1), but different because it ensures that the target is set when the player holds a controller over an object for a while. Without this, the collision may fail or become buggy.
  3. When the collider exits an object, abandoning an ungrabbed target, this code removes its target by setting it to null.

Next, you’ll add some code to grab an object below OnTriggerExit():

private void GrabObject()
{
    // 1
    objectInHand = collidingObject;
    collidingObject = null;
    // 2
    var joint = AddFixedJoint();
    joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
}

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

In here, you:

  1. Move the colliding GameObject into the player’s hand and remove it from the collidingObject variable.
  2. Add a new joint that connects the controller to the object using the AddFixedJoint() method below.
  3. Make a new fixed joint, add it to the controller, and then set it up so it doesn’t break easily. Finally, you return it.

What can be grabbed must be released. This next block handles releasing the object:

private void ReleaseObject()
{
    // 1
    if (GetComponent<FixedJoint>())
    {
        // 2
        GetComponent<FixedJoint>().connectedBody = null;
        Destroy(GetComponent<FixedJoint>());
        // 3
        objectInHand.GetComponent<Rigidbody>().velocity = controllerPose.GetVelocity();
        objectInHand.GetComponent<Rigidbody>().angularVelocity = controllerPose.GetAngularVelocity();

    }
    // 4
    objectInHand = null;
}

This code removes the grabbed object’s fixed joint and controls its speed and rotation when the player tosses it away. The controller’s velocity is vital here. Without using it, the discarded object would drop straight down no matter how perfect your throw might be. Trust me, it doesn’t feel right. :]

Section-by-section breakdown:

  1. Make sure there’s a fixed joint attached to the controller.
  2. Remove the connection to the object held by the joint and destroy the joint.
  3. Add the speed and rotation of the controller when the player releases the object, so the result is a realistic arc.
  4. Remove the reference to the formerly attached object.

Finally, add this inside Update() to handle the controller input:

// 1
if (grabAction.GetLastStateDown(handType))
{
    if (collidingObject)
    {
        GrabObject();
    }
}

// 2
if (grabAction.GetLastStateUp(handType))
{
    if (objectInHand)
    {
        ReleaseObject();
    }
}
  1. When the player triggers the Grab action, grab the object.
  2. If the player releases the input linked to the Grab action and there’s an object attached to the controller, this releases it.

I bet you can’t wait to try this out! Save the script and return to the editor.

Select both controllers in the Hierarchy and add a Controller Grab Object component to each.

With both controllers still selected, set the Grab Action to \actions\default\in\Grab. Next, select only Controller (left), set its Hand Type to Left Hand and drag the Steam VR_Behaviour_Pose component onto the Controller Pose slot.

Now do the same for the right controller, but use Right Hand instead.

Time to have some fun! Get your controllers ready, start the game and put on the headset. Pick up and toss around some cubes and balls using the hair trigger or the grip button. You can even juggle with a bit of practice.

GrabbingPlay

You have to hand it to yourself, you’re pretty awesome right now. However, I think you can make your VR experience even cooler.

Making A Laser Pointer

A laser pointer is handy in a VR world for all sorts of reasons. You can use them to pop virtual balloons, aim guns better and frustrate digital kittens.

Making one is quite simple: All you need is a cube and another script. Start off by creating a new Cube in the root of the Hierarchy (Create > 3D Object > Cube).

Name it Laser, set its position to (X:0, Y:5, Z:0), change the scale to (X:0.005, Y:0.005, Z:1) and remove the Box Collider component. Focus on it and you’ll see it floating above the rest of the level:

FloatLaser

Lasers shouldn’t cast shadows, and they’re always the same color, so you can get the desired effect by using an unlit material.

Create a new material in RW \ Materials and name it Laser. Then, change its shader to Unlit/Color and set its Main Color to pure red:

LaserMat

Assign the new material by dragging it onto Laser in the Hierarchy or the Scene view:

DragLaserMat

Finally, drag the Laser from the Hierarchy to RW \ Prefabs and delete the original one from the Hierarchy.

Now make a new C# script named LaserPointer in RW \ Scripts and open it.
Add this using declaration to the top of the file:

using Valve.VR;

Next, Add these familiar variables right above Start():

public SteamVR_Input_Sources handType;
public SteamVR_Behaviour_Pose controllerPose;
public SteamVR_Action_Boolean teleportAction;

These reference a controller and the Teleport action.

Add these variables underneath teleportAction:

public GameObject laserPrefab; // 1
private GameObject laser; // 2
private Transform laserTransform; // 3
private Vector3 hitPoint; // 4
  1. This is a reference to the Laser prefab.
  2. laser stores a reference to an instance of the laser.
  3. The transform component is stored for ease of use.
  4. This is the position where the laser hits.

Add this method below Update() to show the laser:

private void ShowLaser(RaycastHit hit)
{
    // 1
    laser.SetActive(true);
    // 2
    laserTransform.position = Vector3.Lerp(controllerPose.transform.position, hitPoint, .5f);
    // 3
    laserTransform.LookAt(hitPoint);
    // 4
    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.

Stepping through each line:

  1. Show the laser.
  2. Position the laser between the controller and the point where the raycast hits. You use Lerp because you can give it two positions and the percent it should travel. If you pass it 0.5f, which is 50%, it returns the precise middle point.
  3. Point the laser at the position where the raycast hit.
  4. Scale the laser so it fits perfectly between the two positions.

Add the following inside Update() to make use of the player’s input:

// 1
if (teleportAction.GetState(handType))
{
    RaycastHit hit;

    // 2
    if (Physics.Raycast(controllerPose.transform.position, transform.forward, out hit, 100))
    {
        hitPoint = hit.point;
        ShowLaser(hit);
    }
}
else // 3
{
    laser.SetActive(false);
}
  1. If the Teleport action is activated:
  2. Shoot a ray from the controller. If it hits something, make it store the point where it hit and show the laser.
  3. Hide the laser when the Teleport action deactivates.

Add the following inside of the empty Start() method:

// 1
laser = Instantiate(laserPrefab);
// 2
laserTransform = laser.transform;
  1. Spawn a new laser and save a reference to it in laser.
  2. Store the laser’s transform component.

Save this script and return to the editor. Select both controllers in the Hierarchy and add a Laser Pointer to each.

With both controllers still selected, set Hand Type to Left Hand and drag the Steam VR_Behaviour_Pose component to the Controller Pose slot. Next, set the Teleport Action to Teleport.

Finally, drag the Laser prefab from Prefabs onto the Laser slot in the Inspector:

Now select just the right controller and set the Hand Type on the Laser Pointer component to Right Hand.

Save your project and give the game another run. Pick up a controller, put on the headset and try holding the touchpad. You’ll see a laser now:

ShootLaser

Before moving on, remove the Actions Test components from the controllers by right-clicking them and selecting Remove Component.

You’re removing them because they write strings for every frame and log them to the Console. It’s not a great thing for performance, and every millisecond counts in VR. They were handy for testing the input, but you should not use them for actual gameplay.

The next step is using this laser to teleport around the room!

Moving Around

Moving around in VR isn’t as simple as pushing the player forward; doing so is a sure-fire way to induce nausea. A more feasible way to get around is teleportation.

The player’s sense of perception accepts a sudden position change more readily than a gradual one. Subtle changes in a VR setting can upset your feeling of balance and velocity more than suddenly finding yourself in a new place.

To show exactly where you’ll end up, you’ll use a marker or reticle that’s provided in Prefabs.
The reticle is a simple, unlit, circular disk:

Reticle

To use the reticle, you’ll append the LaserPointer script, so open it in a code editor and add these variables below the existing ones:

// 1
public Transform cameraRigTransform; 
// 2
public GameObject teleportReticlePrefab;
// 3
private GameObject reticle;
// 4
private Transform teleportReticleTransform; 
// 5
public Transform headTransform; 
// 6
public Vector3 teleportReticleOffset; 
// 7
public LayerMask teleportMask; 
// 8
private bool shouldTeleport; 

Each variable plays an important role:

  1. The transform of [CameraRig].
  2. Stores a reference to the teleport reticle prefab.
  3. A reference to an instance of the reticle.
  4. Stores a reference to the teleport reticle transform for ease of use.
  5. Stores a reference to the player’s head (the camera).
  6. The reticle offset from the floor, so there’s no “Z-fighting” with other surfaces.
  7. A layer mask to filter the areas on which teleports are allowed.
  8. Set to true when a valid teleport location is found.

In Update(), replace this line:

if (Physics.Raycast(controllerPose.transform.position, transform.forward, out hit, 100))

With this one that takes the layer mask into account:

if (Physics.Raycast(controllerPose.transform.position, transform.forward, out hit, 100, teleportMask))

This makes sure the laser can only hit GameObjects that you can teleport to.

Also in Update(), add this code underneath the call to ShowLaser(hit):

// 1
reticle.SetActive(true);
// 2
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
// 3
shouldTeleport = true;

Here’s what this does:

  1. Show the teleport reticle.
  2. Move the reticle to where the raycast hit with the addition of an offset to avoid Z-fighting.
  3. Set shouldTeleport to true to indicate the script found a valid position for teleporting.

While still in Update(), find laser.SetActive(false); and add the following line underneath it:

reticle.SetActive(false);

This hides the reticle in the absence of a valid target.

Add the following method below ShowLaser() to handle the act of teleporting:

private void Teleport()
{
    // 1
    shouldTeleport = false;
    // 2
    reticle.SetActive(false);
    // 3
    Vector3 difference = cameraRigTransform.position - headTransform.position;
    // 4
    difference.y = 0;
    // 5
    cameraRigTransform.position = hitPoint + difference;
}

Who knew teleporting is as simple as five lines? Time to step through that code:

  1. Set shouldTeleport to false when teleportation is in progress.
  2. Hide the reticle.
  3. Calculate the difference between the positions of the camera rig’s center and the player’s head.
  4. Reset the y-position for the above difference to 0, because the calculation doesn’t consider the vertical position of the player’s head.
  5. Move the camera rig to the position of the hit point and add the calculated difference. Without the difference, the player would teleport to an incorrect location. See the example below:

TeleportDifference

As you can see, the difference plays a crucial role in precisely positioning the camera rig and putting the player exactly where they expect to land.

Add this at the end of Update(), just outside the teleport action state if-else statement:

if (teleportAction.GetStateUp(handType) && shouldTeleport)
{
    Teleport();
}

This teleports the player if the touchpad is released and there’s a valid teleport position.
Finally, add this code to Start():

// 1
reticle = Instantiate(teleportReticlePrefab);
// 2
teleportReticleTransform = reticle.transform;
  1. Spawn a new reticle and save a reference to it in reticle.
  2. Store the reticle’s transform component.

That’s it! Save your script and return to Unity.

Select both controllers in the Hierarchy and take note of the new fields:

Drag [CameraRig] to the Camera Rig Transform slot, drag TeleportReticle from Prefabs to the Teleport Reticle Transform slot and drag Camera to the Head Transform slot.

Now set the Teleport Reticle Offset to (X:0, Y:0.05, Z:0) and set the Teleport Mask to CanTeleport. CanTeleport is not a default layer — it was created for this tutorial. The Floor and Table objects are the only ones that are part of this layer.

Play the game and use your laser on the floor to teleport around the room.

FinalGame

This sandbox is now fully functional and ready for endless tinkering!

Where to Go From Here?

You can download the complete project using the Download Materials button at the top or bottom of this tutorial. In this HTC Vive tutorial for Unity, you learned how to:

  • Download and configure SteamVR.
  • Handle the HTC Vive controller input.
  • Interact with physics objects in VR.
  • Make a laser pointer.
  • Teleport around an area.

This project is only the beginning — make it your own. I’m curious to see what you come up with.

Did you enjoy this tutorial? Want to learn more? Check out our book Unity AR & VR by Tutorials, which has more info on making AR & VR games with Unity, including support for VRTK, ARKit and the HoloLens!

Thanks for reading, I hope you enjoyed following along with this tutorial as much as I did writing it.

If you have any suggestions, questions or if you want to show off what you did to improve this project, join the discussion below.

Average Rating

4/5

Add a rating for this content

7 ratings

Contributors

Comments