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. By Eric Van de Kerckhove.

4.4 (19) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

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!