Get the FULL version

Unity: Raycast Reflection

Unity: Raycast Reflection thumbnail

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:

Creating a new Cube.

Creating a new Cube game object.

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:

Attaching the Line Renderer

Attaching the Line Renderer to the 'Ray Emitter' game object.

After it has been attached to the Ray Emitter, add a material to it and set both the Start Width and End 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:

Adding some walls.

Rotate the walls so the emitted ray can reflect in all of them.

Finally, the following script must be attached to the Ray Emitter game object:

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 lineRenderer follow 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 integers nReflections 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, the nReflections 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 the lineRenderer (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 the Vector3.Reflect() method.

That’s it! Here’s how it look like:

Example project screenshot

Example project screenshot.

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.

You can download the example project below:

Downloads

13 Comments to “Unity: Raycast Reflection”

  1. Willien says:

    Sorry ~~

    the download link lose ( raycastreflection.zip ) ???

  2. David says:

    Excellent work. Thanks.

    I just have one small problem. It seems as though the reflections aren’t going in quite the right direction. For example, I’d reflect off of a cube, perfectly parallel to the ground and after the first collection, it would start to angle down towards the ground. I’m guessing that there’s something strange that I don’t understand with the hit.normal which causes it to do something funky. Any guidance?

    • David says:

      Nevermind. Figured it out. I think the problem was in the first loop above. I believe that

      inDirection = Vector3.Reflect(hit.point,hit.normal);

      should be

      inDirection = Vector3.Reflect(ray.direction, hit.normal);

      Making that change made it behave like I expected. Kudos again for a great explanation!

      • DimasTheDriver says:

        Thanks for pointing that out! Sorry for the late reply. I have just added the necessary alterations to the post and source project. Also, the source project has been upgraded to Unity version 3.4.2f2.

  3. Fernando says:

    Hi, I`m having a problem while trying to raise the nReflection to 3 or a higher number. I think the problem is with the LineRenderer, because the last element of it stops following the raycast and always go to a fixed position (usually in the middle of the scene). Does anyone know how to solve this problem?

  4. Rosa says:

    Hello~ this’s awesome tutorial ! it help me a lot :)
    but one more question, can I Detect the only first hit object tag name, then decide to reflection or not?

    • DimasTheDriver says:

      Yes, you can. You will need to compare the tag of the first game object returned by the first ray intersection. You can obtain it from the RaycastHit struct, from the transform member variable, for example:

      if( hit.transform.CompareTag(“expectedTag”) )
      {
      //… do something
      }

  5. cariaga says:

    thanks this is just awesome

  6. vijay says:

    Thanks a lot man…….. This is just awsome, you gave me desired solution….

  7. Wabo says:

    Wow holy snap, I’m so glad I ran into this solution, this will fix 2 of my problems: using a preview angle shot, and being able to bounce off an object in an angled direction. Thanks alot, this is very very awesome!

  8. mosf says:

    hey! SUPERMEGAULTRA thanks for this!!!
    is there anyway to tell the ray to bounce on certain targets and avoid bouncing over others?

Leave a Reply to mosf

Post Comments RSS