Day 46 — Impovements: homing missile
Hey and welcome!
In this one we’re going to look into implementing a homing missile that will locate and move towards the closest enemy currently on the screen. First let’s create the object for our missile, I’ve recoloured one of the laser objects for prototyping purposes. If you set the tag of the missile to Laser we don’t need to worry about putting in the code for handling the collision with our enemies.
With that done create the script and start adding in the code for it beginning in the Start() method:
GameObject[] _enemies = GameObject.FindGameObjectsWithTag("Enemy");
This will return an array of all objects currently in the scene that have the tag of Enemy. The interesting part about FindGameObjectsWithTag is that if there are no tags matching what you put in it will return an empty array which means there’s no reason to null check this as _enemies will never be null.
Now for the code for our missile behaviour:
if (_enemies.Length == 0)
{
StartCoroutine(DestroyMissileRoutine());
transform.Translate(Vector3.up * _speed * Time.deltaTime);
}
else
{
Transform[] enemyTransform = new Transform[_enemies.Length];
float minDistance = Mathf.Infinity;
float distance; for (int i = 0; i < _enemies.Length; i++)
{
enemyTransform[i] = _enemies[i].transform;
Vector3 vectorTarget = enemyTransform[i].position - transform.position;
Quaternion targetRotation = Quaternion.LookRotation(Vector3.forward, vectorTarget); distance = Vector3.Distance(enemyTransform[i].position, transform.position); if (distance < minDistance)
{
Transform nearestTarget = enemyTransform[i];
minDistance = distance;
transform.position = Vector3.MoveTowards(transform.position, nearestTarget.position, _speed * Time.deltaTime);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, _rotationSpeed * Time.deltaTime);
}
}
}
There’s quite a lot to go over here so let’s start small by looking at the first if statement that checks if _enemies.Length equal 0. When it equals 0 that means that there are no enemy objects currently on the screen that can be added to our _enemies array. In the if statement we have some default movement for the laser along with a coroutine that destroys the object after a couple of seconds.
In the else part of this you’ll recognise quite a lot of the code here from our enemy ramming code with a couple of changes. The first one is that we’re not rotating the target vector like we did for our enemy since the rotation starts out fine as is, so in our LookRotation() we have the targetVector value in there as is, instead of a rotated one. The next main difference is that our MoveTowards() and RotateTowards() are sitting in an if statement that I’ll go over shortly.
As for the unique code let’s start with our variables distance and minDistance. Distance is being used later to hold our Vector3.Distance() value which you’ll recognise again from the enemy ramming and then our minDistance is being assigned a value of Mathf.Infinity which means that it’s an infinite/max value that a float can possibly be.
Now the reason we have this is because we need this next if statement to run at least once. So regardless of what value distance is it will always be less than infinity.
Inside the if statement itself we’re starting off by creating a local variable with a type of transform in order to assign the value of the current enemy transform in the loop. With that done we then assign minDistance to the current distance value which in turn overwrites the infinity value. With that done the homing missile will start moving towards the current enemy transform using our MoveTowards and RotateTowards. Since the missile is moving towards the enemy it means that the distance will always be less than the min distance so this if statement will keep happening every frame until it collides with the nearest enemy.
If you’re not sure why it’s heading towards the closest target let’s pretend that we have 2 enemies that have been passed through this loop and one of them has a distance of 3.0f while the other has one of 5.0f. If the loop starts on the enemy with the distance of 5.0f it will initially start to go towards that enemy transform, however after the loop has gone through that iteration it gets to the next enemy with a distance of 3.0f. Since we’re assigning distance to the min distance on every frame this means that our enemy with a distance of 5.0f is no longer true and so its transform will no longer be passed into the if statement where the movement is handled.
That was quite a lot to go over and is probably the most complicated thing we’ve had to do for our game so far so let’s finish things off nice and easy with this code in our Player script:
private int _missileCount = 1;
[SerializeField]
private GameObject _homingMissile;if (Input.GetKeyDown(KeyCode.F) && _missileCount > 0)
{
FireMissile();
}private void FireMissile()
{
Instantiate(_homingMissile, transform.position + new Vector3(0, 1f, 0), Quaternion.identity);
_missileCount -= 1;
_uiManager.UpdateMissileText(_missileCount);
_audio.Play();
}public void RefillMissileAmmo()
{
_missileCount = 1;
_uiManager.UpdateMissileText(_missileCount);
}
This should all be looking pretty familiar here, this code is handling the instantiating for our homing missile when the F key is pressed as well as some logic for replenishing the missile and updating our missile text in the UIManager script which looks like this:
[SerializeField]
private Text _missileText;public void UpdateMissileText(int missileCount)
{
_missileText.text = "Missile: " + missileCount + "/1";
}
Finally with all that sorted you should have something that looks like this:
That takes care of our pesky dodging enemy pretty well now. The only thing remaining now is to create a new pickup similar to our ammo pickup that replenishes the missile which you should be good and familiar with how to do on your own!