Hi,
I have looked through various answers but still have issues.
Basically, I have 4 spawnpoints( cubes ), I get the transforms from the cubes and spawn enemies randomly at each position. All ok working fine.
My problem is that an enemy will spawn at the same spawnpoint as another enemy.
Now all I need to do is to check if the enemy is at that spawnpoint. I can then not spawn an enemy there.
I checked out physics.sphereoverlap and other physics functions, i tried layermasks too.
Thank you
The code I have so far is as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DarkTonic.CoreGameKit;
public class UnDeadSpawnerScript : MonoBehaviour
{
private int waveAmount = 0;
public GameObject[]SpawnPoints;
private float waitTime;
private int spawnPointChoice = 1;
private int spawnPointOtherChoice;
public void Start()
{
MakeUnDead(); // to test \\
}
public void MakeUnDead()
{
waitTime = Random.Range(10.0f, 15.0f);
spawnPointChoice = 1; // number of undeads to spawn //
spawnPointOtherChoice = Random.Range(0, 4); // position numbers to spawn //
for (int i = 0; i < spawnPointChoice; i++)
{
Vector3 Position = SpawnPoints[spawnPointOtherChoice].GetComponent<Transform>().position;
/// have some code here to check if the UNDEAD spawn exists or not \\\
PoolBoss.SpawnInPool("UNDEAD", Position, Quaternion.identity);
}
if (waveAmount < 10)
{
SpawnMoreUnDeads();
waveAmount = waveAmount + 1;
}
}
public void SpawnMoreUnDeads()
{
StartCoroutine("Wait");
}
IEnumerator Wait()
{
yield return new WaitForSeconds(waitTime);
MakeUnDead();
}
public void StopTheSpawns()
{
StopCoroutine("Wait"); ///// called from gameManager Script /////
print("spawnsStopped");
}
}
Since you are looping through the spawn points anyways when you call MakeUnDead I would just put a public bool on the spawn point. If the bool is false spawn a mob there and make it true. When the mob on that point dies make the bool false again.
Edit ^^ good call Alexander, if they are just spawning 1 unit per position ^^
First of all, good job, nice, readable, single-responsibility script, clear comments. You’re pretty close but it’s just got a few odd things.
I would approach this differently. I would create some arbitrary cooldown, and the enemies spawning would go through some arbitrary move phase making it impossible for them to spawn on top of each other. I would then plan to arrange them in the stage such that they would walk in off a ledge or step out of some fog wall the player can’t go into.
-spawnPointChoice should read “enemiesToMake” or some such- no point in matching names until you have a file with 20-30 variables (hopefully half of which are recycled as garbage after the constructor- I digress). Maybe “currentSpawnPoint?”
-Spawn more probably shouldn’t be happening following a coroutine labeled wait yeah?
Novice advice wanting 3rd opinion:
-Instead of an array of GameObjects, use an array of Vector3 world positions. I’m not sure the best way to do this while maintaining the convenience of using GO’s… One idea is to collect them by checking the string name of the objects.
-I encourage you to go ahead and ditch the coroutine for this one. It might seem like you’re saving yourself an Update() loop, but in practical terms co-routines are “doing a lot” behind the scenes, so if you were thinking along those lines I understand why but it’s probably wrong to avoid an Update with a coroutine. A couroutine takes the function you feed it, looks at its logical outcome, and then enumerates every iteration of itself as a unique “anonymouse function” in an array of functions, which it then steps over in its own loop. This is a heady topic for a week of study some other week, but basically you can use IEnumerator to do what coroutines do (and that’s what they’re using).
Some good replies, thank you for taking the time to help out.
The suggestion of setting a Bool works fine but will only spawn one undead at a time until it is killed and the bool is switched back.
To answer a few questions.
My undeads are static and do not move hence the need to know if they are on the spawn point.
So, I need to check if an Undead is on the spawn point, if so then the random spawnpoint chosen should not spawn until it has been killed.
When the undead function is called again and the random spawnpoint is different it should then spawn another undead at that point.
I considered adding the chosen random spawnpoints to an array, checking if the the random one exists and if so do nothing until it is removed. This seems quite complicated and I figureed there must be a more elegant solution.
Great advice on the coroutine, I take it on board.
Great advice on var naming too, i will change once i get this working.
public void MakeUnDead()
{
waitTime = Random.Range(10.0f, 15.0f);
spawnPointChoice = 1; // number of undeads to spawn //
spawnPointOtherChoice = Random.Range(0, 4); // position numbers to spawn //
for (int i = 0; i < spawnPointChoice; i++)
{
Vector3 Position = SpawnPoints[spawnPointOtherChoice].GetComponent<Transform>().position;
/// if "the spawned undead at the random chosen spawnpoint is there" \\\
///then
/// doNothing()
/// else
PoolBoss.SpawnInPool("UNDEAD", Position, Quaternion.identity);
}
if (waveAmount < 10)
{
SpawnMoreUnDeads();
waveAmount = waveAmount + 1;
}
}
I think you should do something like alexander said. The thing i would add would be to keep a list of available spawn points. You start it with all the spots, when you spawn you take it out of the list. You wait your delay and pick a new one from the list.
When an enemy dies you put the spawn location back into the available list. Then you never have to worry about picking a spawn point that is occupied.
I am not sure why JayMounes is arguing against coroutines so much because this to me is when you would really use one. Unless he means don’t start and stop then all the time. Then I can agree with that.
Not sure what this is about “half of which are recycled as garbage after the constructor”. Why would you want to create anything to just have it garbage collected. GC is a big performance issue.
I just typed this out so use it as reference not copy paste. This is how i would setup my coroutine. With something like the free list. You still have to manage when to add the spawn location back to the free list when the undead dies.
public class UnDeadSpawnerScript : MonoBehaviour
{
private int waveAmount = 0;
[SerializeField]
private int maxWaveAmount = 10;
public GameObject[] SpawnPoints;
private float waitTime;
private int numberOfUndeadsToSpawnEachIteration = 1;
private bool spawnUnDeads;
private List<GameObject> freeSpawnPoints;
public void Start()
{
StartTheSpawns();
}
private void MakeUnDead()
{
int spawnindex = Random.Range(0, freeSpawnPoints.Count); // position numbers to spawn //
Vector3 Position = freeSpawnPoints[spawnindex].transform.position;
/// have some code here to check if the UNDEAD spawn exists or not \\\
PoolBoss.SpawnInPool("UNDEAD", Position, Quaternion.identity);
freeSpawnPoints.RemoveAt(spawnindex);
waveAmount = waveAmount + 1;
}
private IEnumerator SpawnMoreUnDeads()
{
while (spawnUnDeads)
{
numberOfUndeadsToSpawnEachIteration = 1; // number of undeads to spawn //
for (int i = 0; i < numberOfUndeadsToSpawnEachIteration; i++)
{
if (waveAmount < maxWaveAmount)
{
MakeUnDead();
}
else
{
break;
}
}
yield return new WaitForSeconds(Random.Range(10.0f, 15.0f));
}
}
public void StartTheSpawns()
{
if (spawnUnDeads == false)
{
spawnUnDeads = true;
StartCoroutine(SpawnMoreUnDeads);
}
}
public void StopTheSpawns()
{
spawnUnDeads = false; ///// called from gameManager Script /////
print("spawnsStopped");
}
}
I don’t think you need to worry too much about your type of spawn point. Just use what is best for the code. Picking a random one from the list won’t make much of a difference if its a vector3, transform or gameobject. Plus if you spawn times are on the order of 10-15 seconds you are not going to notice. Plus if you plan on having lots of undead you will more issues to worry about with drawing them most likely than telling one to spawn.
Thanks for this, this is what I originally thought but not experienced enough to code.
I have made a couple of small changes to the code, like you say use it as a reference.
First I removed my “public GameObject[ ] SpawnPoints”
I then made the “private List of free spawnpoints” public so I can drop my gameobjects( spawnpoints) into the list in the inspector.
This worked well, I can see the elements being removed in the inspector as the unDeads are spawned.
I now get an “ArgumentOtOfRangeException”, when the coroutine tries to spawn the 5th Undead.
Should I stop the coroutine ?
To be fair the situation where all 4 are spawned would be rare but possible.
Adding the spawnpoints back, could you throw a pointer for me please.
Thanks
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DarkTonic.CoreGameKit;
public class UnDeadSpawnerScript : MonoBehaviour
{
private int waveAmount = 0;
[SerializeField]
private int maxWaveAmount = 10;
//public GameObject[] SpawnPoints; // removed//
private float waitTime;
private int numberOfUndeadsToSpawnEachIteration = 1;
private bool spawnUnDeads;
public List<GameObject> freeSpawnPoints; // made public so i can input spawnpoints //
public void Start()
{
StartTheSpawns();
}
private void MakeUnDead()
{
int spawnindex = Random.Range(0, freeSpawnPoints.Count); // position numbers to spawn //
Vector3 Position = freeSpawnPoints[spawnindex].transform.position;
/// have some code here to check if the UNDEAD spawn exists or not \\\
PoolBoss.SpawnInPool("UNDEAD", Position, Quaternion.identity);
freeSpawnPoints.RemoveAt(spawnindex);
waveAmount = waveAmount + 1;
}
private IEnumerator SpawnMoreUnDeads()
{
while (spawnUnDeads)
{
numberOfUndeadsToSpawnEachIteration = 1; // number of undeads to spawn //
for (int i = 0; i < numberOfUndeadsToSpawnEachIteration; i++)
{
if (waveAmount < maxWaveAmount) //
{
MakeUnDead();
}
else
{
break;
}
}
yield return new WaitForSeconds(Random.Range(10.0f, 15.0f));
}
}
public void StartTheSpawns()
{
if (spawnUnDeads == false)
{
spawnUnDeads = true;
StartCoroutine("SpawnMoreUnDeads");
}
}
public void StopTheSpawns()
{
spawnUnDeads = false; ///// called from gameManager Script /////
print("spawnsStopped");
}
}
I can give a noobie advice, as I am new to all this. Make 4 game object place them where you want your four spawn points. Attach your spawn script to them, having two spawn points(overlapped) and set ‘bool’ to check if one is dead. In this way, 4 enemies will respawn in four different points and the fifth one will not respawn until one of them is dead. Hope this is an another right way to do this.
Just check in you make undead that the free list count is > 0. If not don’t do anything.
You don’t need to stop the coroutine if you are out of points. It will just keep trying. You would only stop it when you want to stop spawning undead.
For returning spawn locations a lot of that depends on what your undead Code looks like.
There are a lot of ways.
Does your poolboss.spawninpool return the object it spawns? If it does you can just give it a reference to the spawn class and the spawn point. So when it dies it can call a function on the undeadspawn script to add location back to free list.
you could do something similar with events.
Similar again but your spawned script could become a singleton. If it’s the only one.
4…
The most straight forward one is 1.
I am assuming you have some undead component on each one spawned.
It should have a reference to the spawned it came from and the point it was spawned at.
Then when it dies it can do something like Spawner.FreeSpawnPoint(spawnpoint) which would add the spawnpoint back into the free list.
So as it sits, you are trying to use a List of game objects with no corelation to the mob you are dealing with, and you are finding it hard to bind them together.
Lets change it up and use a Dictionary. This will let you specify, this spawn point has this mob… The only deal then is, you need to know when that mob dies, so you can remove it from the binding.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class UnDeadSpawnerScript : MonoBehaviour
{
private int waveAmount = 0;
private int maxWaveAmount = 10;
private GameObject[] pool;
private float waitTime;
private int numberOfUndeadsToSpawnEachIteration = 1;
private bool spawnUnDeads;
private Dictionary<Transform, GameObject> spawnPoints;
public void Start()
{
GetSpawnPoints();
StartTheSpawns();
}
void Update()
{
foreach (var spawnPointsKey in this.spawnPoints.Keys)
{
var pointMob = spawnPoints[spawnPointsKey];
if (pointMob != null)
{
var health = pointMob.transform.root.GetComponentInChildren<Health>();
if (health != null && health.health == 0)
{
spawnPoints[spawnPointsKey] = null;
Destroy(pointMob.gameObject, 5);
}
}
}
}
private void MakeUnDead()
{
var available = this.spawnPoints.Keys.Where(t => this.spawnPoints[t] == null).ToList();
var spawnAt = available[Random.Range(0, available.Count)];
if (spawnAt == null)
{
return;
}
this.spawnPoints[spawnAt] = PoolBoss.SpawnInPool("UNDEAD", spawnAt.position, Quaternion.identity);
waveAmount = waveAmount + 1;
}
private IEnumerator SpawnMoreUnDeads()
{
while (spawnUnDeads)
{
numberOfUndeadsToSpawnEachIteration = 1; // number of undeads to spawn //
for (int i = 0; i < numberOfUndeadsToSpawnEachIteration; i++)
{
if (waveAmount < maxWaveAmount) //
{
MakeUnDead();
}
else
{
break;
}
}
yield return new WaitForSeconds(Random.Range(10.0f, 15.0f));
}
}
public void StartTheSpawns()
{
if (spawnUnDeads == false)
{
spawnUnDeads = true;
StartCoroutine("SpawnMoreUnDeads");
}
}
public void StopTheSpawns()
{
spawnUnDeads = false; ///// called from gameManager Script /////
print("spawnsStopped");
}
private void GetSpawnPoints()
{
spawnPoints = new Dictionary<Transform, GameObject>();
for (var i = 0; i < transform.childCount; i++)
{
this.spawnPoints[transform.GetChild(i)] = null;
}
}
}