Tutorial written by Amanda End and Circuit Stream.

Welcome to Section Five of the HTC Vive Virtual Reality Programming Tutorial. In this section we’re going to go over interacting with your objects by pointing or looking at them, rather than touching them directly!

Previously, we covered creating scripts that allow us to touch objects and interact with them. Great! But what if we want to interact with an object just by looking at it? Or pointing at it? In order to do that, we’re going to need to create another input script, similar to our controller input script, that can hook into our interactable base class. We could just add new logic to the controller input script, but since we also want to be able to attach it to the head, and we might not want it always attached, let’s make it a separate script.

Raycasting Overview

 
So how do we detect when something is pointing at something else? Raycasting! Raycasting is a technique used a lot in virtual reality game design. If you’ve never heard of raycasting, think of it like a laser beam detector. You’re sending out a beam from a certain location and in a certain direction, and if it runs into something, it gives you information about what and where it hit!

 
Note: Raycasts aren’t actually visible unless you make them. You probably knew this but just in case, I figured I’d mention it. The above image is actually SteamVR’s laser pointer component. Also, there are a couple tools that we will cover later that could be used in place of this, but I wanted to build a system in myself that was similar to our existing input system.

Updating the Interactable Base Class

 
So we need to add some methods to our base class. Up to this point, we only have ButtonPressDown and ButtonPressUp. We don’t want to use those for this, so let’s create a few new ones: RayEnter, RayExit, and RayStay. Even though the new input script will go on both head and hands, I’m going to be accepting VRControllerInput script as an argument.

Similar to our other methods, working with the assumption that if it is null, then the method was triggered by the head (since the head won’t have that script attached). I’m also adding a RaycastHit argument to our enter and stay methods, since I might want information about where the raycast hit the object.

///
/// Called when either the head or a controller is pointed at an object
///

///Leave null if ray is coming from head public virtual void RayEnter(RaycastHit hit, VRControllerInput controller = null) { //Empty. Overriden meothod only. } ///

/// Called every frame the head or a controller is pointed at an object
///

///Leave null if ray is coming from head public virtual void RayStay(RaycastHit hit, VRControllerInput controller = null) { //Empty. Overriden meothod only. } ///

/// Called when either the head or a controller leaves the object
///

///Leave null if ray is coming from head public virtual void RayExit(VRControllerInput controller = null) { //Empty. Overriden meothod only. }

New Input Script

 
Now let’s create our new input script that will go on our head and hands. Like our controller input, this will be the script that has the information we need to trigger our methods, but instead of looking for things like a button being pressed or the controller being inside an object, we’re raycasting out from the head and hands and looking for it to hit things.

New Script

To get started, let’s make a new script. I called my other input script VRControllerInput, so I’m going to call this one VRRaycastInput. It should inherit from the default MonoBehaviour class.

Differentiate between “head” and “hands”

We want our interactable scripts to be able to differentiate between head and hands. As mentioned before, our interactables are going to be accepting VRControllerInput as an argument, working with the assumption that if it is null, the method was triggered by the head. So, in our script, we’ll create a class variable for the VRControllerInput script and attempt to retrieve it from the gameobject on Awake. If one doesn’t exist, the script is on the head, and variable will remain as null. We can now pass this argument into our raycast methods.

void Awake()
{
	//If attached to head, will return null (expected behavior)
	//I'm using this method to differentiate between head and hands
	controllerInput = GetComponent();
}

Raycasting Methods

We know that there are three methods that we want to trigger on our interactables: RayEnter, which we want to trigger on the first frame that we’re pointing at the object, RayExit, which we want to trigger on the first frame after we’ve stopped pointing at the object, and RayStay, which we want to trigger every frame (except the first) that I’m pointing at the object.

I don’t want to put all the logic of checking for the interactable script and calling their methods in my Raycasting checks, so let’s create three methods in this class that we can put that logic in. I named them the same as the methods on the interactables that we’ll be calling, but if that confuses you, feel free to name them something else.

protected void RayEnter(RaycastHit hit)
{

}

protected void RayStay(RaycastHit hit)
{

}

protected void RayExit()
{

}

Triggering RayStay

All of our Raycasting will be done in the Update loop. Let’s start with detecting when we should call the RayStay method. Again, this is the method we want to call during every frame that we’re pointing at the object.

protected GameObject hitObject;
protected RaycastHit rayHit;

void Update()
{
	//Check if raycast hits anything
	if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out rayHit))
	{
		//If the object is the same as the one we hit last frame
		if (hitObject != null && hitObject == rayHit.transform.gameObject)
		{
			//Trigger "Stay" method on Interactable every frame we hit
			RayStay(rayHit);
		}
		//We're still hitting something, but it's a new object
		else
		{
			//Keep track of new object that we're hitting
			hitObject = rayHit.transform.gameObject;
		}
	}
}

The if statement at the top here is sending out a ray, and returning true if the ray hits something. We’re passing in rayHit as an out argument, so that if the ray does hit something, we have information about it. Once we’ve established that we hit something, we want to make sure that the object we’re hitting is the same object we were hitting on the last frame, otherwise we’d be pointing at a new object.

So we’ll create a class variable called hitObject that will keep track of the gameobject that we’re pointing at. If this is the first frame we’re hitting something, it will be null. Then if we are hitting the same object, we’re calling our RayStay method on the interactable. If we hit something and it’s not the same object that we were hitting last frame, we’re assigning hitObject to that new object, so that on the next frame, if we’re still pointing at the same object, RayStay will be called.

Triggering RayEnter

We only want RayEnter to be triggered when we hit something, and only on the first frame that we hit it. We actually have a spot for this already carved out, since that’s the same situation that we need for assigning the hitObject. So we can put the RayEnter method next to that.

//We're still hitting something, but it's a new object
else
{
	//Keep track of new object that we're hitting, and trigger the ray "Enter" method on Interactable
	hitObject = rayHit.transform.gameObject;
	RayEnter(rayHit);
}

Triggering RayExit

Lastly, we want to trigger RayExit when we’ve stopped hitting something. There are two situations for that: one is when we’ve hit something new, and the other is when we’ve started hitting nothing. So in that same spot where we’re assigning the new hitObject and triggering its RayEnter method, we’ll trigger the RayExit for the previously hit object.

//We're still hitting something, but it's a new object
else
{
	//Trigger the ray "Exit" method on Interactable
	RayExit();

	//Keep track of new object that we're hitting, and trigger the ray "Enter" method on Interactable
	hitObject = rayHit.transform.gameObject;
	RayEnter(rayHit);
}

We’ll also place it on the outside of the main if statement we created where we checked if we were hitting anything. That way, If we’re not hitting anything, we can call RayExit on the hitObject.

void Update()
{
	//Check if raycast hits anything
	if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out rayHit))
	{
		//If the object is the same as the one we hit last frame
		if (hitObject != null && hitObject == rayHit.transform.gameObject)
		{
			//Trigger "Stay" method on Interactable every frame we hit
			RayStay(rayHit);
		}
		//We're still hitting something, but it's a new object
		else
		{
			//Trigger the ray "Exit" method on Interactable
			RayExit();

			//Keep track of new objec that we're hitting, and trigger the ray "Enter" method on Interactable
			hitObject = rayHit.transform.gameObject;
			RayEnter(rayHit);
		}
	}
	else
	{
		//We aren't hitting anything. Trigger ray "Exit" on Interactable
		RayExit();
	}
}

You may notice that I’m not checking to see if hitObject is null before triggering RayExit, but don’t worry, we’ll do that in the Ray methods.

Wait What Methods

Those methods we created earlier! Time to fill them out. Remember, these are the methods on the VRRaycastInput script, not the VRInteractableObject script. Again, if it confuses you for them to have the same name, feel free to name them something different.

protected void RayEnter(RaycastHit hit)
{
	//Asign to class variable so it can be used in RayStay
	hitObjectObjectInteractable = hitObject.GetComponent();
	if (hitObjectObjectInteractable)
	{
		//If hit object is an Interactable, trigger RayEnter Method
		hitObjectObjectInteractable.RayEnter(hit, controllerInput ?? null);
	}
}

protected void RayStay(RaycastHit hit)
{
	if (hitObjectObjectInteractable)
	{
		//If hit object is an Interactable, trigger RayStay Method
		hitObjectObjectInteractable.RayStay(hit, controllerInput ?? null);
	}
}

protected void RayExit()
{
	if (hitObjectObjectInteractable)
	{
		//If hit object is an Interactable, trigger RayExit Method
		hitObjectObjectInteractable.RayExit(controllerInput ?? null);

		//Clear class variables
		hitObjectObjectInteractable = null;
		hitObject = null;
	}
}

Lastly!

Since we don’t want to be checking to see if the object we’re hitting has a VRInteractableObject script on it every single frame, we’re checking in the RayEnter script and assigning it to a class variable (hitObjectInteractable).

The RayStay and RayExit methods, we can just check to see if that variable is null. For all three methods, if hitObjectInteractable not null (meaning object is an interactable), we’re triggering the proper Ray method for each that we created in the base class.

In the RayExit method, we’re also setting the interactable variable and gameobject variable to null, to clear reference to that object. And that completes the input side of our script!

And that completes our input script for raycasting! Next time we’ll be covering what you can do with them!

Previously: Interactable Examples.
Next Up: Raycast Examples.

Learn unity online with our Unity 3D course for virtual reality coding.