Implementing Weighted RNG in Unity
By: Harvey Limbo
Tags: Probability, C Sharp Programming, Game Development, Unity Game Development

RNG or Random Number Generation is a concept that is applied throughout many kinds of games ranging from Pokemon to gacha games such as Genshin Impact.

Using a six-sided dice, you have a 1 in 6 chance to either get a 1,2,3,4,5, or 6. The chance or the probability it will land on 3 is equal to the probability that the dice will land on a 6. To skew the probabilities to be uneven, we must add weights to each side of the dice to ensure uneven probabilities when rolling.

For this tutorial, please check out https://github.com/hlimbo/Space-Shooter-Tutorial for the full code source.

In my game, I used Weighted RNG for:

Dropping power-ups when an enemy gets destroyed by the player (60 percent chance of receiving a random power-up drop)

Dropping random power-ups from the sky at set intervals (e.g. drop every 8 seconds from a random x position)

The Math behind Weighted RNG

To create a weighted RNG system in Unity, we’ll need to know the number of possible power-ups that could drop into the Space Shooter game and come up with some arbitrary weighted values that associate each power-up with a weighted value.

In this case, I used a dictionary named weightTable that holds a power-up type key and int value pair:

The higher the number, the more likely a power-up of a given type (e.g. AMMO or TRIPLE_SHOT) will show up. The lower the number, the less likely a power-up of a given type will show up. These weights can be adjusted as needed to either make the game easier or harder.

The next question you might be asking is, “how do I know what the expected percentage drop rate is for AMMO?” To answer that question, we take the AMMO’s weighted value and divide it by the sum of the weights in the weightTable :

125 / (90 + 45 + 60 + 125 + 20 + 30 + 10) = (125 / 380) * 100 = 32.895%

In the example above, I rounded to the nearest 3 decimals. This of course, is only the expected percentage and not the actual percentage as that will be determined by the RNG functions that we will use in the next section.

Actual avg calculated using 100k iterations

As you can see, the expected average values for each weight differ from the actual average values but by a small amount. This is because we use the random function to pick a random weight and is reasonably unpredictable for the intent of the game.

Using RNG

The way this algorithm works is by picking a random value between 0 and the sum of the weights. Next, the algorithm subtracts a weight from the weight table until randomWeight goes below 0. Once it goes below 0, it will return the PowerUpType enum value where it will be used to instantiate the power-up in game.

The O-notation of the algorithm is O(N) where N is the number of weights available. Alternatively, I could have implemented a solution with a better O-notation of O(log(N)) using Binary Search but figured that it wasn’t necessary because my game only has 7 possible power-ups to randomly select from. If my game is dealing with 100s or even 1000s of possible power-ups to pick from, then implementing Binary Search to randomly pick a weighted random value might have been the better pick here. To see alternatives of how this weighted RNG algorithm could have been implemented please see: https://blog.bruce-hill.com/a-faster-weighted-random-choice