Search code examples
c#listunity-game-enginerandomscene

Random scene loader not working correctly


I'm trying to develop the functionality in my game that loads a random scene of a total of 8 populated in a List that then gets removed from the list forestSceneIndexes = new List<int>(); once the scene is loaded. That way when the next random scene is loaded it chooses from the existing scenes in the lists, and so an and so forth.

The first thing I do is populate my list with scenes with the function:

private IEnumerator DoInitSceneIndexList()
    {
        // Initialize scene name list
        for (int i = 0; i < numberOfForestScenes; i++)
        {
            forestSceneIndexes.Add(firstForestSceneIndexNumber + i);
            Debug.Log("Scene index " + forestSceneIndexes[i].ToString() + " added to int list.");
        }
        yield return new WaitForEndOfFrame();

        initSceneList = false;
    }

After that I get the random scene in the function DoGetRandomScene

private IEnumerator DoGetRandomScene()
    {
        // Get scene name from random pick
        randomSceneIndex = Random.Range(firstForestSceneIndexNumber, (firstForestSceneIndexNumber + forestSceneIndexes.Count) - 1);

        yield return new WaitForEndOfFrame();

        StartCoroutine(DoRemoveRandomSceneFromList());

        // Load selected scene
        StartCoroutine(DoLoadScene(randomSceneIndex));
    }

Once I get the random scene I then immediately remove that index from my list:

private IEnumerator DoRemoveRandomSceneFromList()
    {
        // Remove selected scene from list
        forestSceneIndexes.Remove(randomSceneIndex);
        yield return new WaitForEndOfFrame();
    }

Then I pass the randomSceneIndex to the DoLoadScene() function

private IEnumerator DoLoadScene(int sceneIndex)
    {
        yield return new WaitForEndOfFrame();

        SceneManager.LoadScene(sceneIndex, LoadSceneMode.Single);
    }

My problem is that it doesn't work correctly, it seems to start okay with the first few scenes but then it just keeps reloading the same scenes once it gets down to about 3 scenes left. I'm not sure what I have wrong, any help would be greatly appreciated! The full script is also added below. Thank you!

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine.Networking;

public class ForestGameManager : NetworkBehaviour
{
    public static ForestGameManager fgm = null; // create singleton

    ForestSceneTimer forestSceneTimer;

    [Header("Network Prefab")]
    public GameObject serverNetworkManagerPrefab;

    [Header("Forest Game Manager")]
    private int fgmIndex;
    private int firstForestSceneIndexNumber;
    private int lastForestSceneIndexNumber;
    private bool _isInitialized;

    [Header("Forest Scenes")]
    [TextArea(1, 1)]
    public string forestSceneNotes = "Please place all Forest Scenes after Forest Loader scene";
    public int numberOfForestScenes;
    private float sceneCountdownLength = 15.0f;
    private List<int> forestSceneIndexes;
    private int randomSceneIndex;
    private bool initSceneList = true;
    private int currentSceneIndex;

    [Header("Forest Animal Management")]
    public int maxAnimalCount = 4;
    public List<ForestAnimal> forestAnimals;

    private void Awake()
    {
        //DontDestroyOnLoad(this.gameObject);

        StartCoroutine(DoInitForestGameManager());
        StartCoroutine(DoInitServerNetworking());

        fgmIndex = SceneManager.GetActiveScene().buildIndex;
        firstForestSceneIndexNumber = fgmIndex + 1;

        //Debug.Log("firstForestSceneIndexNumber Index is : " + firstForestSceneIndexNumber);

        forestSceneTimer = GetComponent<ForestSceneTimer>();
    }

    private void Start()
    {
        if (!this._isInitialized)
        {
            forestAnimals = new List<ForestAnimal>();
            forestSceneIndexes = new List<int>();
            lastForestSceneIndexNumber = ((firstForestSceneIndexNumber + numberOfForestScenes) - 1);

            if (initSceneList)
            {
                StartCoroutine(DoInitSceneIndexList());
                StartCoroutine(DoGetRandomScene());
            }

            Debug.Log("There are " + numberOfForestScenes + " forest scenes in MasterLoader.");
            this._isInitialized = true;
        }
        else
        {
            this.forestAnimals.Clear(); //I assume you can clear the list on scene change
        }

        InvokeRepeating("CheckAnimalCount", 0, 1f);
        InvokeRepeating("CheckRemainingScenes", 0, 10f);
    }

    private void CheckAnimalCount()
    {
        if (forestAnimals.Count >= maxAnimalCount)
        {
            GameObject toDestroy = forestAnimals[0].forestAnimalGO;
            forestAnimals.RemoveAt(0);
            StartCoroutine(ExplodedMesh(toDestroy));

            //yield return new WaitForSeconds(1f);
        }
        Debug.Log("Checking animal count");
    }

    private void CheckRemainingScenes()
    {
        if (currentSceneIndex >= firstForestSceneIndexNumber && currentSceneIndex <= lastForestSceneIndexNumber)
        {
            // If all forest scenes have been used, re-initialize scene name list and pick the next random scene
            if (forestSceneIndexes.Count == 0)
            {
                StartCoroutine(DoInitSceneIndexList());
                StartCoroutine(DoGetRandomScene());

                Debug.Log("Out of scenes, re-initializing scene list.");
            }

            //yield return new WaitForSeconds(1f);

            Debug.Log("Checking remaining scenes");
        }
    }

    private IEnumerator ExplodedMesh(GameObject toDestroy)
    {
        MeshExploder meshExploder = toDestroy.transform.GetChild(0).gameObject.AddComponent<MeshExploder>();
        yield return new WaitForEndOfFrame();
        toDestroy.transform.GetChild(0).gameObject.GetComponent<MeshExploder>().Explode();
        yield return new WaitForEndOfFrame();
        Destroy(toDestroy);
        yield return new WaitForEndOfFrame();
    }

    private void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        currentSceneIndex = SceneManager.GetActiveScene().buildIndex;

        //Debug.Log("Current scene index is : " + currentSceneIndex);

        if (currentSceneIndex >= firstForestSceneIndexNumber && currentSceneIndex <= lastForestSceneIndexNumber)
        {
            // Start scene change countdown timer
            forestSceneTimer.StartSceneCountTimer(sceneCountdownLength);
        }
    }

    private IEnumerator DoInitSceneIndexList()
    {
        // Initialize scene name list
        for (int i = 0; i < numberOfForestScenes; i++)
        {
            forestSceneIndexes.Add(firstForestSceneIndexNumber + i);
            Debug.Log("Scene index " + forestSceneIndexes[i].ToString() + " added to int list.");
        }
        yield return new WaitForEndOfFrame();

        initSceneList = false;
    }

    private IEnumerator DoInitForestGameManager()
    {
        if (fgm == null)
            fgm = this;
        else if (fgm != null)
            Destroy(gameObject);

        DontDestroyOnLoad(gameObject);
        yield return new WaitForEndOfFrame();
    }

    private IEnumerator DoInitServerNetworking()
    {
        Instantiate(serverNetworkManagerPrefab);
        yield return new WaitForEndOfFrame();
    }

    public void GetRandomScene()
    {
        Debug.Log("Get Random Scene");
        StartCoroutine(DoGetRandomScene());
    }

    private IEnumerator DoGetRandomScene()
    {
        //Debug.Log("First forest scene index : " + firstForestSceneIndexNumber);
        //Debug.Log("Last forest scene index : " + lastForestSceneIndexNumber);

        // Get scene name from random pick
        randomSceneIndex = Random.Range(firstForestSceneIndexNumber, (firstForestSceneIndexNumber + forestSceneIndexes.Count) - 1);

        Debug.Log("First scene in list is " + firstForestSceneIndexNumber + " , the last one is " + ((firstForestSceneIndexNumber + forestSceneIndexes.Count) - 1));

        yield return new WaitForEndOfFrame();

        StartCoroutine(DoRemoveRandomSceneFromList());

        // Load selected scene
        StartCoroutine(DoLoadScene(randomSceneIndex));

        Debug.Log("randomSceneIndex is " + randomSceneIndex);

        for (int i = 0; i < forestSceneIndexes.Count; i++)
        {
            // Debug.Log("Scene " + forestSceneIndexes[i].ToString() + " is still left in the list");
            Debug.Log(forestSceneIndexes.Count.ToString() + " scenes are still left in the list");
        }
    }

    private IEnumerator DoRemoveRandomSceneFromList()
    {
        // Remove selected scene from list
        forestSceneIndexes.Remove(randomSceneIndex);
        Debug.Log("Scene " + randomSceneIndex + " has been removed from the list.");
        yield return new WaitForEndOfFrame();
    }

    private IEnumerator DoLoadScene(int sceneIndex)
    {
        yield return new WaitForEndOfFrame();

        SceneManager.LoadScene(sceneIndex, LoadSceneMode.Single);

        foreach (ForestAnimal animal in forestAnimals) // Spawn Animal Stored in the forestAnimals List
        {
            // print("Animal " + animal.forestAnimalName + "is still in the list");
            //GameObject animalGO = (GameObject)Instantiate(animal.forestAnimalGO);
            //NetworkServer.Spawn(animalGO);

            Debug.Log(animal.forestAnimalName + " re-instantiated in new scene");
        }
    }
}

Solution

  • Aside from the issues mentioned in the comments, which you should definitely go over carefully, the problem is in how you are picking your random number. You aren't pulling from the actual list of indices. It should be something like this:

    private IEnumerator DoGetRandomScene()
    {
        // Get scene name from random pick
        int choice= Random.Range(0, forestSceneIndexes.Count - 1);
        //here's where you convert your random choice into an actual scene index
        int sceneIndex = forestSceneIndexes[choice]; 
    
        yield return new WaitForEndOfFrame();
        //If you want to remove the scene, just do it now
        forestSceneIndexes.RemoveAt(choice);
        // Load selected scene
        StartCoroutine(DoLoadScene(sceneIndex));
    }
    

    Note, I didn't modify most of your use of coroutines but as indicated in the comments, you definitely have a lot to cleanup there.