I am working these days in prototyping a simple puzzle game where the player needs to direct a laser beam towards a target using mirrors. The map should contain some obstacles that stops the light from moving forward.
To break down that problem we need first to write it in simple words. How do we reflect shoot the laser from a point and make it move and react to certain objects and reflect?
Lets break down the problem:
- We need to have a start point of the laser. That point can be the object where the laser will be shot from.
- We need to track the laser in the direction it is moving towards and see whether it will collide with an object or not.
- If the laser hits an object tagged as a mirror, we need to do two things; we need to calculate the direction of the reflected beam, and we need to store the point of impact in the mirror as a point within the laser beam.
- Repeat point 2, 3 and 4 until we either hit an object that is not a mirror, or not hit anything at all. If the laser does not collide with any thing, we will set its end point to be a point that is out of the screen bounds to give the illusion that the laser is shooting to infinity.
With that out of the way, lets examine the code. First, we will render the laser using the LineRenderer component, this will give us big fixability into drawing the line with multiple points. To make this super easy, I created a script and called it LaserBeam. This class does not inherit from MonoBehavior and its sole job is to calculate the points of the laser and draw it on the screen. I also decided to create the component using the code. Here are the variables of the class:
As you can see in figure 1, the most important variable is the List of laser indices this is where all the points of the line renderer will be store to draw the line on the screen.
Then, I created the class constructor that initializes the object and starts calculating the path of the laser.
The constructor initializes and adds the LineRenderer component into the game object and then calls the function CastLaser() that shoots the laser and calculates it path.
As demonstrated in figure 3, the first thing we need to do is to store the current point we are at in the list of indices of the laser. At the very beginning that point will be the transform.position of the game object shooting the laser. They we need to create a ray starting from that position and shooting upward for the prototype the initial direction is transform.up because I want the laser to be shot upward. Then in the if statement, we do basic ray casting and we check what we hit. If we hit anything, we get into the CheckHit() function, if not, we add an index to a very far point and then draw the ray to give the illusion of shooter the laser to infinity.
The actual magic happens in the CheckHit() function; here is the code of the function:
The first thing we need to do is to know where we collided with. Did the laser collide with a mirror object or a wall. If the laser collided with a wall, we add the collision point as an index in the list and draw the laser and that's it.
If the laser hits an object that is tagged as a mirror, then we need to calculate the new direction of the laser. Thankfully, and without getting into complex math, Unity provides a great function that reflects a vector off a plane defined by a normal. We already know the direction that we came from, and RaycastHit can provide the normal of the point where the laser collided. Using those two pieces of information we can calculate the new reflected direction using Vector3.Reflect(). So, we store the point of collision in the list of indices and then calculate the new direction using the Vector3.Reflect() function. Then we input the new point and the new direction and call CastLaser() again; and thus, we are recursively drawing the line. If you examine CastLaser() and CheckHit() functions you can see that the exit criteria from the recursion is either we hit a wall, or hit nothing at all.
The UpdateLaser() function is straight forward as we need to iterate through the entire list of indices and just add them all into the LineRenderer component. Unity will automatically draw the line for you once you add the indices.
The laser game object it self contains a different script that inherits from MonoBehavior, that scripts destroys and creates the laser beam in every frame. With that, I have a functional reflection system that I can use in my game!