- 最后登录
- 2014-10-23
- 注册时间
- 2011-7-19
- 阅读权限
- 90
- 积分
- 81303
- 纳金币
- -1
- 精华
- 11
|
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 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 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:
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 refletion direction is the reflection of the current hit point flipped at the hit normal
inDirection = Vector3.Reflect(hit.point,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***nning, 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***ns
nReflections+1
times for each frame, meaning that, for this example, it will***cute three times for each game update. This loop has basically a
if-else
block that checks whether if it’s first iteration is being***cuted (line 55). Case that’s ***e 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 hit position 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***nning, 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. On top of that, the reflection is calculated differently: instead of using the hit point as the first parameter for the
Vector3.Reflect()
method, the
inDirection
is used (line 90). This happens because the first reflected ray is being projected at in the same direction defined by
inDirection
. Trying to calculate the reflection using the
hit.position
again would result in the reflection of the reflection, which is not what we want.
That’s it! Here’s how it look like:
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.
|
|