Reputation: 145
I've been working on this section of code in the main Gameplay.cs script of my Unity 2D project for a long while now and no matter which way I change it and shift things around, it either causes Unity to lock up on Play or, after some tweaking, will instantly go from Wave 0 to Wave 9. Here is my original attempt, which causes Unity to freeze on Play. I do not understand why this happens and would like some insight on where my logic has gone wrong.
I am trying to achieve the following:
private float startWait = 3f; //Amount of time to wait before first wave
private float waveWait = 2f; //Amount of time to wait between spawning each wave
private float spawnWait = 0.5f; //Amount of time to wait between spawning each enemy
private float startTimer = 0f; //Keep track of time that has elapsed since game launch
private float waveTimer = 0f; //Keep track of time that has elapsed since last wave
private float spawnTimer = 0f; //Keep track of time that has elapsed since last spawn
private List<GameObject> zombies = new List<GameObject>();
[SerializeField]
private Transform spawnPoints; //The parent object containing all of the spawn point objects
void Start()
{
deathUI.SetActive(false);
//Set the default number of zombies to spawn per wave
zombieCount = 5;
PopulateZombies();
}
void Update()
{
startTimer += Time.deltaTime;
if (startTimer >= startWait)
{
waveTimer += Time.deltaTime;
spawnTimer += Time.deltaTime;
SpawnWaves();
}
//When the player dies "pause" the game and bring up the Death screen
if (Player.isDead == true)
{
Time.timeScale = 0;
deathUI.SetActive(true);
}
}
void SpawnWaves()
{
while (!Player.isDead && ScoreCntl.wave < 9)
{
if (waveTimer >= waveWait)
{
IncrementWave();
for (int i = 0; i < zombieCount; i++)
{
if (spawnTimer >= spawnWait)
{
Vector3 spawnPosition = spawnPoints.GetChild(Random.Range(0, spawnPoints.childCount)).position;
Quaternion spawnRotation = Quaternion.identity;
GameObject created = Instantiate(zombies[0], spawnPosition, spawnRotation);
TransformCreated(created);
spawnTimer = 0f;
}
}
waveTimer = 0f;
}
}
}
I am a beginner and understand my code may not follow best practice. I also have a working enemy spawner that uses Coroutine and yield returns, but would like to get this version of my enemy spawning working.
Upvotes: 3
Views: 981
Reputation: 622
I don't have any eperience with Unity but with XNA, i assume their main game processing to be similar with both using Update() and Draw() functions.
An Update() function is generally called a few times per second, in your code SpawnWaves() should be executed after every Update call once startTimer >= startWait.
So lets look at SpawnWaves()
void SpawnWaves()
{
while (!Player.isDead && ScoreCntl.wave < 9)
{
if (waveTimer >= waveWait)
{
IncrementWave();
for (int i = 0; i < zombieCount; i++)
{
if (spawnTimer >= spawnWait)
{
[..]
spawnTimer = 0f;
}
}
waveTimer = 0f;
}
}
}
This while loop is a problem, because it is a sort of game loop in a game loop. It will only exit, if the player is dead or your wave count is over nine.
After the call of SpawnWaves() there are two possibilities, depending on Time.deltaTime, which is more or less random after a program start, waveTimer can be bigger or smaller than waveWait.
Case: waveTimer >= waveWait
All code inside will be executed without delay. So if its its possible to IncrementWave() it will be done nine times in milliseconds. Then while is false and code inside never executed again.
Case: waveTimer < waveWait
In this case IncrementWave() will never be called and your game could spin endless in this while loop.
So what you have to do is basically replacing this while loop with an if statement, to allow the program to continue.
If(!Player.isDead && ScoreCntl.wave < 9)
From then on your counter incrementations will be called on Update() more than once.
waveTimer += Time.deltaTime;
spawnTimer += Time.deltaTime;
And your conditions for wave changing and spawning, can become true over time.
if (waveTimer >= waveWait),
if (spawnTimer >= spawnWait)
I hope this can guide you to fix your problems.
Update:
For this behaviour it's a good idea to seperate IncrementWave and spawn conditions. For this purpose you need an extra bool variable, called haveToSpawn in this example.
if (waveTimer >= waveWait)
{
IncrementWave();
waveTimer = 0f;
haveToSpawn = true; //new variable
}
if (haveToSpawn && spawnTimer >= spawnWait)
{
for (int i = 0; i < zombieCount; i++)
{
[..] //spawn each zonbie
}
spawnTimer = 0f; //reset spawn delay
haveToSpawn = false; //disallow spawing
}
Upvotes: 2
Reputation: 17145
Use a coroutine to call the spawn waves method:
private float startWait = 3f; //Amount of time to wait before first wave
private float waveWait = 2f; //Amount of time to wait between spawning each wave
private float spawnWait = 0.5f; //Amount of time to wait between spawning each enemy
private float startTimer = 0f; //Keep track of time that has elapsed since game launch
private float waveTimer = 0f; //Keep track of time that has elapsed since last wave
private float spawnTimer = 0f; //Keep track of time that has elapsed since last spawn
private List<GameObject> zombies = new List<GameObject>();
[SerializeField]
private Transform spawnPoints; //The parent object containing all of the spawn point objects
void Start()
{
deathUI.SetActive(false);
//Set the default number of zombies to spawn per wave
zombieCount = 5;
PopulateZombies();
StartCoroutine(SpawnWaves());
}
void Update()
{
startTimer += Time.deltaTime;
if (startTimer >= startWait)
{
waveTimer += Time.deltaTime;
spawnTimer += Time.deltaTime;
}
//When the player dies "pause" the game and bring up the Death screen
if (Player.isDead == true)
{
Time.timeScale = 0;
deathUI.SetActive(true);
}
}
IEnumerator SpawnWaves()
{
//wait 3 seconds
yield return new WaitForSeconds(startWait);
//then:
while (!Player.isDead && ScoreCntl.wave < 9)
{
if (waveTimer >= waveWait)
{
IncrementWave();
for (int i = 0; i < zombieCount; i++)
{
if (spawnTimer >= spawnWait)
{
Vector3 spawnPosition = spawnPoints.GetChild(Random.Range(0, spawnPoints.childCount)).position;
Quaternion spawnRotation = Quaternion.identity;
GameObject created = Instantiate(zombies[0], spawnPosition, spawnRotation);
TransformCreated(created);
spawnTimer = 0f;
}
}
waveTimer = 0f;
}
//wait until the end of frame
yield return null;
}
}
To have a better understanding of how unity coroutines work:
A coroutine is a method with return type of IEnumerator and behaves as a collection of code blocks which are executed asynchronously. The yield instruction separates code blocks and specifies the amount of time frames to wait before next code block starts executing.
There are several types of yield instructions:
You can think of
Upvotes: 1