Unity Script: How to Spawn Random Objects with Custom Probabilities

We are sharing a few Unity scripts that we use on a regular basis. Last week, I posted a camera follow script. This week, we are sharing a spawner script. This script can spawn random objects from a list of potential prefab objects. Each object has an associated probability number that allows us to increase or decrease the likelihood of an object appearing. This means that although each spawn is random, we can choose which objects we want to appear more often.

The Full Code

Here is the full code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    [Serializable]
    public class SpawnObject {
        public GameObject spawnObject;
        public float probability;
    }

    [SerializeField] private List<SpawnObject> spawnObjects = new List<SpawnObject>();
    [SerializeField] float spawnTime;

    private float lastSpawnTime;
    private float currentSpawnTimer;
    private float[] probabilities; 

    // Start is called before the first frame update
    void Start()
    {
        probabilities = new float[spawnObjects.Count];

        float current_probability = 0.0f;
        for(int index=0; index<spawnObjects.Count; ++index)
        {
            current_probability += spawnObjects[index].probability;
            probabilities[index] = current_probability;
        }

        lastSpawnTime = 0;
        currentSpawnTimer = spawnTime + UnityEngine.Random.Range(-1.0f,1.0f);
    }

    // Update is called once per frame
    void Update()
    {
        lastSpawnTime += Time.deltaTime;

        if(lastSpawnTime > currentSpawnTimer){

            float spawnX = UnityEngine.Random.Range(-20.0f,20.0f);
            float spawnY = 0.98f;
            float spawnZ = UnityEngine.Random.Range(-20.0f,20.0f);
            Vector3 spawnPoint = new Vector3(spawnX,spawnY,spawnZ);

            // Determine which object to spawn
            float spawnRandom = UnityEngine.Random.Range(0.0f,1.0f);
            int index = 0;
            while(index<probabilities.Length && spawnRandom > probabilities[index]){
                ++index;
            }

            if(index < probabilities.Length){
                GameObject newObject = Instantiate(spawnObjects[index].spawnObject);
                newObject.transform.position = spawnPoint;
            } else {
                Debug.Log("Spawner: Probability out of range");
            }

            lastSpawnTime = 0;
            currentSpawnTimer = spawnTime + UnityEngine.Random.Range(-1.0f,1.0f);
        }
        
    }
}

Spawn Object Class

First, we create a class within a class. We want to create a spawn object that includes (1) a reference to a prefab object and (2) the probability of the object to spawn. We add the [Serializable] attribute before the class. This allows Unity to turn the class into a format that Unity can use efficiently. Importantly, this also allows us to create instances of the class through the Unity UI.

[Serializable]
public class SpawnObject {
    public GameObject spawnObject;
    public float probability;
}

Instance Variables

Next, we have the instance variables. We have two variables that we will expose to the Unity UI using the SerializeField attribute:

  • spawnObjects: list of SpawnObjects (see SpawnObject section above)
  • spawnTime: approximately how often should the spawn occur

We also create three private instance variables that are used for timing:

  • lastSpawnTime: the list time that an object was spawned
  • currentSpawnTimer: a semi-random timer based on spawnTime
  • probabilities: an array used to turn the spawnObject probability into a number between 0 and 1 that we can use with a random number generator to spawn different objects
[SerializeField] private List<SpawnObject> spawnObjects = new List<SpawnObject>();
[SerializeField] float spawnTime;

private float lastSpawnTime;
private float currentSpawnTimer;
private float[] probabilities; 

Initialization

We initialize in the start method. The first thing we do is allocate the probabilities array to the same size as the number of objects in spawnObjects. We also initialize currentProbability to 0.

Next, we iterate through the list of spawnObjects. For each object in spawnObjects, we add the object probability to currentProbability, and we store in the probabilities array. The way this will work is that we will generate a random number between 0 and 1. We will then find where that number falls in the probabilities array and generate that spawnObject.

We set our lastSpawnTime to 0. Finally, we create our currentSpawnTimer. As mentioned above, this is a semi-random timer. We start with the base, spawnTime, and then we add a random number between -1 and 1. This means that we can generally control the spawn time. However, there is some randomness to the actual spawning.

void Start()
{
    probabilities = new float[spawnObjects.Count];

    float current_probability = 0.0f;
    for(int index=0; index<spawnObjects.Count; ++index)
    {
        current_probability += spawnObjects[index].probability;
        probabilities[index] = current_probability;
    }

    lastSpawnTime = 0;
    currentSpawnTimer = spawnTime + UnityEngine.Random.Range(-1.0f,1.0f);
}

The Update Method

The update method first adds Time.deltaTime to our lastSpawnTime. Next, we check whether this is greater than our currentSpawnTimer. If no, we are done.

lastSpawnTime += Time.deltaTime;

if(lastSpawnTime > currentSpawnTimer){

If it is time to spawn, we create a random x and z positions (in this case, we keep y the same since everything will spawn at the same height). We combine x, y, and z into a vector.

float spawnX = UnityEngine.Random.Range(-20.0f,20.0f);
float spawnY = 0.98f;
float spawnZ = UnityEngine.Random.Range(-20.0f,20.0f);
Vector3 spawnPoint = new Vector3(spawnX,spawnY,spawnZ);

Next, we create a random number between 0 and 1 and store in spawnRandom. We will use this to pick the object we want to spawn. We then loop through our probabilities array until our current random number, spawnRandom, is greater than the number at the current index of the probabilities array (see the graphic in the Initialization section above).

// Determine which object to spawn
float spawnRandom = UnityEngine.Random.Range(0.0f,1.0f);
int index = 0;
while(index<probabilities.Length && spawnRandom > probabilities[index]){
    ++index;
}

As long as the index is within the probabilities array, we create the new object using Instantiate.

if(index < probabilities.Length){
    GameObject newObject = Instantiate(spawnObjects[index].spawnObject);
    newObject.transform.position = spawnPoint;
} else {
    Debug.Log("Spawner: Probability out of range");
}

Finally, we set the lastSpawnTime to 0 and create a new semi-random spawn time and store in currentSpawnTimer.

lastSpawnTime = 0;
currentSpawnTimer = spawnTime + UnityEngine.Random.Range(-1.0f,1.0f);

Conclusion

This spawner method is not too complicated, but is very useful. It allows you to generate instances of prefab objects at random locations at a semi-random time. Hopefully, you found this useful. I have several ideas for improvement. We could add variables that allow us to set a region for spawning. In addition, it would be useful to have a way to spawn on a map that is not flat (i.e., determine the y value based on the height of the map at x and z). If you have other ideas, drop them in the comments below.