Part 3 — Fire & Forget: Creating a Smart Homing Missile for your Unity Game Project!
Objective: Implement a new weapon to your player’s arsenal in your Unity 2D Game Project. Make it smart. It should be able to detect, track and destroy any enemy game objects present in the game scene.
Part 3 — Creating the guidance system of the Homing Missile.
Now that everything is in place, we need to build the script to translate the missile toward Enemy targets. Go ahead and create a new C# script and name it “HomingMissilePlayer”. Attach this script to the PlayerHomingMissile Prefab.
At the very top before the public class declaration, type the following line.
While not necessary to include this, it does reduce any chances of errors while setting up your Game Objects to which this script will be attached. For more information, you can look at the following Unity Scripting API link.
We then set up a few variables. The first one is of type Rigidbody2D named _rb. We create a public Game Object to hold the explosion Prefab (homingMissileExplosionEffect). Create another private Game Object to store a reference to the nearest Enemy ship (_closestEnemy). We then set two private floats to hold the speed of the homing missile (_missileSpeed) and the speed at which it will turn towards the closest enemy (_rotateSpeed).
We then store a reference to the Game Object’s (missile) 2D Rigidbody into _rb.
In Update(), we start by checking to see if we have an Enemy Game Object stored in _closestEnemy. If it’s null, we run the FindClosestEnemy() method and store the results in _closestEnemy. If it’s not null, we then execute MoveTowardsEnemy().
Otherwise, we simply translate the missile Game Object up at half its set speed (_missileSpeed) until it reaches the upper limit of 13 along the Y-axis and self-destructs (once off the Game Scene in the upper limit).
We start the FindClosestEnemy() method by “trying” a block of code. Within that block of code, we need to set up an array of type Game Object called “enemies”. This is where we will store all game objects found that have the Tag “Enemy” (FindGameObjectsWithTag).
We then create a new variable of type GameObject, call it “closest” and set it to null (empty). Another variable of type float called “distance” will store the distance between the missile and the Enemy ship, and for now, we set it to infinity. The last step is to store the transform position of the missile into a variable of type Vector3 called “position”.
We then cycle through each Game Object in the “enemies” array and store it in the variable “enemy”. We then do a comparison of the Game Object stored in “enemy” and the missile’s “position”, and save the calculated difference into a variable of type Vector3 named “diff”.
Once we have that “diff” value, we get its Squared Magnitude and save it into float “curDistance” which represents the current distance (squared length of the vector) between the missile and the Enemy Game Object from the array. To read more about the Square Magnitude, follow the following link to the Unity Scripting API.
We then need to compare the calculated current distance (curDistance) to the stored distance value (by default was infinity). If the current distance between the missile and the “enemy” Game Object from the array is less than that stored in the distance variable, that Game Object stored in “enemy” is now computed as the closest Enemy and is stored in the variable “closest”. At the same time, curDistance replaces the value stored in “distance”; this now becomes the new baseline for comparison.
We continue this process, looking at each Game Object stored in the “enemies” array until we finally return the nearest Enemy Game Object stored in “closest”.
If an error occurs while cycling through the array (inside the “try” block of code above), we “catch” that error and return a null value.
I know I struggled a bit working through this last bit of code. Just go through it a few times, and it will begin to make some sense. You look at the tags, store them in an array, then cycle through the array computing the distance between the missile and each object in the array. The closest object wins the prize and gets destroyed first. Sounds much simpler this way :)
So now that we have identified the nearest Enemy, what do we do? We get the missile to home in on the target, that’s what we do!
We start by storing the difference between the nearest Enemy and the missile’s Rigidbody2D position into a variable of type Vector2 named “direction”. You use (Vector2) to cast _closestEnemy.transform.position Vector3 into a Vector2. This tells us the direction from our missile to our target, the direction in which we need to turn to face the Enemy ship.
You then take “direction” and normalize it. What does this mean?…
If you normalize a vector then it will scale all of the values down until the biggest is a value of 1. This is typically used for when you want to get a direction to something and don’t want the distance included in the vector so you normalize it to only have the direction. The magnitude of the vector is aka the length.
In other words: to make the calculation simple and clear, we need to normalize the vector to sets its length to “1” without changing its direction.
We move the missile by taking the missile’s Rigidbody2D (_rb) velocity and moving it forward by using transform.up, multiplied by the _missileSpeed. Problem is, doing so does only that: moves it forward (up). What we want is for the missile to change direction, track, and home into an Enemy ship. This is done by using the “Cross Product”.
Now, what is the Cross Product? It is a mathematical function that can be applied to two 3-dimensional vectors. This function will “spit out” a third vector which is orthogonal (perpendicular) to the two original input vectors. Unity’s Scripting API discusses Vector3.Cross here. The cross product is useful in that it helps us figure out which way our missile needs to turn, and by how much, in order to face the Enemy ship.
We need to get the Vector3.Cross between the direction we need to face to aim at the target (direction), and the direction the missile is facing (transform.up). Since these two vectors already occupy the X-axis and the Y-axis, the Cross resultant vector goes on the Z-axis. We store this result into float rotateAmount.
The last step is to rotate our Rigidbody2D by setting its angular velocity to the product of -rotateAmount and the _rotateSpeed.
Note: -rotateAmount is not a typo! You need to use the negative value of rotateAmount, or else the missile will rotate away from the nearest target.
Unless the homing missile finds a target or simply flies in a straight line off the game view window when no targets are present, the MissileSelfDestruct() coroutine simply destroys the homing missile 2.5 seconds after instantiation.
The entire script should look like this:
The result is shown below. The Player, once the missiles are collected, will have at their disposal an awesome-looking homing missile that will track and destroy Enemy ships.
Over the past few days, we’ve looked into the implementation of a homing missile system for our GameDevHQ 2D Space Shooter Game Project in Unity. At this point, you may wish to explore other tweaks such as having any remaining missiles detach and float from the Player ship when you lose a life, ready to be collected once more when the player respawns in the Game Scene.
Use your imagination and have fun. Thanks for reading :)