Randomized Clones are Spawning in the same location

Hi there. I’m new to Unity/C# and I have tried my best to follow online tutorials. Doing exactly what someone describes is easy enough, but I think of things I would like to do differently to make the game more fun for me. I created a simple game that has a ball rolling down a long narrow plane. I placed rectangles as obstacles that move between certain X values forever so that when the ball hits the obstacles the movement stops and the level starts over. Placing each piece is time-consuming, and I wanted to see if I could figure out how to automatically generate the obstacles on the plane between certain X and Z ranges. I looked at several “spawner” tutorials and the code below is the closest that I could get.

Sometimes, however, the rectangles spawn at the same or nearly the same location, crash into each other and fly off the plane or start moving in the wrong direction. I can’t seem to find a way to have the obstacles spawn randomly, but make sure that one obstacle is a minimum distance from another obstacle.

Thank you for any and all assistance.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public int numberToSpawn;
    public List<GameObject> spawnPool;
    public GameObject quad;

    void Start()
    {
        spawnObjects();
    }

    public void spawnObjects()
    {
        destroyObjects();
        int randomItem = 0;
        GameObject toSpawn;
        MeshCollider c = quad.GetComponent<MeshCollider>();

        float screenX,screenY, screenZ;
        // Vector3 pos;

        for (int i = 0; i < numberToSpawn; i++)
        {
            randomItem = Random.Range(0, spawnPool.Count);
            toSpawn = spawnPool[randomItem];

            screenX = Random.Range(-6f, 6f);
            screenY = transform.position.y;
            screenZ = Random.Range(20f, 800f);
            // pos = new Vector3(screenX, screenY, screenZ);

            Instantiate(toSpawn, new Vector3(screenX, screenY, screenZ), toSpawn.transform.rotation);

        }
    }
    private void destroyObjects()
    {
        foreach (GameObject o in GameObject.FindGameObjectsWithTag("Obstacle"))
        {
            Destroy(o);
        }
    }


}

You can do something like quantizing the spawn area and making sure that each new random location is not in an earlier quantized location.

In english:
Pick a size L. L is the size of a box. You can split your spawning area into boxes of size L. Let’s just say it’s 1 for now.

Whenever you pick a random spot to spawn an enemy, you figure out which box that random spot falls into. You can do this pretty easily by rounding the location to the nearest multiple of L. If L is 1, that just means the nearest whole number. So for example if you randomly pick the location (2.64, 3.01), that falls into the box at (2, 3).

Once you know which box your random location falls into, you can check if any other obstacle has previously spawned in that box. You can do this quickly by keeping around a HashSet, which is just a collection of spots that you have previously spawned obstacles in. (HashSet is a type of collection that is good for checking if an element is contained in it)

If another obstacle has previously spawned in that particular box, you generate a new random location and repeat the process until you find a free box.

Once you find a free box, you spawn your obstacle, and you insert the box it landed in into the list of previously used boxes.

Obviously you will need to make sure you have enough boxes to fit the number of obstacles you want to spawn.

Another option is Poisson disc sampling: