Creating a Probability-Weighted Pool of Objects in Unity

Published

In programming, we make heavy use of random draws. For example, choose an element at random from an array.

The problem here is that these draws are done with equal probability and sometimes we want an element to occur more frequently than another (for examples, brushes for terrain details).

A simple way to do this is to choose some game objects and define a probability to draw each of them. Then, we compute the cumulative probabilities, draw a random value between 0 and 1 and iterate on the cumulative probabilities to find the index of the chosen object: we use the first index at which the drawn probability is lower than the calculated cumulative probability.

Here’s the C# code for the draw logic for drawing multiple object:

    public GameObject[] Draw_Multiple(int drawCount)
    {
        CheckInitialized();

        GameObject[] drawnObjects = new GameObject[drawCount];

        int k;
        float randomValue;
        for (int idDraw = 0; idDraw < drawCount; idDraw++)
        {
            randomValue = (float)_mRand.NextDouble();

            k = 0;
            do
            {
                if (randomValue < _mCumulativeProbabilities[k])
                {
                    break;

                }

                k++;

            } while (k < _mCumulativeProbabilities.Length);

            drawnObjects[idDraw] = mGameObjects[k];
        }


        return drawnObjects;

    }

To Note: We are dependent on the cumulative probabilities being calculated before drawing, and having set a random number generator, which is what is done in CheckInitialized. You might want to enforce this check at every run to avoid potential issues of the probabilities having changed. This could also allow for dynamic changes to the draw, such as using it in a loot system with runtime changes to loot rates.

I set this at first to avoid recomputing the cumulative probabilities and setting a PRNG at every call (and therefore creating garbage everytime), but for batch calls this could be superflous. This works as is, but I encourage you to make your own changes to better fit your needs.

The full code for the Scriptable Object is avaible in the GameObjectDrawPool.cs file on my UnitySimpleUtils repository on Github.