As any person that has already used Unity’s Ray class knows, there’s no support for reflection, which could be useful for some specific cases. This post will try to offer a solution to that, explaining how to create a script which casts a ray that gets reflected when it hits a surface. Not only that, but the script also allows to set the number of times the cast ray should bounce. An example project with a scene and the code explained below is available for download at the end of the tutorial.
Before looking how the reflection script works, a scene must be set with some walls to reflect the ray. Additionally, a game object will be required to act as the source of the ray . To create the ray’s source, just select GameObject->Create Other->Cube:
The name of the game object doesn’t matter, so use any one you like. For this tutorial, I’ll be naming it as “Ray Emitter“. Since the cast ray isn’t visible, something must represent it, and that’s why we need to add a Line Renderer component, by clicking Component->Miscellaneous->Line Renderer:
After it has been attached to the Ray Emitter, add a material to it and set both the Start Width andEnd Width attributes to 0.1 . Make sure that the Use World Space box is checked. Next, create various cube game objects and scale them so they look a more like walls. Position these created objects close to the Ray Emitter game object, to ensure that the ray bounces in one of these walls:
Finally, the following script must be attached to the Ray Emitter game object:
view plaincopy to clipboardprint?
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (LineRenderer))]
public class RaycastReflection : MonoBehaviour
{
//this game object's Transform
private Transform goTransform;
//the attached line renderer
private LineRenderer lineRenderer;
//a ray
private Ray ray;
//a RaycastHit variable, to gather informartion about the ray's collision
private RaycastHit hit;
//reflection direction
private Vector3 inDirection;
//the number of reflections
public int nReflections = 2;
//the number of points at the line renderer
private int nPoints;
void Awake ()
{
//get the attached Transform component
goTransform = this.GetComponent<Transform>();
//get the attached LineRenderer component
lineRenderer = this.GetComponent<LineRenderer>();
}
void Update ()
{
//clamp the number of reflections between 1 and int capacity
nReflections = Mathf.Clamp(nReflections,1,nReflections);
//cast a new ray forward, from the current attached game object position
ray = new Ray(goTransform.position,goTransform.forward);
//represent the ray using a line that can only be viewed at the scene tab
Debug.DrawRay(goTransform.position,goTransform.forward * 100, Color.magenta);
//set the number of points to be the same as the number of reflections
nPoints = nReflections;
//make the lineRenderer have nPoints
lineRenderer.SetVertexCount(nPoints);
//Set the first point of the line at the current attached game object position
lineRenderer.SetPosition(0,goTransform.position);
for(int i=0;i<=nReflections;i++)
{
//If the ray hasn't reflected yet
if(i==0)
{
//Check if the ray has hit something
if(Physics.Raycast(ray.origin,ray.direction, out hit, 100))//cast the ray 100 units at the specified direction
{
//the reflection direction is the reflection of the current ray direction flipped at the hit normal
inDirection = Vector3.Reflect(ray.direction,hit.normal);
//cast the reflected ray, using the hit point as the origin and the reflected direction as the direction
ray = new Ray(hit.point,inDirection);
//Draw the normal - can only be seen at the Scene tab, for debugging purposes
Debug.DrawRay(hit.point, hit.normal*3, Color.blue);
//represent the ray using a line that can only be viewed at the scene tab
Debug.DrawRay(hit.point, inDirection*100, Color.magenta);
//Print the name of the object the cast ray has hit, at the console
Debug.Log("Object name: " + hit.transform.name);
//if the number of reflections is set to 1
if(nReflections==1)
{
//add a new vertex to the line renderer
lineRenderer.SetVertexCount(++nPoints);
}
//set the position of the next vertex at the line renderer to be the same as the hit point
lineRenderer.SetPosition(i+1,hit.point);
}
}
else // the ray has reflected at least once
{
//Check if the ray has hit something
if(Physics.Raycast(ray.origin,ray.direction, out hit, 100))//cast the ray 100 units at the specified direction
{
//the refletion direction is the reflection of the ray's direction at the hit normal
inDirection = Vector3.Reflect(inDirection,hit.normal);
//cast the reflected ray, using the hit point as the origin and the reflected direction as the direction
ray = new Ray(hit.point,inDirection);
//Draw the normal - can only be seen at the Scene tab, for debugging purposes
Debug.DrawRay(hit.point, hit.normal*3, Color.blue);
//represent the ray using a line that can only be viewed at the scene tab
Debug.DrawRay(hit.point, inDirection*100, Color.magenta);
//Print the name of the object the cast ray has hit, at the console
Debug.Log("Object name: " + hit.transform.name);
//add a new vertex to the line renderer
lineRenderer.SetVertexCount(++nPoints);
//set the position of the next vertex at the line renderer to be the same as the hit point
lineRenderer.SetPosition(i+1,hit.point);
}
}
}
}
}
This is a long script, but don’t worry: more than half of it is just for making the lineRendererfollow the ray; the part that makes the reflection work is actually quite small. Right at its start, there are some variables being declared, such as goTransform and the lineRenderer. Both will act as handles for the two components this script needs to read values from or modify (lines 9 and 11). Then, we have the Ray and the RaycastHit object – the first casts the ray, and the second queries information about the objects the ray is colliding with (lines 14 and 16).
The inDirection is a Vector3 that will store the direction of the reflected ray and the integersnReflections and nPoints are, respectively, the number of reflections and the number of vertices the line representing the ray must have (lines 19 and 22). On a side note, for this script, thenReflections is the number of times the ray is reflected, and not the number of times the ray has hit something. This means that nReflections with a value of 2 will make the ray “bounce” two times before “stopping” on the third collision.
Back to the code, inside the Awake() method, the goTransform and lineRenderer variables are initialized (lines 27 through 33). Finally, at the Update() method, line 38 defines that the number of reflections can’t be smaller than one (otherwise, there is no point of using this script over a simple raycast). Next, the ray is cast forwards using the attached game object as the origin (line 40). After that, the Debug.DrawRay() method is called, drawing a magenta colored line to represent the ray (line 43). This is just one of the many Debug.Draw() method calls in this script. The rays created by it can only be seen when the game is running, under the Scene tab. They won’t appear at the Game tab or when the game is compiled and exported, as the class name suggests, they are for debugging purposes only. More information can be found here.
Moving on, lines 46 through 50 ensures that the lineRender is going to be rendered to represent the path of the ray. Finally, we have reached the most critical part of the code: the for loop (line 52). This block of code runs nReflections+1 times for each frame, meaning that, for this example, it will execute three times for each game update. This loop has basically a if-else block that checks whether if it’s first iteration is being executed (line 55). Case that’s true and the ray is colliding with an object, line 61 initializes the inDirection variable, by calculating the reflection of the ray, using the Vector3.Reflect() method. It takes two parameters, the first is a Vector3 we want to reflect and the other is the axis to be used for the reflection. These parameters were filled, respectively, with the ray’s direction and the normal of the surface at the collision point.
With the inDirection variable calculated, the ray can be cast again, using the former as the new direction (line 63). The next two lines renders two ray representations for debugging purposes only, as explained above. The one being rendered with the magenta color will represent the reflected ray, and the blue one represents the collision normal (lines 66 and 68). Again, they can only be seen when the game is running, at the Scene tab. Then, the following lines check if the number of reflections is set to be only a single one. If it is, the script must add a new vertex to thelineRenderer (lines 74 through 78). Next, the position of the next vertex in the lineRenderer is set to be the same as the collision point (line 81).
The code inside the else block works the same way as the one inside the if part, except that we don’t need to check for the first iteration of the loop. The reflection is calculated in the same manner as before: the ray direction and hit normal are passed as parameters to theVector3.Reflect() method.
That’s it! Here’s how it look like:
At the example project, the LineRenderer won’t draw the line if it isn’t going to hit anything. However, chances are, in a real game situation, there’s always going to be a surface for the ray to bounce. That said, this script could be improved by checking if the ray isn’t colliding with anything, assigning a position to the lineRenderer to draw. But this is a unusual situation, meaning this code will fit most purposes when a raycast reflection is required.