Phase 2: Homing Projectile
In this challenge, I have to create a projectile that would lock on the nearest enemy and home in on it to destroy the enemy.
Create a homing projectile that seeks the closest target.
Turn into a rare powerup.
I’ve broken this challenge into three sections.
- The Player that stores the missile ammo and firing.
- The Powerup that replenishes the missile ammo count.
- The Missile itself will select and home on the closest enemy.
The Player Logic
Our player script has been responsible for the movement, weapons and giving the actual powerups. Creating the logic to fire the missile was the first and most important part of the challenge. The reason being that it allows us to start testing the homing missile and make adjustments.
Opening the Player script, I started by adding the variables that I’d need. I created a private GameObject named “_missilePrefab” that will store our missile prefab. I also created a bool variable named “_canFireMissile” to check if we can fire a missile and lastly a float variable called “_missileCooldown” with a value of 2f. The cooldown is to prevent the player from firing missiles too rapidly.
Before getting into the rest of the Player logic, I quickly created the UI elements and in the UIManager script, I created an array called “_MissileImg” that stores all of the UI missiles and created a Sprite variable called “_missileActiveImg” and “_missileInactiveImg” that would replace a missile image with a greyed-out one to indicate a spent missile.
In one of my previous articles, I created a secondary powerup called the Orb of Destruction and used the right mouseclick as the button to fire this weapon, but since this was more of a bomb than a weapon, I decided to rebind that key to the spacebar and will be using the right click to fire the homing missile.
The Player script has a Fire() function and under our primary weapon, I created the logic to launch the homing missile. I won’t be explaining in great detail how I’ll be creating that logic since I’ve covered that numerous times but instead want to focus on the homing missile.
The Powerup Logic
Still editing the Player script, we’ll be adding the first powerup logic. From previous articles like this one, I created the powerup system to be very modular and it’s very easy to add more powerups. I highly recommend reading that article since I’ll just briefly covering this logic.
In the ActivatePowerup() function we’ll be creating a new case in the switch that would regenerate the “_missileCount” to 6.
Our powerups have their own script that’s responsible to give the correct powerup and is also responsible for the powerup to move downwards. This script also needs to be updated with another switch case.
The last part of the powerup is to create the actual powerup. With every new powerup, I’d just use an existing prefab and unpack the prefab in the hierarchy to allow me to save this powerup as a new prefab. Once I’ve unpacked the powerup I changed its ID to 7 and replaced the image to represent the powerup that will be given.
The Missile Logic
I’ve left the best and hardest part of this challenge for last. The homing missile is made up of two important parts. The first part is responsible to select the closest enemy on the screen and the second part is flying towards the selected enemy. The first part was honestly the hardest since the math was strong with this one.
To create the missile logic I created a new script called HomingMissile and opened that with Rider. I needed to create quite a few variables and will go through each one explaining what it does.
The first variable I created was a float called “_speed” for the missile’s speed and gave it a value of 10f. Next, I created another float called “_rotateSpeed” and set that value to 250f. This variable is responsible for how fast the missile will rotate towards its target. I also created a variable for our RigidBody2D and named it “_rigid”. Lastly, I created two Transform variables. One named “_target” which is the target of the missile and the second one is a secondary target called “_nullTarget”. This is to ensure that when there are no targets that the game doesn’t throw null errors. The null target is an empty GameObject that’s set off the screen. Please note that this was a cheap fix and will change this later.
I then created a function called SelectTarget() that will be used to select a new target. In that function, I ensure that our “_target” has nothing selected by setting it to null. Then I create a new float named “closestTarget” and set it to MathF.Infinity. Next, I get the missile’s current position using a Vector2 named “currentPos”, and next, I’m getting all of the potential targets. All of the enemies have a tag called “Enemy” and I’ll be using it to get all of the enemies in the scene by using an array called “possibleTargets” and getting all of the objects with the tag “Enemy”. At this point, the bulk of the initial set-up is done and now we need to loop through each enemy and compare it with the missile’s position. To do that we use a foreach loop, and we’ll use define a GameObject called “possibleTarget” in our array called “possibleTargets”.
Inside the foreach loop, we'll create another local Vector2 variable called “directionToTarget” and we’ll minus the “currentPos” from our possible target's position. Next, we’ll be creating yet another local float variable “distanceToTarget” and set it to “directionToTarget.sqrMagnitute”. At this point, we can check if our “distanceToTarget” is less than our “closestTarget”. If that’s true then our “closesttarget” equals the “distanceToTarget”. And lastly, our “_target” becomes our “possibleTarget”.
I hope you’ve been able to follow what I’m explaining. It was quite complex for me to figure out but it turned out well. In our Start() function, we’ll call this function to get a target. Now it’s time to work on our movement. Since this is a very simple script that doesn’t have tons of things to update at once I decided to add the movement logic straight to our Update() function.
This script uses the exact functionality that our Enemy Missile is using except for the targeting system. In that article, I covered the basics of crosspoints and how to hone in on a selected target. For this missile before the missile starts moving we check that our target is not null. If the target is null then our “_nullTarget” is selected and the missile will be shot into the air and be destroyed.
The very last thing I added to the HomingMissile script is a standard Destruct() function that all the missiles, enemies and so forth have.
The last thing that needs to be done is actually build out the missile in the hierarchy and attach it to the player.
I dragged the sprite that I wanted to use for the missile to the Hierarchy and attached the HomingMissile Script to the GameObject and renamed it to HomingMissile, Next, I added the Explosion and prefabbed the missile. Lastly, I had to attach the HomingMissile prefab to our player to be able to shoot it.
This was a fairly long article today but the logic of the homing missile was hard to explain although it looks fairly simple as code. I hope you enjoyed this article and I’m looking forward to the next one which will probably span over three or more articles since I’ll be writing about our final boss.