Arguement out of range exception on a lists index

Hi unityAnswers. I’m trying to go through a list of files (that resets to the first model after the last model is done) and I have a plus and minus button for going forwards and backwards. But I can’t figure out why clones index is out of range when I click the plus button. Any help is greatly appreciated.

public int UPPER_BOUNDS; // set to 3 in unity
        public TouchRotation tRotation;
        public List<GameObject> models = new List<GameObject>(); // three game objects loaded
        // in unity


        private List<GameObject> clones = new List<GameObject>();
        private GameObject temp;
        private int count = 0;

    // Use this for initialization
    void Start()
    {
        ObjReader.use.autoCenterOnOrigin = true;
        count = 0;
        clones.Add(temp);
        Debug.Log(clones.Count);  // outputs 1
        Debug.Log(clones.Capacity); // outputs 4
        TouchRotation tRotation = this.gameObject.AddComponent<TouchRotation>();
        tRotation.camera = this.camera;
    }

    public void loadModel()
    {
        clones[count] = Instantiate(models[count]) as GameObject; 
    }

    public void unloadModel()
    {
        Destroy(clones[count]);
    }

    public void incrementCounter()
    {
     //  try
     //  {

        
        Debug.Log(count + " pre in");
        if (count == UPPER_BOUNDS)
        {
            Debug.Log(count + " upper_bounds pre");
            count--;
            unloadModel();
            count--;
            loadModel();
            count++;
            Debug.Log(count + " upper_bounds");
        }
        else if (count > UPPER_BOUNDS)
        {
            Debug.Log(count + "greater upper_bounds pre");
            count = UPPER_BOUNDS;
            unloadModel();
            count = 0;
            clones = null;
            loadModel();
            Debug.Log(count + " count > upper_bounds");
        }
        else
        {
            Debug.Log(count + " pre else");
            if (clones[count] == temp)
            {
                Debug.Log(count + "clones[count] temp");
                count++;
                loadModel(); // it appears to be breaking on this line
            }
            else
            unloadModel();
            count++;
            loadModel();
            Debug.Log(count + " else inc");
        }
        }
      //  catch (System.Exception)
     //   {
      //      count = 0;
           
   //     }
   // }

    public void decrementCounter()
    {
    //    try
    //    {

       
        Debug.Log(count + " pre dec");
        if (count <= 0)
        {
            Debug.Log(count + " pre count reset");
            count = 0;
            Debug.Log(count + " post count reset");
            unloadModel();
            Debug.Log(count + " post unload model");
        }
        else
        {
            unloadModel();
            count--;
            loadModel();
            Debug.Log(count + " else dec");
        } 
        }
      //  catch (System.Exception)
       // {
     //       count = 0;            
     //   }
   // }
}

UPPER_BOUNDS = 3

There are 3 models loaded, indexed 0,1,2

Whenever loadModel() or unloadModel() is called and count is greater than 2 you will get “index out of range error”.

Lines 53, 54:

count = UPPER_BOUNDS;  //(i.e. count = 3)
unloadModel();

=> Count = 3, unloadModel() but no model exists at index 3 => error

Lines 71, 72:

If count < 3 (UPPER_BOUNDS) this will execute. So, when count = 2, it will execute but will increase to 3

count++; //increases count to 3
loadModel();  //error no model at index 3

Make sure that loadModel() and unloadModel() never get called with count greater than 2, or the maximum number of models in clones.


The following code for the FileLoader script should do what you want. It’s quite different -and much simpler- from your original code but should do the job fine. I provided plenty of comments in each part of the code, so you can understand the changes I made, as well as the functionality. A major change was removing the clones List, and replacing it with a currentModel GameObject, which also makes very easy referencing this from the GameController. In effect, a ‘currentIndex’ replaced ‘count’, and this always points to the element in the models List that is loaded in currentModel, and made available to the GameController through a readonly property.

One important thing that I want to say is that although this code provides the functionality you specify, it will not perform so well. The reason for this is that as the user goes forwards and backwards in the list of models, the Garbage Collector will be called all the time to clear all objects that are destroyed (or lose their reference). When the Garbage Collector kicks in, the performance of the application will drop, until the Garbage Collector finishes its job. A better way to implement this is to have all models inside the scene and disabled. Then instead of Instantiating and Destroying, you just disable the previous and enable the next model. Such a solution would perform much better because there is no Garbage Collector involved. To implement this, create an Empty GameObject in the scene and add to it -as children- all the models you have in Assets/Models. Then disable all of the children.

Then, all the GameObjects inside the models List will be instantiated GameObjects (and disabled), rather than prefabs. So, Inside the loadModel() method you don’t need to use Instantiate(), but currentModel.SetActive(false) to disable and currentModel.SetActive(true) to enable. I have included the relevant code for these changes, but I have commented it out, so you decide what you want.

So here is the code:

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

public class FileLoader : MonoBehaviour {

    //It seems that the only use of the following two fields is to get a reference to the Main Camera (SceneCamera)
    //and then assing it to the GameController's TouchRotation (assigned in Start())
    //Since FileLoader has no other use of either SceneCamera or TouchRotation, it is better to remove these fields from FileLoader
    //and have a public Camera field directly in TouchRotation
    public Camera SceneCamera; //should be moved to TouchRotation script
    public TouchRotation tRotation;  //remove

    public List<GameObject> models = new List<GameObject>(); // three game objects loaded in unity

    //You don't need this!
    //You just need ONE currentModel field where GameObjects from the models List are loaded
    //as you move to the next or previous element in Models
    //so, you don't even need an Unload method.  Every time you move in "models" the relative model will be loaded
    //as the currentModel
    //private List<GameObject> clones = new List<GameObject>(); // Remove!!!

    //private GameObject temp;  //no need for this, so remove

    //Renamed UPPER_BOUNDS to highestIndex and made private
    //This will always point to the highest index in the models List,
    //because the number of elements in the models List determines the highest possible index
    //for the availability of an element to be loaded as currentModel
    private int highestIndex;

    //This is the index of the element in models List that has already been loaded to currentModel
    private int currentIndex  = -1;  // initalize to -1, because no model loaded at start

    //Backing field for CurrentModel read-only property,
    //so that GameController can always read the CurrentModel through this property
    private GameObject currentModel;

    //GameController must have a reference to the FileLoader script in order to access this property
    //i.e. public FileLoader in GameController (probably in TouchRotation script)
    //then drag-n-drop the FileManager GameObject from the Hierarchy
    //to the FileLoader field in the Inspector of GameController
    public GameObject CurrentModel
    {
        get { return currentModel; }
    }

    // Use this for initialization
    void Start()
    {
        ObjReader.use.autoCenterOnOrigin = true;  //it seems this doesn't belong here... maybe moved to TouchRotation?

        TouchRotation tRotation = this.gameObject.AddComponent<TouchRotation>();  //remove
        tRotation.camera = this.camera;  //remove


        if (models.Count > 0)
        {
            //Set the highest index possible, which depends on the number of models available in the models List
            highestIndex = models.Count - 1;

            //Load the first model, so the user has something to see when the app starts
            //without having to click on any buttons
            currentModel = Instantiate(models[0]) as GameObject;

            ////If you decide to populate the models List with instantiated GameObjects in the scene (in a disabled state),
            ////rather than prefabs from Assets\Models, then replace the above line currentModel = Instantiate(models[0]) as GameObject;
            ////with the following:
            //currentModel = models[0];
            //currentModel.SetActive(true);

            currentIndex = 0;
        }
        else
        {
            Debug.LogError("No Models found!  Please add at least one model in the Models List.");
            Debug.Break();
        }
    }

    //Instantiates the GameObject at index position "index" in the models List
    //and references it with the currentModel variable
    public void loadModel(int index)
    {
        //make sure that when index is higher than highestIndex
        //it will be reset to 0 (according to specification)
        //
        //Since Previous() is the only point where currentIndex is decremented
        //but it will never make a loadModel() call unless currentIndex > 0,
        //the following condition index < 0 will always be FALSE.
        //So condition might appear redundant at this point, and may be removed.
        //However, it was included as a safety measure, in case of future code changes
        if (index > highestIndex || index < 0)
        {
            index = 0;
        }

        //Previous GameObject instance referenced by currentModel will be Garbage Collected
        currentModel = Instantiate(models[index]) as GameObject;

        ////If you decide to populate the models List with instantiated GameObjects in the scene (in a disabled state),
        ////rather than prefabs from Assets\Models, then replace the above line currentModel = Instantiate(models[index]) as GameObject;
        ////with the following:
        ////
        ////Disable current model - no need to check for null, since initialized in Start()
        //currentModel.SetActive(false);
        //currentModel = models[index]; //reference new model
        //currentModel.SetActive(true); //activate new model

        currentIndex = index;
    }

    //No need for an unloadModel() method
    //Just assign a new model from the model List to the currentModel field
    //
    //public void unloadModel()
    //{
    //    Destroy(clones[currentIndex]);
    //}

    //Renamed "incrementCounter" to Next because it's more descriptive of what it does
    //e.g. when the counter is at the highest number and then it goes to 0, it's actually decrementing the counter...
    //
    //Load the Next model
    public void Next()
    {
        //Since loadModel(0) is called in Start() this will always be TRUE
        //So the if{} statement might appear redundant at this point, and may be removed.
        //However, it was included as a safety measure, in case of future code changes
        if (currentIndex >= 0)
        {
            //after the model at the highestIndex has been previously loaded in currentModel
            //the parameter for the following loadModel() call will equal to highestIndex + 1
            //but it will be reset to 0 inside the loadModel() method,
            //in which case the first model (at index 0) will be loaded.
            //Therefore, when "Next" is called, it will call loadModel(),
            //with its parameter ranging from 0 to highestIndex + 1
            loadModel(currentIndex + 1);
        }
    }


    //Renamed "decrementCounter" to Previous because it's more descriptive of what it does
    public void Previous()
    {
        //if Next() has not be called at least once after the first element in the Models List is loaded
        //and currentIndex increased to a value greater than 0, calling the Previous() method will not do anything
        //so, loadModel() will never be called with a parameter that is smaller than 0.
        //Therefore, when "Previous" is called, it will call loadModel(),
        //with its parameter ranging from 0 to highestIndex - 1
        if (currentIndex > 0)
        {
            loadModel(currentIndex - 1);
        }
    }

}

When you start a list there’s a count of zero and null items in the list.

If you add an item then you have a count of 1 but the item number is 0.

Try moving the count++; to after loadModel();

I think you’re trying to add a model that’s not there.

Sorry if this is a bit vague it’s been a long day of cancelled trains and missed connections so my brain isn’t functioning at, I was going to say 100% but I think, at all is closer to the truth.

Problem is in this line:

clones[count] = Instantiate(models[count]) as GameObject;

You should use clones.Add and clones.Remove!

You can use clones after adding item to the list! ( 0 <= i < clones.Count )