Coroutines with Unity
The desired idea is to repeatedly do something — create an object or repeat an instruction sequence one after the other, including maybe with a time delay.
Create an empty object.
In this example, the empty object will link to a script to create Enemy gameObjects every few seconds. The name for the manager “Spawn_Manager.”
<right_click> the scripts view to create a new SpawnManager script.
<click> on the Spawn_Manager to highlight it in the Hierarchy.
<click> and <drag> the SpawnManager script up to the Spawn_Manager object.
Adjust the location Vector3(x,y,z) as needed to avoid the inconvenience of the Unity default location. Here is example location 0,0,0.
The Spawn_Manager empty gameObject is a place to hold instructions. The instructions code the behavior for spawning objects.
What is a coroutine?
Rather than run scripts one after another as fast as the Unity processing is able to, coroutines allow a pause to elapse before repeating a script, or before running a follow-up script.
Open the SpawnManager script with a <double-click> from the Assets view list.
public class SpawnManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// want to spawn an enemy gameObject every five seconds.
}
}
With a script file waiting, the setup is complete. Here is a result of a search on “unity coroutines.”
Unity — Manual: Coroutines (unity3d.com)
Normally, a function, such as Update() runs every frame. Here is an example of a function that runs every frame:
void Update()
{
Decrement();
}
private void Decrement()
{
for (int down_count = 3; down_count > 0; down_count-- )
{
Debug.Log("counter = " + down_count);
}
}
The Decrement function is called each game frame, and the function runs to completion without a pause.
Using a coroutine with “IEnumerator” and “yield” allows for the function to iterate once and return, then after a space of time iterate again and return. This means the game can continue however many frames without being held by the function.
IEnumerator requires at least one “yield” event.
IEnumerator Fade()
{
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
{
c.a = alpha;
renderer.material.color = c;
yield return null;
}
}
Here is the link after a search for “unity scripting api monobehavior start coroutine:”
Unity — Scripting API: MonoBehaviour.StartCoroutine (unity3d.com)
This example invokes the coroutine, yet continues to execute the function in parallel. It uses a continuous while(true) loop.
public class ExampleClass : MonoBehaviour
{
// In this example we show how to invoke a coroutine and
// continue executing the function in parallel.
private IEnumerator coroutine;
void Start()
{
// - After 0 seconds, prints "Starting 0.0"
// - After 0 seconds, prints "Before WaitAndPrint Finishes 0.0"
// - After 2 seconds, prints "WaitAndPrint 2.0"
print("Starting " + Time.time);
// Start function WaitAndPrint as a coroutine.
coroutine = WaitAndPrint(2.0f);
StartCoroutine(coroutine);
print("Before WaitAndPrint Finishes " + Time.time);
}
// every 2 seconds perform the print()
private IEnumerator WaitAndPrint(float waitTime)
{
while (true)
{
yield return new WaitForSeconds(waitTime);
print("WaitAndPrint " + Time.time);
}
}
}
This example does not have the continuous loop. The called coroutine runs until completion:
// In this example we show how to invoke a coroutine and wait until it
// is completed
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
IEnumerator Start()
{
// - After 0 seconds, prints "Starting 0.0"
// - After 2 seconds, prints "WaitAndPrint 2.0"
// - After 2 seconds, prints "Done 2.0"
print("Starting " + Time.time);
// Start function WaitAndPrint as a coroutine. And wait until it is completed.
// the same as yield return WaitAndPrint(2.0f);
yield return StartCoroutine(WaitAndPrint(2.0f));
print("Done " + Time.time);
}
// suspend execution for waitTime seconds
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);
print("WaitAndPrint " + Time.time);
}
}
For our Example, we initiate a gameObject each five seconds.
IEnumerator SpawnRoutine()
{
yield return null; // pause for one cycle and continues with next line
Debug.Log("wait one cycle");
yield return WaitForSeconds(5.0f) // pause for five seconds and continue
Debug.Log("Waited five seconds");
}
Because Enemy cube gameObjects creation is continuous, a while(true) loop is used.
IEnumerator SpawnRoutine()
{
while(true)
{
yield return WaitForSeconds(5.0f);
}
}
In the current example, there is an Enemy gameObject in the Hierarchy list, and an Enemy object in the Prefab folder of the Asset list. We have a Spawn_Manager in the Hierarchy list.
- create the gameObject in the SpawnManager Script.
public class SpawnManager : MonoBehaviour
{
[SerializeField]
private GameObject _enemyPrefab;
...
- in the Unity environment, _enemyPrefab is now an empty visible component to the empty gameObject Spawn_Manager: “Enemy Prefab: None (Game Object)”
- Delete the Enemy object in the Hierarchy
- <click> on Spawn_Manager in the Hierarchy to highlight and view in the Inspector.
- <click> and <drag> the Prefab Enemy from the Assets to the “None (Game Object) of the Enemy Prefab in the Spawn Manager (Script) component of the Spawn Manager in Hierarchy.
- Now the Enemy Prefab is accessible by the Spawn_Manager.
public class SpawnManager : MonoBehaviour
{
[SerializeField]
private GameObject _enemyPrefab;
...
IEnumerator SpawnRoutine()
{
while(true)
{
Vector3 positionToSpawn = new Vector3(Random.Range(-8f, 8f), 7, 0);
Instantiate(_enemyPrefab,positionToSpawn, Quaternion.identity);
yield return new WaitForSeconds(5.0f); // float, not int
}
}
}
- Now call the SpawnRoutine() from the Start() of the SpawnManager class
public class SpawnManager : MonoBehaviour
{
...
// Start is called before the first frame update
void Start()
{
StartCoroutine(SpawnRoutine()); // or StartCoroutine("SpawnRoutine"); using a string
}
...
- Now save, compile, and run to adjust speeds.