Skip to main content

On Sale: GamesAssetsToolsTabletopComics
Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

I looked at your code and made my own little version of it :

using UnityEngine;

using System.Collections;

public class Spawn : MonoBehaviour

{

    public static Spawn Instance;

    public GameObject objectToSpawn;

    public float spawnFrequency = 1f;

    public float spawnPadding = 0.1f;

    public int maxObjectsOnScreen = 5;

    private float nextSpawnTime = 0f;

    private int objectsOnScreen = 0;

    private float _Delta;

    private GameObject[] SpawnedObjects;

    private bool SpawningEnabled = false;

    private void Awake()

    {

        Spawn.Instance = this;

    }

    private void Start(){

        //Create an array for the Spawned Object cache that will hold all Spawned Objects references in the scene.

        SpawnedObjects = new GameObject[maxObjectsOnScreen];

        SpawningEnabled = true;

        StartSpawnEvent(spawnFrequency);

    }

    private void Update(){

        _Delta = Time.deltaTime;

    }

    private void StartSpawnEvent(float SpawnFrequency)

    {

        if(SpawnEvent!= null)

        {

            StopCoroutine(SpawnEvent);

            SpawnEvent = null;

            //By stopping & setting the existing Spawn Event reference to null, you clean any reference Cache.

            //This ensure that the garbage collector can clean anything from it.

        }

        SpawnEvent = SpawnCoroutine(SpawnFrequency);

        StartCoroutine(SpawnEvent);

    }

    private float SpawnTimer = 0f;

    private IEnumerator SpawnEvent;

    private int CurrentObjectCheck = 0;

    IEnumerator SpawnCoroutine(float SpawnFrequency)

    {

        SpawnTimer = SpawnFrequency;

        //This while() only  allow the Coroutine to run while the Object on screen is lesser than the max allowed.

        while (SpawningEnabled && objectsOnScreen < maxObjectsOnScreen)

        {

            SpawnTimer -= _Delta;

            if (SpawnTimer <= 0f)

            {

                //Spawn Timer is ringing. Spawn a new Object.

                for(CurrentObjectCheck = 0; CurrentObjectCheck < maxObjectsOnScreen; CurrentObjectCheck++)

                {

                    if (SpawnedObjects[CurrentObjectCheck] == null)

                    {

                        //An empty slot has been detected.

                        //This function will add the object and adjust the relevant quantities.

                        SpawnNewObject(CurrentObjectCheck);

                        //Break will stop the for() loop because you want the timer to restart before checking and spawning any other.

                        break;

                    }

                }

                //Reset the spawn timer back to the frequency.

                SpawnTimer = SpawnFrequency;

            }

            yield return null;

        }

    }

    //Small note: When you got a value that you will reuse often, but not simultanously in paralel, it's better to cache it.

    private Vector2 RandomViewPosition_Holder;

    private void SpawnNewObject(int ObjectID)

    {

        if (SpawnedObjects[ObjectID] != null)

        {

            //It's almost impossible that something remains in the reference since you just looked it up in the coroutine.

            //Still, just to be on the safe side, you don't want to leave any remains of lost data in the cache.

            Destroy(SpawnedObjects[ObjectID]);

            SpawnedObjects[ObjectID] = null;

        }

        RandomViewPosition_Holder = new Vector2(

            UnityEngine.Random.Range(Camera.main.ViewportToWorldPoint(Vector3.zero).x + spawnPadding, Camera.main.ViewportToWorldPoint(Vector3.right).x - spawnPadding),

            UnityEngine.Random.Range(Camera.main.ViewportToWorldPoint(Vector3.zero).y + spawnPadding, Camera.main.ViewportToWorldPoint(Vector3.up).y - spawnPadding));

        SpawnedObjects[ObjectID] = Instantiate(objectToSpawn, gameObject.transform);

        //From your code, I guess this is a 2D game, hence why you fill the world Y (elevation) with the view Y (height on screen) as, otherwise, it would have been different.

        SpawnedObjects[ObjectID].transform.position = new Vector3(RandomViewPosition_Holder.x, RandomViewPosition_Holder.y, 0f);

        //You can use the ++, but I prefer to use the += for the sake of clear definition. For example, if you were to spawn 2 object, you can just change += 1 to += 2 and so on;

        objectsOnScreen += 1;

    }

    public void DestroyTargetObject(GameObject targetToRemove) {

        foreach(GameObject obj in SpawnedObjects)

        {

            if(obj == targetToRemove)

            {

                //Object has been found in the Array of Spawned Objects

                Destroy(obj);

                objectsOnScreen -= 1;

                //You can set some kind of checker here if you want to limit the maximum number of object spawned in a match.

                //For example, cache an int and add to it here and once it reach a value (meaning X object got destroyed), the player won.

                //That's why I added the SpawningEnabled boolean as a condition for the spawn to occure in the coroutine. Setting it to false stop the object from respawning once destroyed.

                StartSpawnEvent(spawnFrequency);

            }

        }

    }

}

With that code, the spawning will only happen when it's possible. With that code, when an object is supposed to be destroyed/removed from the screen, you only have to call something like Spawn.Instance.DestroyTargetObject(this); or replace the "this" by the object reference if it's done from another object's script. As long as "this" refer to the SAME object as the one you instantiate, it will be destroyed, removed from the list and a new one will be created in its stead (unless, as noted, the SpawningEnabled bool value is set to false.)