- Home /
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);
}
}
Answer by Mikhailzrick · Feb 11, 2021 at 09:56 AM
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);
}
}
Answer by AbandonedCrypt · Feb 11, 2021 at 10:39 AM
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.
I don't disagree it's a bit wonky as you say. I hadn't thought of a raycast. Do you mean have the gameobject that I use for the spawn points cast rays and if it detects something(an enemy), exclude that spawn point from the available spawn points?
Your answer

Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
how to make enemy clone work independently? 1 Answer
Enemy Spawner help 1 Answer