Balanced Spawning

Renato Figueiredo
4 min readDec 20, 2023

--

Objective: Learn how to improve our spawning system to a balanced system.

So our game can spawn enemies and powerups frequently, but sadly we can’t alter the chance of each enemy and powerup being spawned.

We are going to implement a system, where we are going to place an amount of chance for our items and enemies to spawn, making some items more likely to spawn, and some less likely.

private int _weightedTotal;
private GameObject ChooseRandomPowerUp()
{
_weightedTotal = 0;

int[] powerupTable =
{
25, //TripleShot
30, //Speed
20, //Shield
15, //Special
20, //Slow
60, //Ammo
10 //Life
};

foreach (var powerup in powerupTable)
_weightedTotal += powerup;

int randomNumber = Random.Range(0, _weightedTotal);
int index = 0;

foreach (var weight in powerupTable)
{
if (randomNumber <= weight)
{
return _powerups[index];
}
index++;
randomNumber -= weight;
}

return _powerups[0];
}

So our game currently has 7 different type of powerups. How does our new system works ?

Instead of randomly choosing one out of our powerup array, we now added a weighted system to it.
First we make to set our WeightedTotal to zero. This is how we are going to calculate our total chance.

Them we created our table, which contains the weight for all our powerups. But what are weights ?
Imagine the following scenario, you are going to take names for Secret Santa. You have one name for each person, so each person has exactly the same chance of being chosen. In a weighted system, we have a bunch of “papers” with every powerup name in it. Some powerups have more papers them others, meaning they are more likely to be chosen.

Our powerup array.

As you can see, our elements in our table, match exactly their position in the Powerups array. Meaning, element 0 is for TripleShot, and 6 is for Life.

So now we are running a foreach loop, running through all items inside our table, and adding their weight to our total. This should lead us to a 180 total.

So we have 170 papers for our Secret Santa, but only 10 are for Life, meaning Life has a 10/170 chance of being chosen. Our Ammo on the other hand, has the most papers inside our bag, with 60. This leaves it with a 60/180 chance of being chosen.

int randomNumber = Random.Range(0, _weightedTotal);
int index = 0;

foreach (var weight in powerupTable)
{
if (randomNumber <= weight)
{
return _powerups[index];
}
index++;
randomNumber -= weight;
}

Now on this part, we are simply generating a random number from 0 to our total, and creating an int to index our array.

Then we run a foreach loop through our PowerupTable, and check all the weights inside.

If the number we got is less or equal to the weight of the current index of the table, we just return that GameObject.
In case that is not true, we increase our index, to make sure we are moving along with our table, and subtract our weight from our randomNumber.

Eventually, we will reach the item that was chosen, by the range of options.

TripleShot: 0–25
Speed: 26–55
Shield: 56–75
Special: 76–90
Slow: 91–110
Ammo: 111–170
Life: 171–180

These are the numbers that the needs to rolled in order to get each of our items.
We also have a return powerups[0] at the end, just in case we for some reason fail to have a proper return, we just return something to the method, to prevent issues.

private IEnumerator SpawnPowerUpRoutine()
{
yield return _powerupSpawnDelay;
while (!_isPlayerAlive)
{
float randomPositionX = Random.Range(_minXPosition, _maxXPosition);
_powerUpSpawnPosition.Set(randomPositionX, _spawnPositionY, 0f);
Instantiate(ChooseRandomPowerUp(), _powerUpSpawnPosition, Quaternion.identity, _powerUpContainer.transform);
yield return new WaitForSeconds(Random.Range(3, 8));
}
}

Now inside our SpawnPowerUpRoutine we simply call the ChoseRandomPowerUp method inside our Instantiate, meaning that we will instantiate whichever object returns from the method, and this is based on the chance/weight system that we created.

Now we are going to implement the same thing for our enemy system, but since we only have 2 different type of enemies, it will be much easier.

private GameObject ChooseRandomEnemy()
{
_weightedTotal = 0;

int[] enemyTable =
{
60, // Default Enemy
40 // Zigzag Enemy
};

foreach (var enemy in enemyTable)
_weightedTotal += enemy;

int randomNumber = Random.Range(0, _weightedTotal);
int index = 0;

foreach (var weight in enemyTable)
{
if (randomNumber <= weight)
{
return _enemiesPrefab[index];
}
index++;
randomNumber -= weight;
}

return _enemiesPrefab[0];
}

First we set our WeightedTotal to zero. Then we create our table with our chances, in this case 60% for Default and 40% for our Zigzag.

We run the foreach loop to sum all the weight in our table.
Then we generate a random number between 0 and our total weight (100), and create our index variable.

We run a foreach loop through all items in the table, and check if the value of the randomNumber is less or equal to the weight. If it is, we return that enemy, otherwise as before, we just increase our index and reduce the weight of our randomNumber.
Default: 0-60
Zigzag: 61–100

As before, we have a return _enemiesPrefab[0] just to prevent issues in case we don’t have a proper return, but this shouldn’t happen.

private void SpawnEnemy()
{
float randomPositionX = Random.Range(_minXPosition, _maxXPosition);
_enemySpawnPosition.Set(randomPositionX, _spawnPositionY, 0f);
GameObject enemy = Instantiate(ChooseRandomEnemy(), _enemySpawnPosition, Quaternion.identity, _enemyContainer.transform);
_activeEnemies.Add(enemy);
EnemyBehaviour enemyBehaviour = enemy.GetComponent<EnemyBehaviour>();
if (enemyBehaviour != null)
enemyBehaviour.IncreaseSpeed(_enemySpeedMultiplier);
}

Now in our SpawnEnemy method, we use our ChooseRandomEnemy that will return which enemy we will spawn.

There you have it, now our Spawn System is much more balanced, making easier for us to control the chance of each item or enemy of being spawned.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response