- 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
![](https://koobas.hobune.stream/wayback/20220613024907im_/https://answers.unity.com/themes/thub/images/avi.jpg)
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