Not all enemies and powerups were created as equals. Because of this line of reasoning, my objective for this article is to create a balanced spawning for my powerups and enemy types.
This is a pretty basic setup that uses a handful of random integer dice rolls to give my enemy and powerup drops different chances to spawn, based on how I want to break up the drop tables. I then use quite a few methods with if else checks to determine the direction of the random variable traveling through the script, eventually having it assign the final value of the randomEnemy and randomPowerup variables. I know this isn’t the most elegant solution, but it was pretty fast to put together and hopefully it’s easy to follow along with.
To get started, I add healthy amount of new variables to my Spawn Manager class. The randomEnemy and randomPowerup variables already existed here, but I thought it was important to show them, being this is the final variable that determines which powerup and enemy is instantiated. The powerupRarity float value will be used to get a random rarity of powerup drop, similarly to the randomUncommon and randomRare powerup drop variables near the bottom of screen shot. The enemies and powerups instantiate through an array, so each has it’s own unique ID number. For my own ease of operations, I create int variables for each projectile and enemy, and assign their respective values to their ID number. Now I don’t need to reference their ID number and can simply call them by name. I introduce three int variables for the players current health, shields and a value to hold the max amount for both. Lastly I create a variable for the player, so I can use script communication to check the status of it’s health and shield.
Moving onwards into the start method, the player variable is assigned by using the find function, and then using GetComponent on the player. The player is null checked to ensure it was properly grabbed.
Maybe it’s easier to look at the final destination. Here is the SpawnEnemyRoutine method, I introduce a new method called RandomWeightedEnemy. All of my enemies previously had an equal chance at spawning. Now the new weighted enemies method will better determine which enemy is instantiated when the randomEnemy variable gets called near the bottom of this SpawnEnemyRoutine method.
Deeper now into the RandomWeightedEnemy method, a local variable called weightedEnemy is introduced. It’s assigned a value of random.value, which will generate a random value between 1 and 0 every time the method is called. A series of if else arguments now comes into play to create different paths depending on the random value that’s generated. If the random value of weightedEnemy is 0.45f or greater, the piranha enemy will spawn. That’s a good 55% chance to spawn a the most basic piranha enemy, which has chances of it’s own to have shields and/ or fire at the player from behind. The jellyfish, red piranha and blowfish each have roughly a 15% chance to spawn each, making up the remaining 45%. After checking the value of the random.value call, I assign the randomEnemy variable to be whatever enemy int value I choose for the different enemy weights.
Now that the enemies are taken care of, I can work on the powerup spawning, which will involve a few more steps. The PowerupSpawnRoutine method works just like the enemy spawn method, only having the randomPowerup variable ultimately determine which powerup is being instantiated.
I want to check the player’s current health and shield level to see if either of those powerups need to be dropped at all. I go into the Player Class now to add two public int methods. The PlayerLives method returns the currentLives value from the player, and the PlayerShield method returns the current shield health.
Back in the Spawn Manager now, let’s dive into the RandomWeightedPowerup method. If the player is alive (not null), I call the public methods on the player to get current values on the shield and lives. This let’s me use these values to set up three conditions through if else statements. If the player has less than the maximum amount of shields and health, a standard drop table is run. If either the health or shields on the player is full, a better drop table is run, and if both health and shields are maxed out, the best drop table is run.
I will only show the StandardPowerupDropTable, because it is almost identical to the Better and Best drop tables, only with the numeric values being adjusted. The powerupRarity variable is finally assigned a Random.value. Ammo reload is the only common drop powerup, and it has a 30% chance to drop (powerupRarity ≥ 0.7f). A range between 0.7 and 0.2 will generate a 50% chance for an uncommon powerup, and a range less than 0.2 will result in a rare powerup (20%). It’s important to note that I have 4 uncommon powerup drops and 3 rare powerup drops when considering the balancing at this point.
The better drop table increases the rare drop rate to 25% and decreases uncommon to 45%, while the best drop table increases the rare drop rate to 30% and decreases the uncommon to 40%.
Even deeper we traverse into the murky depths of this seemingly endless maze of if else statements…into the RandomUncommonPowerup method. This is where the values of the player health and shield come in. The shield is an uncommon drop, and health is a rare drop. I place both of these at the end of my Random.Range calls so that they are easy to exclude when I don’t want to drop them. For instance, if the player has less than full shield health, the full range of uncommon powerups is available. Else if the player has full shield health, the unnecessary shield powerup is excluded from the drop range. This also puts the player into the better drop table, which has higher chances for rare powerup drops. The randomPowerup variable is finally assigned to the appropriately rolled value.
The RandomRarePowerup method works in the same way, only checking the health of the player, and removing it from the powerup drop range if the player has full health.
That’s it for my rudimentary dive into a balanced spawn system! Thanks for reading along and I hope to see you in my next article!