GameDevHQ — Day 18 — Enemy Movement Patterns and Negative Pickups
Today’s work was a continuation of the development of different enemy movement behaviors and types for Phase II: Core Programming. Where I last left off yesterday I was able to implement a a basic variation to the enemy movement patterns where the enemies can travel in varying diagonal angles. today I expanded on that design to add in an enemy movement pattern that moves the enemy in a serpentine motion as well as a quick re-do of the spawning logic to select which enemy to spawn. The change to the spawning logic was simply to replace the layered if-else statement that I was using for a switch-case statement as I realized that the condition I was using for the if-else statement was simply checking the value of an integer variable would be a valid condition for a switch statement which would be a more efficient alternative. The switch worked with minimal hiccups and I can now use this setup to more easily build off of and add more spawning options as we go on.
Enemy Movement Patterns
For the movement change the goal was to allow an enemy to fly down the screen while moving from side to side. I tried a few different methods to solve this problem with the first using a coroutine to handle the delay between moving to each side. This first attempt ran a coroutine that ran a modified version of the diagonal movement code shown below:
The created coroutine would use the value -0.25f in place of the -0.75f value shown above to move the enemy to the left, then it would use a WaitForSeconds(1.5f) to keep this movement pattern. After the wait timer runs out the value for horizontal axis translation switches from -0.25f to 0.25f to switch the movement towards the right. This worked somewhat on early testing but the issue that I found was due that when the coroutine would switch to the second motion pattern after the wait timer there was a significant increase in speed by the enemy as it veered to the right. This was an inconsistent and unintended behavior so I had to make some edits.
In my second attempt I changed the coroutine from using a WaitForSeconds() function to using a while(true) loop that ends with yield return null to translate the object using a Sine arc pattern.
This method involved replacing the horizontal value with the command Math.Sin() using both time scale multiplied by the value 0.25f to generate a fluctuating curve that the enemy will travel along as it also flies downwards on the screen. This method did achieve the desired result of a back and forth curved movement in the enemy but the issue I ran into was that altering the value for -0.25 to other amounts led to drastic changes in the arc sizes. Setting the value to 1 or higher led to arcs that would veer the enemy off the screen if they spawned off to the sides. However the issue with setting the value to lower values such as -0.25 led to smaller arcs but the side to side movement was very slow. I kept the lines of code sectioned off for this method as I would want to tool around with the results that could happen if I modified the Time.time parameter given to Mathf.Sin.
My third attempt that I settled on a result dropping the use of a coroutine and to simply use a Translate function within a method called in the Update() section. The difference in this approach is that Mathf was still used to calculate the horizontal axis movement but instead of Mathf.Sin() I used Mathf.PingPong() which is a method that fluctuates between given minimum and given maximum values.
To allow this function to be easily changed instead of hardcoding in the minimum and maximum positions I used the variables “curveMin” and “curveMax” to handle these parameters and set their default values to -1.25 and 1.25 respectively. This method was successful and allowed the enemy to travel back and forth at a uniform pace that was easily adjustable in both speed and curve size.
Creating New Enemy Types
One of the requirements given for this section is to develop a new type of enemy with different qualities besides just its movement pattern. My first idea for this challenge was to try and make an enemy type that consisted of three enemies that code break apart from each other. The current version of this enemy type is one where there is a main enemy that is the same size of the regular enemies but is flanked by two smaller enemies who can each fire a single laser. To further distinguish this enemy type I also tinted the color of the sprites a bit.
The main behavior I wanted out of this version of the enemy is to have the two minions follow the movement of the main ship and to continue traveling on their own if the main ship is destroyed. To accomplish this I had to add a boolean check called “isMinion” that can be used to distinguish between if the enemy is a child of a parent enemy or if it is a standalone or parent enemy. If this boolean is set to true various parts of the enemy script are disabled for that object to prevent it from acting on it’s own while it remains a child to its parent object. The challenging part that ended up having a simple answer was ensuring that if the main ship is destroyed the two remaining minion ships will not be destroyed alongside the main ship but instead continue traveling on their own as independent entities. The answer to this was to check for the parent object and store its reference in a GameObject variable if the minion boolean is set to true and if the parent is destroyed then to set that parentObj variable as well as “transform.parent” to null to detach it from the parent. Then once the minion ship is detached from the parent the script will then run the function CalcLineMovment() to keep the ship moving in a linear path and resetting it to the top of the screen once it flies out of bounds.
Some further testing showed once this behavior was established that the smaller enemies were somewhat difficult to hit. To resolve this problem I tried to widen out the minion ships a bit on only the x-axis to make them bigger targets and give them a slightly more unique look. The longer term design I want to achieve with this enemy prefab that I made is I want to expand the behavior of it at some point to where the main ship can launch its minion ships at the player. As a result this process was both laying the foundation for developing more mechanics down the line but the current state that this enemy type is in also works to fulfill a need for enemy variety as the smaller ships offer some more challenge to shoot down and create further hazards if they are let loose from their parent ship.
Creating Negative Pickups
Another addition that was requested for the game was to develop obstacles that the player could encounter that have different effects than simply damaging the player. While a majority of the powerups that spawn in the game a beneficial to the player a twist on this system is to spawn pickups that present negative consequences to the player if they are collected. My first attempt at designing one of these negative pickups is to design a pickup that does not damage the player but does put them in a position where it is tougher to avoid taking damage. The effect of the negative pickup I designed was one where, on collection, the player will move at half their base speed and their thrusters will be disabled for the entire duration. This significantly reduces the player’s ability to avoid damage but does not take control out of the player’s hands like a stun effect would which provides potentially satisfying counterplay to the penalty.
To implement this new item I created a new powerup icon with Photoshop by editing the same file I used to make the homing shot icon and then applied the same components that the rest of the powerups have besides the powerup ID that it uses to distinguish itself to the Spawn Manager.
Since this pickup effects the players speed in an inverse way to the speed boost powerup I decided to show the duration of the pickup’s effect by using the same progress bar that the speed boost power up uses. To distinguish the effect of the slow down, the duration meter also changes color to match the color of the pickup, in this case purple. Additionally the fuel gauge completely empties out and all associated visual effects to being out of fuel come into effect to communicate to the player that their movement options are limited.
The coroutine to refill the fuel once it is completely empty is disabled until the slow down duration wears off. One behavior I noticed from testing was that the refill effect only kicks in after its default delay that it uses if the player empties their fuel bar manually. This effect extended the penalty time for the slow down for longer than I intended so a balancing change I made to the effect was to also significantly shorten the delay that the coroutine CompRefillFuel() takes before refilling the fuel. However the shortened delay is only in effect after the Speed Down penalty wears off. To accomplish this I changed the hard coded delay duration for the coroutine into a variable that is changed to a wait timer of 0.2f upon execution of the slow down effect. This ensures that the fuel bar is refilled almost immediately after the negative effect wears off and then the coroutine resets that delay back to its default value at the end of its execution. This ensures that if the player empties their fuel gauge after they return to normal they still experience the intended 2 seconds delay before getting their thrusters back.
The final large change I had to make to accommodate this addition was implementing the ability for the Speed Boost powerup to overwrite the Speed Down pickup and vice-versa. The design for this was similar to allowing the different shot type pickups to overwrite each other. The process set two booleans to check if the powerup effect was active and if its associated coroutine was currently running. If for example I picked up the Speed Boost powerup while under effects of the Speed Down pickup the script would check and then set the “_speedDownActive” boolean to false and also run StopCoroutine() on the SpeedDownPowerDownRoutine() to completely disable the effects of the previous pickup.
I feel like this was a good first step to designing new obstacles to the game that provide different challenges for the player to manage. I have a few other ideas that I want to implement such as a negative pickup that spawn a new enemy whenever it is collected but those will be additions for a later time. The next feature that I want to work on implementing tomorrow will be balanced spawning and potentially the wave system. Until then here’s another demo for the current progress that I summarized today. Mahalo for reading!