Unity3D Tutorial #6

Teleporting

[@Duncan]

To begin, start Unity and open up your Throw A Ball project from the last Unity tutorial.  If you didn't do it or would like to start fresh, make a new project, import SteamVR, and create a new 3D Object->Plane named "floor."

We're going to create a new Layer called "CanTeleport" that we'll use later to determine if a surface is something we can teleport onto.  Click on your floor object and then look at the top of the inspector window.  Near the top, select the Layer drop down and click "Add layer."  Note that this is the "Layer" dropdown in the Inspector for the object you've selected, not the "Layers" dropdown that you might see right above the Inspector window as part of the top Unity bar.  Pick one of the User Layer options and in the accompanying text box type CanTeleport and then navigate back to the floor's inspector window, go back to the Layer drop down, and select your newly created CanTeleport layer.

Setting up a Laser Pointer and Reticle

First, we're gonna create an object which will be the laser we'll use to point around the room.  Whenever we press the controller's touchpad, it'll appear as a thin red line extending to the floor wherever we're pointing.

Create a new 3D Object Cube and name it "Laser." Set its scale to 0.005, 0.005, 0 and remove its Box Collider component.

Create a material for the laser, also called Laser.  Set its shader to Unlit->Color and make it red.  Drag it onto the Laser object in the hierarchy.  Create a Prefabs folder in your Assets folder and drag the Laser object from the hierarchy menu into the Prefabs folder and then delete the version in the hierarchy menu.

Now we'll make the reticle.  Create new Cylinder 3D object named "Reticle."  Set its Y scale to 0.01.  Give it the same material as the laser and similarly drag it into the Prefabs folder and then delete the copy from the hierarchy menu.

The Script

Create a new C# Script called LaserPointer.  Below, I've included the code for the entire file - it's a little long but it's all commented to explain what's going on.  Open your LaserPointer script, enter this code, and save the file.

using System.Collections;

using System.Collections.Generic;

using UnityEngine;


public class LaserPointer : MonoBehaviour {


    // These lines make a reference to an object to be tracked, which will be a controller

    // and then fill it using the SteamVR Controller get method.

    private SteamVR_TrackedObject trackedObj;


    private SteamVR_Controller.Device Controller

    {

        get { return SteamVR_Controller.Input((int)trackedObj.index); }

    }


    // Gets reference to the controllers

    void Awake()

    {

        trackedObj = GetComponent<SteamVR_TrackedObject>();

    }


    // These four variables are for displaying the laser where the user is pointing


    // Will refer to the prefab we made for the laser

    public GameObject laserPrefab;

    // Will hold an actual instance of a laser

    private GameObject laser;

    // Holds transform of the laser

    private Transform laserTransform;

    // To mark the point the laser will hit

    private Vector3 hitPoint;


    // Will be called when the user has pressed the touchpad and is pointing at a valid

    // location so the laser needs to be shown.

    // Makes the laser visible, points it in the right direction, and scales it to hit the surface.

    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);

    }


    // These 8 variables are for displaying the teleport reticles and

    // updating the user's position


    // transform of the camera rig

    public Transform cameraRigTransform;

    // reference to the teleport reticle prefab we made

    public GameObject teleportReticlePrefab;

    // instance of the reticle

    private GameObject reticle;

    // position of the reticle

    private Transform teleportReticleTransform;

    // position of the user's head

    public Transform headTransform;

    // We'll use this variable to offset the reticle off the floor slightly (by giving a positive y offset)

    // so no z-fighting occurs (it'll show on the floor better).

    public Vector3 teleportReticleOffset;

    // reference to the layer teleporting is allowed on

    public LayerMask teleportMask;

    // true if a valid teleport can occur

    private bool shouldTeleport;


    // Initializes a few of our variables from the prefabs we'll provide via the Unity editor

    void Start () {

        laser = Instantiate(laserPrefab);

        laserTransform = laser.transform;

        reticle = Instantiate(teleportReticlePrefab);

        teleportReticleTransform = reticle.transform;

    }



    // Update is called once per frame

    void Update()

    {

        // if the user presses the touchpad

        if (Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad))

        {

            RaycastHit hit;


            // If they're pointing at a valid location

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

            {

                hitPoint = hit.point;

                // Calls the method that we wrote to properly show the laser

                ShowLaser(hit);

                // Make the teleport reticle visible

                reticle.SetActive(true);

                // Put the teleport reticle in the right spot

                teleportReticleTransform.position = hitPoint + teleportReticleOffset;

                // Mark that the user can currently teleport to the marked spot

                shouldTeleport = true;

            }

        }

        else // Not pointing at valid spot so don't show anything

        {

            laser.SetActive(false);

            reticle.SetActive(false);

        }

        // If they've released the button and are pointing at a valid spot, teleport them there

        if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport)

        {

            Teleport();

        }

    }


    // Teleports the user to the location currently being pointed at.

    // Marks that the user can't teleport while they're already teleporting,

    // hides the reticle, moves the cameraRig to the new location.

    private void Teleport()

    {

        shouldTeleport = false;

        reticle.SetActive(false);

        Vector3 difference = cameraRigTransform.position - headTransform.position;

        difference.y = 0;

        cameraRigTransform.position = hitPoint + difference;

    }

}

Back in Unity, select both controllers in the hierarchy and drag the LaserPointer script onto their Inspector window as a new component.  We'll now need to provide the script all the objects we reference in the code.  In the LaserPointer (Script) section of the Inspector menu for both controller objects there should be a few blank fields that we'll fill by dragging items into them.  Fill Laser Prefab with your "Laser" from the Prefabs folder.  Camera Rig Transform gets "[Camera Rig]" from the hierarchy menu.  Fill Teleport Reticle Prefab with your "Retical" from the Prefabs folder.  Fill Head Transform with "Camera (head)" from the hierarchy menu.  In the Teleport Reticle Offset, set Y to 0.05 and keep X and Z at 0.  Finally, in the Teleport Mask dropdown select the CanTeleport layer.

Play!

Your scene should be all ready for you to connect a Vive and try it out.  Once it's working, consider getting creative and building on the scene in some way.  You could change the reticle material or size, add other surfaces to teleport to (just make sure they're on the CanTeleport layer), add objects to pick up and hit (they'll need box colliders and you'll need the controller pick up set up from Throw a Ball Tutorial), or anything else you can think of!