UnityEditor - How do I prevent a for loop from affecting multiple variables in unityEditor array?

I’ll try to make this concise as possible. I’m making a custom editor to manage my Levels for my game. I have 3 arrays I’m using: levels, subLevels, and units. Levels contain multiple subLevels and subLevels contain multiple units. Units being the possible enemy types that can spawn in that subLevel.

I’ve created buttons that I can use to easily add or subtract a level, subLevel, or Unit. When I add a new subLevel, it copies the values and inserts the new subLevel. After I added the subLevel, if I attempt to change a unit to a different type in one subLevel, it changes the respective element in ALL subLevels in that particular level. This is the main problem I’m trying to fix. IF I remove/add a unit of a subLevel, this particular subLevel won’t be affected by the aforementioned problem.

This problem seems to start on line 82. The for loop is going through each subLevel from the for loop on line 36. I don’t know how to setup my code to ONLY affect the desired index on line 108 and 109. I’ve tried to break/continue out of the for loop if GUI.changed, but to no luck.

If you need more information, don’t hesitate to ask. I’ve been stuck on the problem for a week now

[CustomEditor(typeof(LevelManager))]
public class EditorLevelManger : Editor
{
    public override void OnInspectorGUI()
    {
        //base.OnInspectorGUI();
        Undo.RecordObject(this, name);

        LevelManager script = (LevelManager)target;
        EditorGUILayout.LabelField("Total Levels:  " + script.level.Length);
        EditorGUILayout.Space();

        //Loop Through Level Array
        for (int i = 0; i < script.level.Length; i++)
        {
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Level " + i, EditorStyles.boldLabel, GUILayout.MaxWidth(60));
            //Remove index from array
            if (GUILayout.Button("-", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)) && script.level.Length > 1)
            {
                ArrayUtility.RemoveAt(ref script.level, i);
                break;
            }

            //If last index in array, Add element instead of Insert
            if (GUILayout.Button("+", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)))
            {
                if (i == script.level[i].subLevel.Length - 1)
                    ArrayUtility.Add(ref script.level, script.level[i]);
                else
                    ArrayUtility.Insert(ref script.level, i, script.level[i]);
            }
            EditorGUILayout.EndHorizontal();

            //Loop Through subLevel Array
            for (int u = 0; u < script.level[i].subLevel.Length; u++)
            {
                EditorGUI.indentLevel = 1;
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField("Sub " + u, EditorStyles.miniBoldLabel, GUILayout.MaxWidth(60));

                // Setting Up Distance per level using sub-levels
                int[] test = new int[script.level.Length];

                for (int x = 0; x < script.level.Length; x++)
                {
                    for (int y = 0; y < script.level[x].subLevel.Length; y++)
                    {
                        if (x == i && y < u)
                            test[i] += script.level[x].subLevel[y].distance;
                    }
                }

                EditorGUILayout.LabelField(test[i].ToString() + "m", GUILayout.MaxWidth(60));


                //Remove index from array
                if (GUILayout.Button("-", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)) && script.level[i].subLevel.Length > 1)
                {
                    ArrayUtility.RemoveAt(ref script.level[i].subLevel, u);
                    break;
                }

                //If last index in array, Add element instead of Insert
                if (GUILayout.Button("+", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)))
                {
                    if (i == script.level[i].subLevel.Length - 1)
                        ArrayUtility.Add(ref script.level[i].subLevel, script.level[i].subLevel[u]);
                    else
                        ArrayUtility.Insert(ref script.level[i].subLevel, u, script.level[i].subLevel[u]);
                }
                EditorGUILayout.EndHorizontal();

                EditorGUI.indentLevel = 1;
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField("Distance", GUILayout.MaxWidth(90));
                script.level[i].subLevel[u].distance = EditorGUILayout.IntField(script.level[i].subLevel[u].distance, GUILayout.MaxWidth(80));
                EditorGUILayout.EndHorizontal();

                //Loop Through Units Array
                for (int j = 0; j < script.level[i].subLevel[u].units.Length; j++)
                {
                    EditorGUILayout.BeginHorizontal();
                    GUI.enabled = false;
                    EditorGUI.indentLevel = 0;
                    EditorGUILayout.LabelField(j.ToString("F0"), GUILayout.MaxWidth(15));
                    GUI.enabled = true;

                    //Remove index from array
                    if (GUILayout.Button("-", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)) && script.level[i].subLevel[u].units.Length > 1)
                    {
                        ArrayUtility.RemoveAt(ref script.level[i].subLevel[u].units, j);
                        break;
                    }

                    //If last index in array, Add element instead of Insert
                    if (GUILayout.Button("+", GUILayout.MaxWidth(25), GUILayout.MaxHeight(14)))
                    {
                        if (j == script.level[i].subLevel[u].units.Length - 1)
                            ArrayUtility.Add(ref script.level[i].subLevel[u].units, script.level[i].subLevel[u].units[j]);
                        else
                        {
                            ArrayUtility.Insert(ref script.level[i].subLevel[u].units, j++, script.level[i].subLevel[u].units[j]);
                            script.level[i].subLevel[u].units[j].unitType = Unit.Type.__;
                        }
                    }
                    script.level[i].subLevel[u].units[j].unitType = (Unit.Type)EditorGUILayout.EnumPopup(script.level[i].subLevel[u].units[j].unitType, GUILayout.MaxWidth(100));
                    script.level[i].subLevel[u].units[j].spawnPercent = EditorGUILayout.FloatField(script.level[i].subLevel[u].units[j].spawnPercent, GUILayout.MaxWidth(30));
                    EditorGUILayout.LabelField("%", GUILayout.MaxWidth(50));
                    EditorGUILayout.EndHorizontal();
                }
            }
            EditorGUILayout.Space();
            EditorGUILayout.Space();
            EditorGUI.indentLevel = 0;
        }

        if (GUI.changed)
        {
            serializedObject.ApplyModifiedProperties();
            EditorUtility.SetDirty(script);
            EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
        }
    }
}

Your code is both adding, inserting and removing items from an array whose size you are relying on for controlling loop behavior. I see you break when you remove stuff, but you probably want to break when you add stuff, because I’m not sure your loop state is as stable as you expect, particularly with regard to partial calculations like the test[ ] array and whatnot.

Instead you might want to make and use a COPY of the array for display purposes, but modify the original array for action purposes. Then you don’t need to break anywhere, just let it run to completion, and the next frame it will be updated with the modified copy of the array, as it gets a new copy from the original array.

1 Like

Oh wait, strike my earlier response. I think I see the problem. You’re not actually copying the element. You’re just inserting it into the list again. It’s the same element, so when you change one, you are changing one, but it’s being displayed multiple times. Same goes for levels or units. They are objects, which are reference types.

Thanks for your response, Kurt. I’ve tried to mess around with the code to create a “new” entry into the array instead of a copy but I’m not sure how to do that properly. It keeps returning “Object not set to a reference of an object”

Adding/removing from arrays are pretty new to me. Any hints to get me pushed in the right direction?

Broadly what you want is called a “deep copy.” You can google about it with those words.

The most reliable way to do this is to make your own “MakeDeepCopy” method for your objects. This method would know best how to produce a copy, given the assumptions you have about your data.

For instance, when you call Instantiate on a Unity GameObject, it is essentially doing a deep copy, but with knowledge about how GameObjects are used. Specifically it replicates everything down the line, giving you a brand-new copy. But it is smart: it won’t clone things beyond a certain point. For instance it won’t clone materials; it maintains the same material references, mesh references, etc.

I looked up Deep Copy but that is way to complex for my understanding at the time. I was unaware about ‘.Clone()’ at the end of an array, that was the solution to my problem. I was using System.Array.Copy() but apparently that is only used on the .NET side of things and didn’t work, at least in my case. Once again, appreciate your help and for steering me in the right direction. Cheers.

1 Like