Unity3d 射线反射

2023-01-31 02:01:40 unity3d 反射 射线

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

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:

view plaincopy to clipboardprint?

  1. using UnityEngine;  

  2. using System.Collections;  

  3.   

  4. [RequireComponent (typeof (LineRenderer))]  

  5.   

  6. public class RaycastReflection : MonoBehaviour  

  7. {  

  8.     //this game object's TransfORM  

  9.     private Transform GoTransform;  

  10.     //the attached line renderer  

  11.     private LineRenderer lineRenderer;  

  12.   

  13.     //a ray  

  14.     private Ray ray;  

  15.     //a RaycastHit variable, to gather informartion about the ray's collision  

  16.     private RaycastHit hit;  

  17.   

  18.     //reflection direction  

  19.     private Vector3 inDirection;  

  20.   

  21.     //the number of reflections  

  22.     public int nReflections = 2;  

  23.   

  24.     //the number of points at the line renderer  

  25.     private int nPoints;  

  26.   

  27.     void Awake ()  

  28.     {  

  29.         //get the attached Transform component  

  30.         goTransform = this.GetComponent<Transform>();  

  31.         //get the attached LineRenderer component  

  32.         lineRenderer = this.GetComponent<LineRenderer>();  

  33.     }  

  34.   

  35.     void Update ()  

  36.     {  

  37.         //clamp the number of reflections between 1 and int capacity  

  38.         nReflections = Mathf.Clamp(nReflections,1,nReflections);  

  39.         //cast a new ray forward, from the current attached game object position  

  40.         ray = new Ray(goTransform.position,goTransform.forward);  

  41.   

  42.         //represent the ray using a line that can only be viewed at the scene tab  

  43.         Debug.DrawRay(goTransform.position,goTransform.forward * 100, Color.magenta);  

  44.   

  45.         //set the number of points to be the same as the number of reflections  

  46.         nPoints = nReflections;  

  47.         //make the lineRenderer have nPoints  

  48.         lineRenderer.SetVertexCount(nPoints);  

  49.         //Set the first point of the line at the current attached game object position  

  50.         lineRenderer.SetPosition(0,goTransform.position);  

  51.   

  52.         for(int i=0;i<=nReflections;i++)  

  53.         {  

  54.             //If the ray hasn't reflected yet  

  55.             if(i==0)  

  56.             {  

  57.                 //Check if the ray has hit something  

  58.                 if(Physics.Raycast(ray.origin,ray.direction, out hit, 100))//cast the ray 100 units at the specified direction  

  59.                 {  

  60.                     //the reflection direction is the reflection of the current ray direction flipped at the hit normal  

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

  62.                     //cast the reflected ray, using the hit point as the origin and the reflected direction as the direction  

  63.                     ray = new Ray(hit.point,inDirection);  

  64.   

  65.                     //Draw the normal - can only be seen at the Scene tab, for debugging purposes  

  66.                     Debug.DrawRay(hit.point, hit.normal*3, Color.blue);  

  67.                     //represent the ray using a line that can only be viewed at the scene tab  

  68.                     Debug.DrawRay(hit.point, inDirection*100, Color.magenta);  

  69.   

  70.                     //Print the name of the object the cast ray has hit, at the console  

  71.                     Debug.Log("Object name: " + hit.transform.name);  

  72.   

  73.                     //if the number of reflections is set to 1  

  74.                     if(nReflections==1)  

  75.                     {  

  76.                         //add a new vertex to the line renderer  

  77.                         lineRenderer.SetVertexCount(++nPoints);  

  78.                     }  

  79.   

  80.                     //set the position of the next vertex at the line renderer to be the same as the hit point  

  81.                     lineRenderer.SetPosition(i+1,hit.point);  

  82.                 }  

  83.             }  

  84.             else // the ray has reflected at least once  

  85.             {  

  86.                 //Check if the ray has hit something  

  87.                 if(Physics.Raycast(ray.origin,ray.direction, out hit, 100))//cast the ray 100 units at the specified direction  

  88.                 {  

  89.                     //the refletion direction is the reflection of the ray's direction at the hit normal  

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

  91.                     //cast the reflected ray, using the hit point as the origin and the reflected direction as the direction  

  92.                     ray = new Ray(hit.point,inDirection);  

  93.   

  94.                     //Draw the normal - can only be seen at the Scene tab, for debugging purposes  

  95.                     Debug.DrawRay(hit.point, hit.normal*3, Color.blue);  

  96.                     //represent the ray using a line that can only be viewed at the scene tab  

  97.                     Debug.DrawRay(hit.point, inDirection*100, Color.magenta);  

  98.   

  99.                     //Print the name of the object the cast ray has hit, at the console  

  100.                     Debug.Log("Object name: " + hit.transform.name);  

  101.   

  102.                     //add a new vertex to the line renderer  

  103.                     lineRenderer.SetVertexCount(++nPoints);  

  104.                     //set the position of the next vertex at the line renderer to be the same as the hit point  

  105.                     lineRenderer.SetPosition(i+1,hit.point);  

  106.                 }  

  107.             }  

  108.         }  

  109.     }  

  110. }  

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:

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.


相关文章