Samurai6219
Samurai6219

Reputation: 15

Gameobjects not spawning after i destroy them (Unity)

Im trying to make a first version of a game with unity, but the trees wont keep spawning after i have destroyed the tree amount to less than 5. In unity when i run the game it displays the tree amount and when it reaches 5 it stops spawning them but if i destroy one it wont spawn a new one even tho the tree amount is less than 5.

Also if i keep destroying them so there is always just two trees then it keeps spawning. But whenever it reaches 5 then it stops.

I have two scripts:

This is the one where if i click the tree it destroys it.

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

public class Harvest : MonoBehaviour
{
    public GameObject tree;
    public int treeScore;
    public void AddTreeScore()
    {
        treeScore++;
    }

    private Spawner spawnerScript;
    void Start()
    {
        spawnerScript = FindObjectOfType<Spawner>();
    }

    public void OnMouseDown()
    {
        GameObject.Destroy(tree);
        spawnerScript.treeAmount--;
        AddTreeScore();
        Debug.Log(treeScore);
        

    }


}

And this is everything else.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public GameObject tree;

    public int treeAmount;

    public float spawnRate = 1f;

    public bool canSpawn = true;

    public int maxAmount = 5;

    private IEnumerator Spawning()
    {
        WaitForSeconds wait = new WaitForSeconds(spawnRate);

        while (canSpawn == true)
        {
            yield return wait;
            
            Vector2 randomSpawnPos = new Vector2(Random.Range(-10, 11), Random.Range(-4, 5));
            Instantiate(tree, randomSpawnPos, Quaternion.identity);
            AddTreeAmount();

            if (treeAmount >= maxAmount)
            {
                canSpawn = false;
                
            }
            if( treeAmount < maxAmount)
            {
                canSpawn = true;
            }
            

        }
    }
    void AddTreeAmount()
    {
        treeAmount++;
    }

    void Start()
    {
        StartCoroutine(Spawning());
    }

    void Update()
    {
        
    }
}

I also tried setting the spawnRate to 0f; and then back to 1f;, but that did not work. It showed in unity that the spawnRate went to 0 but it kept still spawning them.

I think i have to set the canSpawn back to true some other way. Or is there more errors or mistakes?

Upvotes: 0

Views: 78

Answers (2)

Rocketman Dan
Rocketman Dan

Reputation: 414

The problem is this loop.

while (canSpawn == true)
{
    yield return wait;
    
    Vector2 randomSpawnPos = new Vector2(Random.Range(-10, 11), Random.Range(-4, 5));
    Instantiate(tree, randomSpawnPos, Quaternion.identity);
    AddTreeAmount();

    if (treeAmount >= maxAmount)
    {
        canSpawn = false;
        
    }
    if( treeAmount < maxAmount)
    {
            canSpawn = true;
    }
}

Your if statement checks if our tree count is over the max amout allowed and if it is then canSpawn is set to false. Your loop only runs when canSpawn is true. So if canSpawn ever becomes false the loop stops executing and you are never able to reach the code that can set it true again. And the loop is only ever started once because its triggered in Start.

The solution is to make the loop infinite so it only ends when your program ends. Simply use an if statement with your condition that allows your spawn to happen around the part that spawns the next tree rather than a while condition. This means you don't break out of the loop while until your game has ended or you intentionally break out of it with the break keyword.

WaitForSeconds wait = new WaitForSeconds(spawnRate);

while (true)
{
    yield return wait;

    if (treeAmount >= maxAmount)
    {
       canSpawn = false;
    }
    else
    {
        canSpawn = true;
    }

    if(canSpawn)
    {
        Vector2 randomSpawnPos = new Vector2(Random.Range(-10, 11), Random.Range(-4, 5));
        Instantiate(tree, randomSpawnPos, Quaternion.identity);
        AddTreeAmount();
    }
}

Upvotes: 0

derHugo
derHugo

Reputation: 90639

Your coroutine Spawning runs exactly once - at the beginning of the game.

void Start()
{
    StartCoroutine(Spawning());
}

Once canSpawn = false; is reached it will never run again.

Instead you could use a field for tracking if an instance of that routine is already running and if not run a new one like e.g.

public class Spawner : MonoBehaviour
{
    // in general for things you only set up via the Inspector
    // you should use private o avoid other classes to modify them (Encapsulation)
    [SerializeField] private GameObject tree;
    [SerializeField] private float spawnRate = 1f;
    [SerializeField] private int maxAmount = 5;

    // backing field for treeAmount
    private int _treeAmount;

    // keeps track whether a Spawning routine is already running
    private bool spawning;

    public int treeAmount
    {
        get => _treeAmount;
        set 
        {
            // this is executed whenever a value is assigned

            _treeAmount = value;
            RunSpawningRoutine();
        }
    }

    private void Start()
    {
         RunSpawningRoutine();
    }

    private void RunSpawningRoutine()
    {
        // if already spawning ignore
        if(spawning) return;

        StartCoroutine(Spawning());
    }

    private IEnumerator Spawning()
    {
        // just in case double safety for blocking concurrent routines
        if(spawning) yield break;

        // block other routines from running (see above)
        spawning = true;

        // simpler and cleaner loop condition
        while (treeAmount < maxAmount)
        {
            yield return new WaitForSeconds(spawnRate);
            
            // generally note that this does not prevent two trees spawning in the same spot
            var randomSpawnPos = new Vector2(Random.Range(-10, 11), Random.Range(-4, 5));
            Instantiate(tree, randomSpawnPos, Quaternion.identity);
            treeAmount++;
        }

        // allow a next routine to start
        spawning = false;
    }
}

This way whenever a value is assigned to treeAmount it will kickoff RunSpawningRoutine. Here you will check whether a routine is already running in which case you don't have to do anything, but if not it will start a new routine.


Follow up for the random position overlaps:

That's not straight forward the way you have it right now as there is no track of existing or destroyed/removed trees.

You could though have a dedicated component

public class Tree : MonoBehaviour
{
    public static readonly HashSet<Tree> Instances = new ();

    private void OnEnable()
    {
        Instances.Add(this);
    }

    private void OnDisable()
    {
        Instances.Remove(this);
    }
}

This way every existing instance of Tree will automatically register and remove itself to and from Instances.

Then in your spawning you can keep re-creating new random positions in case one is already taken like e.g.

using System.Linq;

...


while (treeAmount < maxAmount)
{
    yield return new WaitForSeconds(spawnRate);
    
    // generally note that this does not prevent two trees spawning in the same spot
    Vector3 randomSpawnPos;
    do
    {
        randomSpawnPos = new Vector3(Random.Range(-10, 11), Random.Range(-4, 5));
    } 
    // you could implement the following also without Linq using a foreach loop
    // but this is one of the cases where Linq just is way more beautiful to read and write ;)
    while(Tree.Instances.Any(tree => tree.transform.position == randomSpawnPos));

    Instantiate(tree, randomSpawnPos, Quaternion.identity);
    treeAmount++;
}

Upvotes: 0

Related Questions