Adding a homing missile powerup
Before we dive in here, I want to give credit where credit is due.
I used two assets from the Unity asset store to add a more polished homing missile to this project rather than really basic placeholder art.
Modular Sci-Fi Weapons | 3D Weapons | Unity Asset Store
Elevate your workflow with the Modular Sci-Fi Weapons asset from Ebal Studios. Find this & other Weapons options on the…
First of all, I used the “ShipKiller” missile model/textures/prefab from this modular sci-fi weapons asset.
Animation Baking Studio (3D to 2D) | Sprite Management | Unity Asset Store
Get the Animation Baking Studio (3D to 2D) package from JYU Dev Studio and speed up your game development process. Find…
Second, this Animation Baking Studio made it super easy for me to convert my 3D model to a 2D sprite.
In my use-case, I simply generate a 2D sprite without animations, but this asset is capable of recording 3D assets in motion and outputting 2D sprites with animations and sprite sheets.
Third, I will once again steal an icon from the Clean Flat Icons asset pack on the Unity Asset Store.
Alright, let’s create a couple prefabs.
Grab the sprite you wish to use as the powerup for our new homing missile and drag it into the hierarchy.
We’ll set the rotation, scale, and color to taste.
Also, don’t forget to set the “Sorting Layer” to Foreground.
Our HomingMissile_Powerup prefab will need a Rigidbody2D, BoxCollider2D, and AudioSource.
Make sure to enable Is Trigger on the collider and set gravity to 0 on the Rigidbody.
Using the sprite I generated from the Animation Baking Studio, I drag it into the hierarchy and configure the settings as shown above.
I also went ahead and manually position the sprite on the left wing of the craft and noted the x value would be -1.3 when that is desired.
We’ll use our HomingMissile prefab to create a second “HomingMissile2” prefab.
This will make it easier for us to position the missiles on the player ship.
For our Homing Missile prefabs to have collision enter events with our enemy game objects, we need to add a collider and a rigidbody as usual.
We’ll need to add the Powerup script to our HomingMissile_Powerup prefab and set the PowerupID to our next available number, which is 8.
We also assign our powerup pickup audio file.
Note: I didn’t want to spend time in photo editing software to rotate the sprite to be oriented as I had desired based on the rotation value I put in above, so I simply put the Z rotation back to 0.
The Z rotation not being 0 caused issues with our move down code in the Powerup script that wasn’t worth the time to address.
Modifying the Player script
I want the player to be able to pick up a maximum of 2 homing missiles, so I’ll need a variable to keep track of how many the player has at any given moment.
I also need to add a prefab Game Object each of the homing missiles to instantiate when the powerup is picked up and show it on our player’s ship.
Then we’ll need a GameObject array to hold the 2 instantiated homing missile game objects for movement and destruction purposes.
When we fire our missiles, we’ll need a speed value as well.
We make sure to drag our HomingMissile prefab to our Player prefab for the newly create HomingMissilePrefabGO field.
And do the same for our HomingMissile2 prefab on the Player prefab.
We’ll need a couple methods, one public and one private, for our homing missile mechanics.
The first is public, AddHomingMissile(), so that our HomingMissile_Powerup can call this method on pickup.
The second will be for firing the missiles, but we’ll deal with that once we have our powerup spawning and our missiles loading onto the player ship working.
In AddHomingMissile() method, we check that we have less than 2 missiles, or there is no reason to attempt to add another missile as that is our max amount.
Depending on how many missiles we currently have, represented by _currentHomingMissiles variable, we’ll add either the 1st or the 2nd HomingMissile prefab to our player ship.
As you can see I’ve commented out some positioning code.
Initially I was going to use just the one prefab and move it into position after a separate instantiation.
For whatever reason, it just wasn’t working correctly so I took the easy approach and created a 2nd homing missle prefab with the correct position on player ship hardwired into the prefab.
Last, we update our _currentHomingMissiles count.
Modifying the Powerup script
I want to make sure I’m always adding in a comment with the new options for _powerupID variable for future convenience.
In the OnTriggerEnter2D() method of our Powerup script, we want to add in case 8 for our homing missile which simply calls the player script to fire its AddHomingMissile() method.
Modifying the SpawnManager script
In our SpawnManager script we need to add variable to hold our new Homing Missile powerup game object prefab.
Let’s make sure to assign our HomingMissile_Powerup to the SpawnManager script component in the appropriate field.
I’ve added another coroutine based on the prior ones.
As mentioned before it is probably unnecessary to have a separate coroutine for each powerup, but I’m focused on functionality right now and code cleanup can come later to make this more elegant.
We make sure to StartCoroutine our new powerup routine in the StartSpawningWave1() method which is triggered on asteroid destruction.
As this will be a powerful powerup, the spawn intervals between 45 and 55 seconds is much longer than most.
At this point, you should be able to run the game and the new homing missile powerup will spawn correctly, then load the appropriate sprite first on the right side of the ship and then the left side of the ship on 2nd powerup pickup.
Now, we need to revisit our Player script and make these missiles do something!
Back to the Player script
Quite a bit to break down here, even though we aren’t even firing our missiles yet!
So let’s start with our objective here.
Before I even fire the missile, my objective is to get the correct enemy game object as the target.
And to determine what enemy game object should be the target when multiple enemy game objects exist, I’ve introduced an “enemy strength” variable on the enemy script.
Modifying the Enemy script
And so that we can access this number from the player, I’ve also added a GetEnemyStrength() public method that will return the value.
To determine what enemy is stronger than another with all of these random generated enemies, I’ve simply just implemented an additive point system basically.
If the fire type is better than another, it gets more points.
If the movement type is better, more points.
Got a shield? More points
You get the idea, and all of this takes place in our Start() method.
We’ll also need to update our OnTriggerEnter2D() method to check for the “Missile” tag.
Of course, checking for a missile tag isn’t going to work if we don’t create that tag and apply it to our missile prefabs!
Back to the Player script (again)
So we create some variables, including a GameObject array to hold all of the enemy game objects currently on screen which we use FindGameObjectsWithTag() to populate.
Then we Foreach through every enemy and check to see if it was more powerful than the last.
If it was, this is our new targetEnemyGO.
If it is equal strength, we check to see if it is closer to the player using Vector3.distance() and assign it to targetEnemyGO if it is.
Make it go boom
After getting a working version, my list of variables for the player script grew significantly as shown above.
The final approach I went with does not allow the 2nd missile to fire while the 1st missile is active and still alive.
The issue was both missiles would target the same high priority enemy and when that enemy died, there was a 2nd active missile that just sat in space with no target waiting for some enemy to bumble into it.
I thought of doing a bubble sort priority list of the enemies, but the bottom line is this game wasn’t designed with a manager script for my enemies and I didn’t want to redo the ground work to frame it back up that way.
So, simply I use _fireHomingMissileCooldownActive to track if the player is allowed to fire a missile or not.
On enemy death, it calls a new method in the Player script, EnableHomingMissileFiring(), which toggles this Boolean value back in.
The Enemy Script side of things
Here is the updated OnTriggerEnter2D method to reenable the Homing missiles once one has collided and destroyed this enemy and itself.
Back to the Player script
To prevent both missiles from firing simultaneously due to checking for the player input in the Update() method every frame, I put a 1 second cooldown in effect as well.
This is called from the FireHomingMissile() method.
It took longer than I’d like, but this is what I came up with as a working product for the FireHomingMissile() method.
So, we still have our enemy strength targeting priority as shown before, but now we actually do something with that information.
After our ForEach loop we check that there is a valid targetEnemyGO and that we have > 0 missiles on the player ship.
If we have 2 missiles, we fire the second equipped missile (left side) first and start our firing cooldown coroutine.
If just 1 missile, we fire the only missile on the right side of our craft and trigger the cooldown.
We also want to make sure that we unparent these missiles, or they will still be affected by the player ship’s movement.
Once they are fired, they need to be independent.
Transform.parent = null is the straightforward approach to handle this.
Now that we have a missile detached from the player’s ship and aimed at a target enemy, we trigger the MoveHomingMissiles() method by setting the Boolean _moveHomingMissiles to true.
In our Update method we populate our _allEnemiesGO array so that it is always up to date.
We check if any missiles have been fired and need to move towards a target, and if so we call the MoveHomingMissiles() method.
We also check for player input, “Fire2”, or right mouse button in my case to fire the missiles.
Even if the player is clicking though, we won’t let them fire as long as Homing missiles are set to be on a cooldown either due to the 1 second delay or because a missile is already in flight and has not been destroyed yet.
There is so much copy paste code here going on.
A more elegant approach would break the copy paste code into another method and pass the necessary variables back and forth, but I’m pretty burned out just getting it to this working point.
Feel free to improve this!
Basically we have based our missile movement on the code used in our Enemy script for ramming the player when in proximity.
I couldn’t explain the rotational code beyond the “180” value was originally 90 and you just have to play with that value in increments of 90 degrees until it starts moving in the correct direction.
I believe this has to do with my missile sprite being horizontal in its original form while being used vertical in the game.
This method was originally written with the thought process that 2 missiles could simultaneously be out in the air tracking down enemies but that approach had issues as stated above.
So really, the original if statement for both _missile1Fired && _missile2Fired is probably unnecessary.
All of these code blocks within the if statement for fired are essentially the same, so we’ll just talk about the “else if (_missle1Fired)” block specifically and know that it applies to everything above it.
We check that we have an assigned target for this missile and that this missile has an active game object.
We then check whether the enemy is on the left or right of our missile.
Due to copy paste code, the variable says “playerOnRight” but this actually should be “missileOnRight” which I’ll correct later.
This then determines the value needed for the “towardsEnemyDirection” variable later in the code.
We do the same for the vertical.
Assign towardsEnemyDirection, use our known working rotate code even if I don’t fully understand the math to it, and then we use Transform.translate to move the missile after the enemy game object.
This updates every frame, but is smoothed out with our use of Time.deltatime and we can customize the speed with _homingMissileSpeed.
After renaming all of my playerOnRight variables to missileOnRight, I’m not even sure this variable is used.
Try commenting this out and you may be able to remove some of this code bloat, but I’m done for tonight.