Prevent spawning of enemies in same location

First time posting. I’ve been learning Unity over the last couple months and things are going good. Learning a lot. But I have run into a roadblock and I’d like to ask for help.

I’ve got a game where enemies are separated into 2 categories(stationary, and mobile), and spawn between preset spawn points of the same categories. I’ve used the Brackeys spawn wave tutorial as a base and have edited it to allow waves to select enemies at random, and an if else to determine if it spawns at a stationary or mobile spawn point. Problem is enemies tend to spawn at the same location overlapping onto themselves during a wave which is undesirable.

I think I need a list, and when a spawn point is selected at random it’s then stored in a list to then be excluded in future spawn points? Then at the beginning of a new wave delete the stored entries in the list. Rinse, repeat.

I am not entirely sure how to go about this however. Any help welcome. Thanks!

==EDIT==
I got busy and was working on other stuff but I did come back to this and it was a lot easier than I originally thought. I just used a checksphere and with a few lines of code it works so much better than any kind of convoluted list.

if (Physics.CheckSphere(s_Spawn.position, 1f, hittableLayers))
{
Debug.Log(“Can’t spawn here”);
return;
}
else
{
Debug.Log("Spawning enemy: " + stationaryEnemies[randomEnemy].name);
Instantiate(stationaryEnemies[randomEnemy], s_Spawn.position, s_Spawn.rotation);
}

using System.Collections;
using UnityEngine;

public class WaveController : MonoBehaviour
{
    public enum SpawnState { SPAWNING, WAITING, COUNTING};

    [System.Serializable]
    public class Wave
    {
        public string name;
        public int count;
        public float rate;
    }

    public GameObject[] mobileEnemies;
    public GameObject[] stationaryEnemies;

    public Wave[] waves;
    private int nextWave = 0;

    public Transform[] mobileSpawnPoints;
    public Transform[] stationarySpawnPoints;

    //private List<int> mobilePreviousSpawnPoint;
    //private List<int> stationaryPreviousSpawnPoint;

    public float timeBetweenWaves = 5f;
    private float waveCountdown;

    private float searchCountDown = 1f;

    private SpawnState state = SpawnState.COUNTING;

    void Start()
    {
        waveCountdown = timeBetweenWaves;
    }

    void Update()
    {
        if(state == SpawnState.WAITING)
        {
            if (!EnemyIsAlive())
            {
                WaveCompleted();
            }
            else
            {
                return;
            }
        }
        
        if (waveCountdown <= 0)
        {
            if (state != SpawnState.SPAWNING)
            {
                StartCoroutine(SpawnWave(waves[nextWave]));
            }
        }
        else
        {
            waveCountdown -= Time.deltaTime;
        }

        void WaveCompleted()
        {
            Debug.Log("Wave Completed!");

            state = SpawnState.COUNTING;
            waveCountdown = timeBetweenWaves;

            if(nextWave + 1 > waves.Length - 1)
            {
                nextWave = 0;
                Debug.Log("All waves complete! Looping...");
            }
            else
            {
                nextWave++;
            }
            
        }

        bool EnemyIsAlive()
        {
            searchCountDown -= Time.deltaTime;
            if(searchCountDown <= 0f)
            {
                searchCountDown = 1f;
                if (GameObject.FindGameObjectWithTag("Enemy") == null)
                {
                    return false;
                }
            }
           
            return true;
        }
    }

    IEnumerator SpawnWave(Wave _wave)
    {
        Debug.Log("Spawning Wave: " + _wave.name);
        state = SpawnState.SPAWNING;

        for (int i = 0; i < _wave.count; i++)
        {
            int randomEnemySelect = Random.Range(0, 10);

            if (randomEnemySelect >= 5)
            {
                SpawnMobileEnemy();
                
            }
            else
            {
                SpawnStationaryEnemy();
                
            }
           
            yield return new WaitForSeconds(1f / _wave.rate);
        }

        state = SpawnState.WAITING;
        yield break;
    }

    void SpawnMobileEnemy()
    {
        int randomEnemy = Random.Range(0, mobileEnemies.Length);
        Transform m_Spawn = mobileSpawnPoints[Random.Range(0, mobileSpawnPoints.Length)];
        Debug.Log("Spawning enemy: " + mobileEnemies[randomEnemy].name);
        Instantiate(mobileEnemies[randomEnemy], m_Spawn.position, m_Spawn.rotation);       
    }

    void SpawnStationaryEnemy()
    {
        int randomEnemy = Random.Range(0, stationaryEnemies.Length);
        Transform s_Spawn = stationarySpawnPoints[Random.Range(0, stationarySpawnPoints.Length)];
        Debug.Log("Spawning enemy: " + stationaryEnemies[randomEnemy].name);
        Instantiate(stationaryEnemies[randomEnemy], s_Spawn.position, s_Spawn.rotation);
    }
}

Well, I figured it out on my own. Maybe there is a more efficient way of doing it but it works.

I went with my initial thought of creating a list, and in my case 2 lists. One for mobile spawn points and another for stationary spawn points. Then when either SpawnMobileEnemy or SpawnStationaryEnemy is called I generate a random int value from 0 to the length of the lists. Then take that number and check if the list already contains it. If so I return to run it again, else I add the value to the list and use the value to determine the place in the transform array.

Then when WaveCompleted is called I clear both lists before the next wave. The only issue I see is if I try to spawn more enemies than spawn points are available. I could probably write something to check for that but for now I’ll just make sure I don’t try to spawn more enemies than there are spawn points.

Here’s the code if anyone wants to take a gander.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class WaveController : MonoBehaviour
    {
        public enum SpawnState { SPAWNING, WAITING, COUNTING};
    
        [System.Serializable]
        public class Wave
        {
            public string name;
            public int count;
            public float rate;
        }
    
        public GameObject[] mobileEnemies;
        public GameObject[] stationaryEnemies;
    
        public Wave[] waves;
        private int nextWave = 0;
    
        public Transform[] mobileSpawnPoints;
        public Transform[] stationarySpawnPoints;

        public float timeBetweenWaves = 5f;
        private float waveCountdown;
    
        private float searchCountDown = 1f;
    
        public List<int> mobileSpawnLocations;
        public List<int> stationarySpawnLocations;
    
        private SpawnState state = SpawnState.COUNTING;
    
        void Start()
        {
            waveCountdown = timeBetweenWaves;
            mobileSpawnLocations = new List<int>();
            stationarySpawnLocations = new List<int>();
        }
    
        void Update()
        {
            if(state == SpawnState.WAITING)
            {
                if (!EnemyIsAlive())
                {
                    WaveCompleted();
                }
                else
                {
                    return;
                }
            }
            
            if (waveCountdown <= 0)
            {
                if (state != SpawnState.SPAWNING)
                {
                    StartCoroutine(SpawnWave(waves[nextWave]));
                }
            }
            else
            {
                waveCountdown -= Time.deltaTime;
            }
    
            void WaveCompleted()
            {
                Debug.Log("Wave Completed!");
                mobileSpawnLocations.Clear();
                stationarySpawnLocations.Clear();
                Debug.Log("Lists cleared for next wave");
    
                state = SpawnState.COUNTING;
                waveCountdown = timeBetweenWaves;
    
                if(nextWave + 1 > waves.Length - 1)
                {
                    nextWave = 0;
                    Debug.Log("All waves complete! Looping...");
                }
                else
                {
                    nextWave++;
                }
                
            }
    
            bool EnemyIsAlive()
            {
                searchCountDown -= Time.deltaTime;
                if(searchCountDown <= 0f)
                {
                    searchCountDown = 1f;
                    if (GameObject.FindGameObjectWithTag("Enemy") == null)
                    {
                        return false;
                    }
                }
               
                return true;
            }
        }
    
        IEnumerator SpawnWave(Wave _wave)
        {
            Debug.Log("Spawning Wave: " + _wave.name);
            state = SpawnState.SPAWNING;
    
            for (int i = 0; i < _wave.count; i++)
            {
                int randomEnemySelect = Random.Range(0, 10);
    
                if (randomEnemySelect >= 5)
                {
                    SpawnMobileEnemy();
                    
                }
                else
                {
                    SpawnStationaryEnemy();
                    
                }
               
                yield return new WaitForSeconds(1f / _wave.rate);
            }
    
            state = SpawnState.WAITING;
            yield break;
        }
    
        void SpawnMobileEnemy()
        {
            int randomEnemy = Random.Range(0, mobileEnemies.Length);
            int randomMobileLocation = Random.Range(0, mobileSpawnPoints.Length);
            if (mobileSpawnLocations.Contains(randomMobileLocation))
            {
                return;
            }
            else
            {
                mobileSpawnLocations.Add(randomMobileLocation);
            }
            
            Transform m_Spawn = mobileSpawnPoints[randomMobileLocation];
            Debug.Log("Spawning enemy: " + mobileEnemies[randomEnemy].name);
            Instantiate(mobileEnemies[randomEnemy], m_Spawn.position, m_Spawn.rotation);       
        }
    
        void SpawnStationaryEnemy()
        {
            int randomEnemy = Random.Range(0, stationaryEnemies.Length);
            int randomStationaryLocation = Random.Range(0, stationarySpawnPoints.Length);
            if (stationarySpawnLocations.Contains(randomStationaryLocation))
            {
                return;
            }
            else
            {
                stationarySpawnLocations.Add(randomStationaryLocation);
            }
            
            Transform s_Spawn = stationarySpawnPoints[randomStationaryLocation]; 
            Debug.Log("Spawning enemy: " + stationaryEnemies[randomEnemy].name);
            Instantiate(stationaryEnemies[randomEnemy], s_Spawn.position, s_Spawn.rotation);
        }
    }

Your solution works, but is wonky and extremely difficult in regards to scalability. Imagine you want to create a bunch of new enemy types, now you will have to cross-iterate through lists over lists of values, because you don’t want enemy A spawning at a position where enemy B is already.

A somewhat more elegant solution would be to cast a ray or sphere to determine if the spawning space is occupied, or have a single list of occupied spawning spaces to check against. The issue with keeping a list of occupied spawning spaces is that moving enemies could always be spawned on, since their positin is not stored in the list.