Fun With Rays
Hi there everyone, back with another article based on challenges that are part of the GameDevHQ Space Shooter Phase 2 Challenges. I have four separate challenges, but they all require one basic principle and from the title of the article you’ve guessed that it’s rays or better put a Raycast.
- Create the functionality to support enemy aggression. If an enemy is close to the player, the enemy will try and “ram” it.
- Create an enemy type that knows when it’s behind the player and fires a weapon backwards.
- If a pickup (powerup) is in front of an enemy, the enemy will fire its weapon to destroy it before the player can get it.
- Create an enemy type that can avoid the player’s weapon. When you fire a shot the enemy should detect a shit in range and try to avoid it.
All four of these challenges has the same sort of behaviour, detect something in its range and react on that. Like with every challenge I would do some research beforehand and look at the best solution for the challenge. In most cases, we could use colliders and other things but some are prone to error. The most logical answer in each challenge was a Raycast.
What is a Raycast?
Raycasting is the process of shooting an invisible ray (think like a laser beam, but you can’t see it) from a point, in a specific direction to detect whether it collides with anything. During this process, you can explicitly specify which layers to check for collisions using a bit shift. Raycasting is broken into two distinctive parts for 2D and 3D.
Now you may have seen a word that made no sense and that was the bit shift. In Unity, we use bit shifting to tell the Raycast specifically which layer or layers to check for collisions. In my project, for the most part, we check to see if the enemy’s ray is hitting the player, and apart from that one enemy checks to see if lasers are near the Raycast and lastly another enemy checked to see if its Raycast was hitting powerups. But don’t fret. I’ll be going over each enemy and explain how their logic works.
This enemy needs to check if the player is a short distance in front of it, and increase its speed to ram into the player.
The most common way to cast a Ray is to create a function using a bool. In this function, you need to declare a start position. This is where the Raycast would start. Next, you need a direction, this can be a straight line or at an angle. To create the actual Raycast we use something called Physics2D.Raycast, there we input the starting position, the direction, the distance and lastly the layer that we want to detect collisions. In my project, my player is on a layer called Player, but from the list of layers, the Player is the 8th layer on the list, and therefore we use a bitshift 1 << 8.
Now that we have the actual ray we can’t see it, therefore we need to use Debug.DrawRay. Here again, we’ll use our position direction and distance, but this time we need to specify a colour to use. In this case, I chose to use green.
Our function isn’t completed just yet. We need to set that bool to true or false if that ray collides with our player. Here we use an if statement to check if our ray.collider is not null. If that is true then we return true otherwise we return false.
This on its own doesn’t do much. In our update method, we need to now check if we’re detecting a player by running the IsDetectingPlayer() function and then do something when that bool is true. I chose to create a coroutine where the enemy speed is increased, an audio clip is played and after a short delay, the enemy speed is returned to normal. And to make sure strange things doesn’t happen I created a private bool to check if ramming is active.
As you can see this script isn’t really complicated at all and you’ll be seeing a lot of that bool function during the rest of this article.
In this challenge, we have to create the functionality to check if the player is behind the enemy and if that is the case then the enemy should shoot backwards. Similarly to how we created the Ramming Enemy, we’ll instead create a ray that points up and again we’ll use the bitshift 1<<8 to check for collisions on the 8th layer.
With our direction we used Vector2.up instead of Vector2.down. Everything else is pretty much the same. My reverse laser prefab works just like a regular laser but this one shoots up and not down.
Shooting at Powerups
The next challenge where the enemy has to shoot at powerups work in a very similar way to the ramming enemy. There are just two big differences. This time we’re checking another layer (9) and our ray would be much longer. to be able to detect and react quick enough. Also instead of flying much faster, we’ll be firing off a laser.
As you can see the functionality remains very similar with minor changes.
In this challenge, the enemy should move to the sides to avoid lasers the player has shot towards this enemy. Admittedly this setup does work but I do feel there is room for improvement, but that’s for another day.
The logical solution to this challenge was to create two Raycasts. One on each side to detect which side a laser is being shot. If a laser is passing to the left, the enemy should move to the right and vice versa. But these shouldn’t be straight at 90 degrees, but rather angled at 45 degrees to detect the laser fast enough to avoid the shot. There are many ways to go about this but the easiest was to plus Vector2.down with Vector2.left to get a Ray pointing towards the left and do the same for the right, but instead of Vector2.left, we use Vector2.right. We also needed to change the bitshift to check for collisions on the 10th layer. I decided since I’ll be reusing the bitshift to create a variable this time that both rays will use.
Now a lot is happening in this script, but only because we have two separate Rays to track and because each ray has its own functions to run.
I had a lot of fun working on these challenges and learning about Raycasts and how bit-shifting works. There are other types of rays like a Spherecast but I haven’t been experimenting with it yet. When that time comes you’ll be sure to find an article written on that.