Unity3D Tutorial #6
Requirements
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!