My objective and challenge for this article, is to make an enemy type that will fire at player powerups when they are in front of it. All of my powerups and the vast majority of my enemies spawn off screen to the right, and then move across the screen to the left. It would be pretty easy to have enemies shooting left at powerups as they all travel in the same direction at various speeds, but I wanted to take a more dynamic approach. I decided to use the Jellyfish enemy, as it spawns to the bottom of the screen and comes into play at different angles. The Jellyfish rotates towards the player if it gets close enough, so this feature will also be used to have it scan the screen for powerup targets as it rotates.
Before I get into firing at powerups, I’ll need a new projectile to shoot! I made this quick vector asset with a spiral pen tool, a circle shape and some smooth gradient fountain fills.
After bringing the new spiral shot into Unity as a Sprite, I rename it and give it my trusty rotation script to rotate it clockwise as if flies. It’s then dragged into the prefabs folder.
This game object is only for the visual of rotating the sprite, so I create a new empty object to be the parent of the sprite and act as the physical projectile. I rename this object Spiral Projectile, and then add the Rigidbody2D, Circle Collider2D and projectile Script components. This new spiral shot takes a projectileID of 4 as an assignment and has the sprite prefab attached as a child.
The enemy will need a reference to the new spiral projectile in the enemy class, so I create a game object variable for it with a Serialized Field for assigning it in the inspector. A variable of data type bool is created to dictate if the enemy has fired at the powerup yet or not.
I am going to use Raycasting to find target powerups, so I want to add a LayerMask variable to target the powerup layer only.
After making a new Powerup layer in the inspector, I assign all powerups to the new powerup layer (aside from the player penalty powerup).
Now on the jellyfish enemy prefab, I go into the inspector to assign the Powerup Layer Mask variable to only target the Powerup layer.
Now that my jellyfish enemy type has a reference to it’s new fire power, I can continue on in the shared enemy script. The enemy movement method is called for all enemy types, but the Jellyfish Raycasting method is only called for the enemyID of 1, which is the jellyfish. To save memory on continuous raycasting in update, I will switch this bool as soon as the enemy hits a target powerup. This way once the enemy has fired at the powerup, it will stop raycasting for targets until the bool is flipped back to false. I originally had this as a two part conditional statement if (_enemyID == 1 || _hasFiredAtPowerup == false), but the enemy was still raycasting after the bool flipped to true. Using the nested if statements here seems to do the trick.
Raycasting and colliders use Unity’s physics engine, so there are times when it’s best to use FixedUpdate for more accuracy to the Raycast. Update and FixedUpdate do not run at the same speed. Being that I am only getting information with the raycast and not changing anything physics related in the game universe, it’s ok to put it here in update.
Here in the raycasting method, the very first line sends the raycast up from the central position of the object doing the casting. The 20f designation is how far the cast will go. This is enough for my game screen to cover the farthest distances corner to corner. The layermask variable is plugged in so the object only casts on the target powerup layer. The collider being hit is first null checked to see if it’s still active, and then the collider is checked to see if it has the tag of Powerup. If the raycast hits a powerup, a Debug.Log message of “We detected a powerup!” is sent to the console. I introduce a local Transform variable just to get the name of the transform being hit in the next Debug line. To be honest, I was going to GetComponent that transform here, but I decided to take an alternate route and left the local variable for info grabbing. Finally, the spiral projectile is instantiated at the position and rotation of the enemy jellyfish. More about why keeping the same rotation is important in a minute. A coroutine is started to handle the cool down method on the raycast, and lastly a Debug.Drawray command is sent to visualize the raycast (raycasts are otherwise invisible).
The coroutine turns the has fired at powerup bool to true, which will keep the jellyfish from raycasting until it’s turned back to false. This happens after 5 seconds, so the jellyfish could at most fire every 5 seconds if there was always a powerup in front of it.
Now that the jellyfish enemy is taken care of, I can move into the shared projectile class. The speed and ID variables apply to all projectile types. This powerup targeting projectile gets additional variables for a Transform (target poewrup), a bool (has target powerup), a Vector3 (target powerup direction) and a LayerMask for the powerup layer.
In void update, the tusk movement method is called for all projectiles. If the projectile has an ID of 4, it can enter the method to use raycasting.
I will cover the raycasting method first, because order of operations here is crucial to avoid a null reference error. This projectile raycast is almost identical to the one on the enemy. As soon as the enemy raycast hits a powerup, it instantiates the projectile at the same rotation, which enables the projectile to immediately hit the same target with this raycast. The main difference to note is here I assign the targetPowerup variable to use GetComponent on the powerup Transform being hit. This will allow me to store the transform of the powerup and target it in the enemy movement method. The final line flips the bool of has target powerup to be true, which will prevent the projectile from raycasting for more target poewrups.
Finally in the movement method, I check to see if the projectile ID is 4. I then check to see if has target powerup is true, because without a target I am going to throw an error. I then check if the target powerup is null, because again, I don’t want any errors! If everything checks out, the projectile subtracts the target powerup Transform position from it’s own position to come up with a following direction. This will give the projectile a homing type of behavior to chase and eventually destroy the powerup. If the player can get to the powerup and collect it before the projectile hits it, the else part of the argument kicks in. Being that I was previously updating the target powerup direction variable, the projectile will then continue moving straight in the last known direction of the powerup (without errors!) after it is collected. The blowfish spine method at the bottom destroys the projectile if it leaves the game screen.
Here is the player moving and using the special ability to draw powerups to collect it before the projectile destroys it.
I also added an OnTriggerEnter2D method to destroy the powerup and then the projectile on collision.
Here is the enemy shooting down the powerup. Thanks for reading!