How to get precise spawn interval (C#)?

Hello! I am having some problems I did not anticipate with my current enemy spawning system. I have a 2D game and I’m using a coroutine with WaitForSeconds as interval between enemy spawning. The precision of this interval is very crucial to the gameplay experience and because I am well into the development and only noticed this problem now I am pretty worried to keep using my current system.

I made a test project with simplified version of my spawn system without any pooling or such things. In this test I am just instating square sprites that move slowly to right with an interval of 0.77 seconds. Here is the result:

This test perfectly replicates the problem I am having. As you can see, the spawn interval is not precise. The spacing between the spawned squares varies quite a bit and I have no idea what is behind this problem. Here is the spawn code:

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour {

	public GameObject squareObj;
	private Vector3[] spawnPos;
	
	void Start () 
	{
		spawnPos = new Vector3[3];
		spawnPos [0] = new Vector3 (-10.0f, -2.0f, 0.0f);
		spawnPos [1] = new Vector3 (-10.0f, 0.0f, 0.0f);
		spawnPos [2] = new Vector3 (-10.0f, 2.0f, 0.0f);
		StartCoroutine ("Spawn");
	}

	IEnumerator Spawn()
	{
		while(true)
		{
			Instantiate (squareObj, spawnPos [0], Quaternion.identity);
			Instantiate (squareObj, spawnPos [1], Quaternion.identity);
			Instantiate (squareObj, spawnPos [2], Quaternion.identity);
			yield return new WaitForSeconds(0.77f);
		}
	}
}

If anyone has any insights on this I would be glad to hear about it, thanks!

WaitForSeconds(.77f) does not make the coroutine fire again after exactly .77 seconds - it makes the coroutine fire again the first frame after .77 seconds have passed.

Time.time gives you the exact time the current frame started at. You can use that to find out exactly how late your spawning is, and then move the objects to compensate:

private IEnumerator Spawn() {
    float extraTime = 0f;

    while (true) {
        Vector3 extraMovement = Vector3.right * movementSpeed * extraTime;

        Instantiate(squareObj, spawnPos[0] + extraMovement, Quaternion.identity);
        Instantiate(squareObj, spawnPos[1] + extraMovement, Quaternion.identity);
        Instantiate(squareObj, spawnPos[2] + extraMovement, Quaternion.identity);
        float spawnTime = Time.time;

        yield return new WaitForSeconds(.77f);

        extraTime = Time.time - .77f - spawnTime;
    }
}

Where movementSpeed is how fast your spawned objects are moving, and you replace Vector3.right with the direction they’re moving in.

You actually probably want to send extraTime to the movement script on the spawned objects, and let them decide how to move to offset that time, but this demonstrates the concept well enough.

There is a callback that is very precise about time interval and that is FixedUpdate(). You can change the interval at wich the FixedUpdate() is called by changing Time.fixedDeltaTime (at 0.2s by default) HOWEVER its on this same interval that the Physics is updated, thus if you change the value to 0.77 your physic will only be updated once every 0.77s wich isn’t much.

If you’re not planning on using any physics (Rigidbody and stuff) you can just change Time.fixedDeltaTime to 0.77.

`

 void Start()
 {
     Time.fixedDeltaTime =0.77f;
 }

void FixedUpdate(){
      Spawn();
 }

void Spawn()
     {
             Instantiate (squareObj, spawnPos [0], Quaternion.identity);
             Instantiate (squareObj, spawnPos [1], Quaternion.identity);
             Instantiate (squareObj, spawnPos [2], Quaternion.identity);
     }

`

If not you could change it to 0.11 sec and in your FixedUpdate Spawn your squares every 7 FixedUpdate() by adding a counter

`

int nbFixedUpdate = 0;

void FixedUpdate(){
     if(nbFixedUpdate++ % 7 == 0)
         Spawn();
}

void Spawn()
     {
             Instantiate (squareObj, spawnPos [0], Quaternion.identity);
             Instantiate (squareObj, spawnPos [1], Quaternion.identity);
             Instantiate (squareObj, spawnPos [2], Quaternion.identity);
     }

`

Hope it answers your question.

This is the usual problem, especially when you want to instantiate something more than once per frame.

private void Start(){
     StartCoroutine(Run ());
}

private IEnumerator Run(){

	bool flag = true;
	float delay = 0.01f; 
	float currentTime = delay;

	//Test purposes
	Vector3 temp = Vector3.zero;

	while(flag){
		currentTime -= Time.deltaTime;

		while(currentTime < 0){
			currentTime += delay;

			//Do things here

			//Test
			GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
			go.transform.position = temp;
			temp.x += 1.2f;
		}

		yield return null;
	}
}