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
///

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.

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.

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.

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.

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!