Had trouble understanding spawn rate

This really confused me, as I thought we were trying to construct a timer that controlled when an enemy should spawn.

Once I thought about it, I realized the ‘Threshold’ variable in “isToTimeToSpawn” could better be thought of as ChanceToSpawn.

Rather than using the overall time since last spawn. We are instead just calculating the chance an enemy has of spawning in 1 second. An enemy that spawns every 4 seconds has a 1/4 (.25) chance to spawn.
This percentage is then multiplied by deltaTime (1 second would be 1).
Finally we use the Random function to generate said chance to compare against our ChanceToSpawn ie Threshold.

It should be noted that this is not perfect. We have a chance of two foxes spawning next to each other, even though their spawn rate is much slower.

6 Likes

This completely confused me too. Your explanation makes it clearer, though I still question why we do it this way, instead of using something like InvokeRepeating. That just seems cleaner than comparing a random value against another basically random value (thanks to * timedelta) and hoping for the best.

I still wonder about the scenario where my machine is super fast and every frame is rendered in 1ms (so my 0.25 chance is now changed to 0.00025, and then compared to a random number which could be much higher every frame.

1 Like

It may be covered later in the course.
But from other reading I have done, it sounds like Coroutines in Unity may be the normal way to handle this.

https://docs.unity3d.com/Manual/Coroutines.html

1 Like

I posted my solution to this over on the Udemy course forum yesterday. It uses coroutines and WaitForSeconds. It also randomises the lane spawner used.

5 Likes

Very nice improvement!

@Stuart_Marsh: very nice job indeed!

I expanded a little your code, adding the Start method to programmatically create the lanes game objects, it can save some time when the number of lanes is changed on a specific level, the only thing needed is to change the Size value of the Spawners array in the Spawner.cs component in Unity.








    void Start(){

        for (int i=0; i<spawners.Length; i++){

            GameObject lane = new GameObject("Lane " + (i + 1));

            lane.transform.parent =  GameObject.Find("Spawners").transform;

            lane.transform.position = new Vector2 (10, i+1);

            spawners[i] = lane;

        }

    }


1 Like

Was thinking about the Coroutine approach while watching this video. And really liked your implementation. Here is another naive approach:

float timeTracker = 0f;

private bool isTimeToSpawn(GameObject attacker){
	bool result = false;

	timeTracker += Time.deltaTime;
	if (timeTracker >= attacker.GetComponent<Attacker> ().spawnEveryXSeconds) {
		result = true;
		timeTracker = 0f;
	}

	return result;
}
1 Like

@Stuart_Marsh : inspiring solution, thank you.

I’ve added some tweaks to have more controls on spawning, add max attackers on board at start and increased them over time (all defined in inspector) :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour {


[Tooltip ("Max attackers on start")]
public int maxAttacker;
[Tooltip ("Delay before increasing maxAttackers")]
public float maxAttackerChangeDelay;
[Tooltip ("Number of Attacker to add after each period given by maxAttackerChangeDelay")]
public int maxAttackerToAddEachTime;

public GameObject[] attackerPrefabs;
public GameObject[] SpawnerLines;
// attackersOnBoard has to be accessible by HealthController  Die () method
public static int attackersOnBoard = 0;

private GameObject Parent;
private float timer = 0f;
private bool[] spawnInLines;

void Start () {
	spawnInLines = new bool[SpawnerLines.Length];
	for (int i = 0; i < SpawnerLines.Length; i++) {
		spawnInLines [i] = false;
	}
}
	
// Update is called once per frame
void Update () {
	int randomLine = Random.Range (0, SpawnerLines.Length);
	int randomAttacker = Random.Range (0, attackerPrefabs.Length);

	timer += Time.deltaTime;
	if (timer >= maxAttackerChangeDelay) {
		maxAttacker += maxAttackerToAddEachTime;
		timer = 0;
	}

	if (!LineWaitingForSpawn (randomLine) && attackersOnBoard < maxAttacker) {
		// Attacker is going to spawn -> add Attacker on board
		attackersOnBoard++;
		StartCoroutine (SpawnAttacker(randomLine, randomAttacker));
	}
}

IEnumerator SpawnAttacker (int line, int attacker) {
	GameObject currentAttacker = attackerPrefabs [attacker];

	float timeToWait = currentAttacker.GetComponent<Attacker> ().seenEveryXSeconds;

	yield return new WaitForSeconds (timeToWait);
	Spawn (currentAttacker, line);
}

bool LineWaitingForSpawn(int line) {
	if (spawnInLines [line] == false) {
		spawnInLines [line] = true;
		return false;
	} else {
		spawnInLines [line] = false;
		return true;
	}
}

void Spawn (GameObject Attacker, int line) {		
	GameObject newAttacker = Instantiate (Attacker, SpawnerLines [line].transform.position, Quaternion.identity);
	newAttacker.transform.parent = SpawnerLines [line].transform;
}
}

Edit : note that there are some differences for lectures to come, here we used only one spawner script to manage all spawners and so, we can’t get individual spawner by the component. (personnally i’ve used tags to find my spawners)

This has been driving me crazy too.

@Stuart_Marsh This code works pretty good, but the problem is that the attacker index is just chosen at random from a range, this means every enemy is equally likely to be spawned and the actual number of each enemy is roughly the same. seenEverySeconds is just being used as a spawn delay rather than how many of each are spawning per second. You can fix this though, by just inserting more prefabs into the array based on the ratio you want (3 lizards for every 1 fox) though there’s probably a better way to do it.

@Hargol99 this doesn’t quite work right, since the time.tracker is reset whenever it gets high enough to spawn an attacker, this means it will only ever spawn the attacker with the lowest spawnEveryXSeconds.

Privacy & Terms