My alternate Spawning solution

I don’t like trying to “logic out” the Update method, so I did it using Coroutines.

It works, but what do you guys think? Is this acceptable?
If you understand Coroutines (at least, enough to use them), is this easier than the Update method?

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

public class Spawner : MonoBehaviour {

	public GameObject[] attacterPrefabs;
	private bool spawning = true;
	private int counter = 0; // This can be used to make "waves" happen.

	// Use this for initialization
	void Start () {
        // Start the "SpawnAttacker" Coroutine For every attacker in my 
        // attackerPrefabs array, passing in the attacker and their spawn delay.
		foreach (GameObject g in attacterPrefabs) {
			Attacker a = g.GetComponent<Attacker> ();
			StartCoroutine(SpawnAttacker (g, a.seenEvery_X_Seconds));
		}
	}

    // If the object becomes enabled, it is Spawning. The Coroutines WILL need to be
    // restarted, because they stop if spawning is false.
	void OnEnable(){
		spawning = true;
        Start();
	}

    // If the object is disabled or destroyed at all, stop all Coroutines and set 
    // spawning to false
	void OnDisable(){
		spawning = false;
        StopAllCoroutines ();
	}
    void OnDestroy(){
		spawning = false;
        StopAllCoroutines ();
	}

	IEnumerator SpawnAttacker(GameObject attacker, float delay){
		while (spawning){
            // Wait for a random amount of time based on the delay - this will 
            // stop all the attackers from coming out in unison and add variety.
			yield return new WaitForSeconds (Random.Range(0f, 0.5f) * delay);
            // If we have spawned enough times, make a wave of attackers appear and 
            // reset the spawn counter. Otherwise, spawn one attacker normally and 
            // increment the spawn counter
			if (counter >= 10) {
				for (int i = 0; i < 5; i++) {
					Instantiate<GameObject> (
                        attacker, 
                        transform.position, 
                        Quaternion.identity, 
                        transform
                    );
					yield return new WaitForSeconds (
                        Random.Range (0.01f, 0.1f) * delay
                    );
				}
				counter = 0;
			} else {
				Instantiate<GameObject> (
                    attacker, 
                    transform.position, 
                    Quaternion.identity, 
                    transform
                );
				counter++;
			}
            // Now wait the normal spawn time. This is technically the attacker's 
            // spawn delay + Random.Range(0f, delay/2)
            // For example, if a fox spawns, you have to wait anywhere from 10 - 15 
            // seconds for the next one.
			yield return new WaitForSeconds (delay);
		}
		yield return null;
	}
}

I used a coroutine as well to increment my game time in whole seconds. Doing these challenges ends up leading me down a deep hole every time I think. I put the spawner script on the root spawn object to have control over all lanes and how often I see an attacker on the screen.

I thought of adding a variable to the attacker.cs to hold a time that the last spawn for that prefab happened. Then checked the game time against the attacker spawn time to see if its time to spawn again. Unless a spawn already happened during that second.

 public GameObject[] attackerPrefabArray;

 private float gameTime = 1.0f;
// Use this for initialization
void Start () {
    StartCoroutine(GameTimeSeconds());
}

// Update is called once per frame
void Update () {
	foreach(GameObject thisAttacker in attackerPrefabArray)
    {
        if (IsTimeToSpawn(thisAttacker)){
            Spawn(thisAttacker);
        }
    }
}

bool IsTimeToSpawn(GameObject attacker)
{
    float lastSpawnTime = attacker.GetComponent<Attacker>().lastSpawnTime;
    float attackerSpawnTime = attacker.GetComponent<Attacker>().seenEverySeconds;
    if (gameTime % attackerSpawnTime == 0 && lastSpawnTime != gameTime)
    {
        attacker.GetComponent<Attacker>().lastSpawnTime = gameTime;
        return true;
    }
    return false;

}
public void Spawn(GameObject attackerGameObject)
{
    int randomSpawn = Mathf.RoundToInt(Random.Range(0, 5));
    Transform childSpawn = transform.GetChild(randomSpawn);
    GameObject myAttacker = Instantiate(attackerGameObject, childSpawn.transform.position, Quaternion.identity) as GameObject;
    myAttacker.transform.parent = childSpawn.transform;
    print("Spawned on " + childSpawn.name);
}

IEnumerator GameTimeSeconds()
{
    while (true)
    {
        gameTime += 1.0f;
        yield return new WaitForSeconds(1);
    }
}

I came up with a simple answer of adding lastUsed to Attacker and then adding Time.deltaTime to it … seems to fit in the way the current game is designed

bool isTimeToSpawn(GameObject attackerGO) {

	Attacker thisAttacker = attackerGO.GetComponent<Attacker>();
	float spawnDelay = thisAttacker.seenEverySeconds;
	float spawnsPerScond = 1 / spawnDelay;

	if (spawnsPerScond > spawnDelay) {
		Debug.Log ("Frame Rate is Capping the Spawning");
	}
	thisAttacker.lastUsed += Time.deltaTime/5f; // devide by number of lanes 
	if (thisAttacker.lastUsed >= spawnDelay) {
		thisAttacker.lastUsed = 0f;
		return true;
	}
	return false;
}