[SOLVED]Adding and Removing GUI elements at runtime

Hi. I’m doing an inventory system where items will be appearing and disappearing from the GUI. I’m getting errors because of this tidbit:

So it seems the logical choice is to somehow have both the events complete, then change the GUI, and then let the next two events run.

I have no idea how to time the events like that though.
Here’s my code snippet:

if (equipPossess.getNumItems(Equippable.IronArmor) > 0)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Label(equipPossess.getItem(Equippable.IronArmor).itemIcon, GUILayout.Height(30), GUILayout.Width(30));
                GUILayout.Label(equipPossess.getItem(Equippable.IronArmor).itemName + " (x"
                    + equipPossess.getNumItems(Equippable.IronArmor) + ")");
                items[1] = GUILayout.Button(equipOrNot[1], GUILayout.Width(70), GUILayout.Height(20));
                GUILayout.EndHorizontal();
            }

Essentially, "Iron Armor’ is a type of body armor, and if there’s at least 1, will be displayed. When this item is equipped to the player, the item is to disappear from the list, so if there was only 1 Iron Armor, then the whole entry should disappear.

I don’t understand, are you having a bug ? What is it (visual aspect, errors in console…)?

Error:

sure its that block of code causing it?

your problem sounds more like it comes from a block with

BeginHorizontal
loop(0 to x) drawelements
EndHorizontal

in that case you would need an if around the whole thing, cause in case of 0 elements you must never enter the block (that or not use GUILayout at all, cause guilayout blocks expect that you put something in or burn your ass :wink: )

also you don’t need to mess with events, its not like you get anything from them in your case

1 Like

I don’t have any block like that in my code, but I have nested all my for loops (that use a .Length as the upper bound) inside an if branch. The problem still persists though.

Like the code snippet showed, the Horizontal group is nested in an if branch, so it should begin nor end if there’s nothing to display. :?

Can you post the script that defines the class of the equipPossess object? Also, can you post the whole of the OnGUI function?

Sure no problem! Here’s the EquipmentPossess class

public class EquipmentPossess : MonoBehaviour
{
    /**** GRAND LIST OF EQUIPMENT *****
     * 0. Bronze Armor
     * 1. Iron Armor
     * 2. Proto Armor
     * 3. Alpha Stone
     * 4. Beta Stone
     */
    int bronze, iron, proto, alpha, beta;
    Dictionary<Transform, int> equipment;
    public Dictionary<Transform, int> Equipment
    {
        get { return equipment; }
    }

    public Transform[] objects;
    UnityEngine.Object i;

    void Awake()
    {
        //Create a Dictionary mapping Item Prefabs to the amount.
        equipment = new Dictionary<Transform, int>();
        for (int j = 0; j < objects.Length; j++)
        {
            equipment.Add(objects[j], 0);
        }

        //Map the integers to the Equippable enums
        bronze = (int)Equippable.BronzeArmor;
        iron = (int)Equippable.IronArmor;
        proto = (int)Equippable.ProtoArmor;
        alpha = (int)Equippable.AlphaStone;
        beta = (int)Equippable.BetaStone;
    }

    void Start()
    {
        equipment[objects[proto]] = 1;
        equipment[objects[bronze]] = 2;
        equipment[objects[alpha]] = 1;
        equipment[objects[beta]] = 2;
    }

    //This will return the quantity of a given item 'a'.
    public int getNumItems(Equippable a)
    {
        return equipment[objects[(int)a]];
    }

    //This will return the item that's used.
    public Equipment getItem(Equippable a)
    {
        return objects[(int)a].GetComponent<Equipment>();
    }

    //This will adjust the quantity of an item
    public void setNumItems(Equippable a, int amount)
    {
        equipment[objects[(int)a]] += amount;
    }

    //This will return the item that's used.
    public Equipment getItem(int a)
    {
        return objects[a].GetComponent<Equipment>();
    }

    public bool anyItems()
    {
        int result = 0;
        for (int i = 0; i < 17; i++)
        {
            result += equipment[objects[i]];
        }
        if (result == 0)
        { return false; }
        else { return true; }
    }
}

The OnGUI method’s quite lengthy, so it was divided into several methods based on the purpose and all called with the OnGUI method. Just to be safe though, here’s the code:

void OnGUI()
    {
        GUI.skin = thisCyanSkin;
        Setup();
    }

void Setup()
    {
        /* SET UP OF VARIABLES */
        skills[0] = SP1_level1_On ? cyanColored : normal;
        skills[1] = SP1_level2_On ? cyanColored : normal;
        skills[2] = SP1_level3_On ? cyanColored : normal;
        skills[3] = SP2_level1_On ? cyanColored : normal;
        skills[4] = SP2_level2_On ? cyanColored : normal;
        skills[5] = SP2_level3_On ? cyanColored : normal;

        //Set up the menu
        GUILayout.BeginArea(new Rect(xWindow, yWindow, lWin, hWin));
        BasicStats();
        Skills();
        Equipment();
        GUILayout.EndArea();
    }

/* BASIC STATS */
    void BasicStats()
    {
        GUILayout.BeginArea(new Rect(0, 0, 640, 160), style);
        GUILayout.Label("Basic Stats", boldened);
        GUILayout.BeginHorizontal();
        GUILayout.BeginVertical(GUILayout.Width(120));
        GUILayout.Label(statsIcon, normal);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(60));
        GUILayout.Label("Name: ", normal);
        GUILayout.Label("Level: ", normal);
        GUILayout.Label("EXP: ", normal);
        GUILayout.Label("Next: ", normal);
        GUILayout.Label("Status: ", normal);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(160));
        GUILayout.Label(player.chara.name, normal);
        GUILayout.Label(player.level.ToString(), normal);
        GUILayout.Label(player.Experience.ToString(), normal);
        GUILayout.Label(player.ToNextL.ToString(), normal);
        GUILayout.Label(player.chara.status.ToString(), normal);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(60));
        GUILayout.Label("HP: ", normal);
        GUILayout.Label("MP: ", normal);
        GUILayout.Label("Attack: ", normal);
        GUILayout.Label("Wisdom: ", normal);
        GUILayout.Label("Defense: ", normal);
        GUILayout.Label("Agility: ", normal);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(100));
        GUILayout.Label(player.chara.hp.ToString() + " / " + player.chara.maxHP.ToString(), normal);
        GUILayout.Label(player.chara.mp.ToString() + " / " + player.chara.maxMP.ToString(), normal);
        GUILayout.Label(player.chara.attack.ToString(), normal);
        GUILayout.Label(player.chara.wisdom.ToString(), normal);
        GUILayout.Label(player.chara.defense.ToString(), normal);
        GUILayout.Label(player.chara.agility.ToString(), normal);
        GUILayout.EndVertical();
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    /* SKILLS ( KANOHI) CONFIGURATION */
    void Skills()
    {
        GUILayout.BeginArea(new Rect(0, 160, 640, 160), style);
        GUILayout.Label("Kanohi and Skills", boldened);
        GUILayout.BeginHorizontal();
        //Kanohi Panel
        GUILayout.BeginVertical(GUILayout.Width(280));
        GUILayout.Space(20);
        GUILayout.BeginHorizontal();
        for (int i = 0; i < 6; i++)
        {
            kanohi[i] = GUILayout.Toggle(kanohi[i], "", kanohiMasks[i], GUILayout.Width(40), GUILayout.Height(40));
        }
        GUILayout.EndHorizontal();
        GUILayout.Space(10);
        GUILayout.BeginHorizontal();
        for (int i = 6; i < 12; i++)
        {
            kanohi[i] = GUILayout.Toggle(kanohi[i], "", kanohiMasks[i], GUILayout.Width(40), GUILayout.Height(40));
        }
        GUILayout.EndHorizontal();
        GUILayout.EndVertical();
        //Skill Points Panel
        GUILayout.BeginVertical(GUILayout.Width(120));
        GUILayout.Label("\nSkill Points", normal);
        GUILayout.Label(player.SkillPoints.ToString(), big, GUILayout.Width(100));
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(120));
        GUILayout.Label("\nSkill Level", normal);
        //The Skill level for the first skill
        GUILayout.BeginHorizontal();
        SP1_level1_On = GUILayout.Toggle(SP1_level1_On, "", level1, GUILayout.Width(20), GUILayout.Height(20));
        GUILayout.Space(10);
        if (SP1_level1_On)
        {
            SP1_level2_On = GUILayout.Toggle(SP1_level2_On, "", level2, GUILayout.Width(20), GUILayout.Height(20));
        }
        else
        {
            GUILayout.Label("", level2, GUILayout.Width(20), GUILayout.Height(20));
            SP1_level2_On = false;
        }
        GUILayout.Space(10);
        if (SP1_level1_On  SP1_level2_On)
        {
            SP1_level3_On = GUILayout.Toggle(SP1_level3_On, "", level3, GUILayout.Width(20), GUILayout.Height(20));
        }
        else
        {
            GUILayout.Label("", level3, GUILayout.Width(20), GUILayout.Height(20));
            SP1_level3_On = false;
        }
        GUILayout.EndHorizontal();
        GUILayout.Space(5);
        //The Skill level for the second skill
        GUILayout.BeginHorizontal();
        SP2_level1_On = GUILayout.Toggle(SP2_level1_On, "", level1, GUILayout.Width(20), GUILayout.Height(20));
        GUILayout.Space(10);
        if (SP2_level1_On)
        {
            SP2_level2_On = GUILayout.Toggle(SP2_level2_On, "", level2, GUILayout.Width(20), GUILayout.Height(20));
        }
        else
        {
            GUILayout.Label("", level2, GUILayout.Width(20), GUILayout.Height(20));
            SP2_level2_On = false;
        }
        GUILayout.Space(10);
        if (SP2_level1_On  SP2_level2_On)
        {
            SP2_level3_On = GUILayout.Toggle(SP2_level3_On, "", level3, GUILayout.Width(20), GUILayout.Height(20));
        }
        else
        {
            GUILayout.Label("", level3, GUILayout.Width(20), GUILayout.Height(20));
            SP2_level3_On = false;
        }
        GUILayout.EndHorizontal();
        GUILayout.EndVertical();
        //The list of skills
        GUILayout.BeginVertical(GUILayout.Width(120));
        GUILayout.Label("Wateraga", skills[2]);
        GUILayout.Label("Watera", skills[1]);
        GUILayout.Label("Water", skills[0]);
        GUILayout.Label("", normal);
        GUILayout.Label("Toa", skills[3]);
        GUILayout.Label("Toara", skills[4]);
        GUILayout.Label("Toaraga", skills[5]);
        GUILayout.EndVertical();
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    /* EQUIPMENT */
    void Equipment()
    {
        GUILayout.BeginArea(new Rect(0, 320, 640, 160), style);
        GUILayout.Label("Equipment", boldened);
        GUILayout.BeginHorizontal();
        GUILayout.BeginVertical(GUILayout.Width(60));
        GUILayout.Label("Weapon: ", normal);
        GUILayout.Space(6);
        GUILayout.Label("Kanohi: ", normal);
        GUILayout.Space(6);
        GUILayout.Label("Armor: ", normal);
        GUILayout.Space(6);
        GUILayout.Label("Stone: ", normal);
        GUILayout.Space(6);
        GUILayout.Label("Accessory: ", normal);
        GUILayout.Space(6);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(100));
        GUILayout.Label("Cyan Sword", normal);
        GUILayout.Space(6);
        GUILayout.Label(mask, normal);
        GUILayout.Space(6);
        GUILayout.Label(armor, normal);
        GUILayout.Space(6);
        GUILayout.Label(stone, normal);
        GUILayout.Space(6);
        GUILayout.Label(extra, normal);
        GUILayout.Space(6);
        GUILayout.EndVertical();
        GUILayout.BeginVertical(GUILayout.Width(160));
        for (int i = 0; i < 5; i++)
        {
            changeEquipment[i] = GUILayout.Button("Change", GUILayout.Width(60), GUILayout.Height(16));
        }
        GUILayout.EndVertical();
        ChangeEquipment();
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    /* This method handles the right side of the equipment
     * panel. You choose which item you want to equip
     * from a list */
    void ChangeEquipment()
    {
        //If the Weapons panel is activated
        if (changeEquipment[5])
        {
            GUILayout.BeginVertical(GUILayout.Width(320));
            scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(280), GUILayout.Height(120));
            GUILayout.EndScrollView();
            GUILayout.EndVertical();
        }

        //If the Kanohi panel is activated
        if (changeEquipment[6])
        {
            GUILayout.BeginVertical(GUILayout.Width(320));
            scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(280), GUILayout.Height(120));
            GUILayout.EndScrollView();
            GUILayout.EndVertical();
        }

        //If the Armor panel is activated, then display all of the armor equipment ([Icon][Name][Equip Button]). 
        //The 'if' blocks below handle what happen if the Equip button is pressed.
        if (changeEquipment[7])
        {
            GUILayout.BeginVertical(GUILayout.Width(320));
            scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(280), GUILayout.Height(120));

            //BRONZE ARMOR
            if (equipPossess.getNumItems(Equippable.BronzeArmor) > 0)
            {
                GUILayout.BeginHorizontal();
                GUIContent bronzeArm = new GUIContent(equipPossess.getItem(Equippable.BronzeArmor).itemName + " (x"
                    + equipPossess.getNumItems(Equippable.BronzeArmor) + ")", equipPossess.getItem(Equippable.BronzeArmor).itemIcon);
                GUILayout.Label(bronzeArm, GUILayout.Height(30));
                items[0] = GUILayout.Button(equipOrNot[0], GUILayout.Width(70), GUILayout.Height(20));
                GUILayout.EndHorizontal();
            }

            //IRON ARMOR
            if (equipPossess.getNumItems(Equippable.IronArmor) > 0)
            {

            }

            //PROTO ARMOR
            if (equipPossess.getNumItems(Equippable.ProtoArmor) > 0)
            {
                GUILayout.BeginHorizontal();
                GUIContent protoArm = new GUIContent(equipPossess.getItem(Equippable.ProtoArmor).itemName + " (x"
                    + equipPossess.getNumItems(Equippable.ProtoArmor) + ")", equipPossess.getItem(Equippable.ProtoArmor).itemIcon);
                GUILayout.Label(protoArm, GUILayout.Height(30));
                items[2] = GUILayout.Button(equipOrNot[2], GUILayout.Width(70), GUILayout.Height(20));
                GUILayout.EndHorizontal();
            }

            //This is the default Unequip button. This is for if there's only one armor equipment in the inventory.
            items[3] = GUILayout.Button("Unequip", GUILayout.Width(70), GUILayout.Height(20));

            GUILayout.EndScrollView();
            GUILayout.EndVertical();

            //These if blocks decide what happens with the Equip or Unequip button is pressed
            if (items[0])
            {
                if (!itemsEquipped[0])
                {
                    if (player.Armor == null)
                    {
                        player.Armor = equipPossess.getItem(Equippable.BronzeArmor);
                        equipPossess.setNumItems(Equippable.BronzeArmor, -1);
                        armor = equipPossess.getItem(Equippable.BronzeArmor).itemName;
                    }
                }
                else
                {
                    equipPossess.setNumItems(Equippable.BronzeArmor, 1);
                    armor = "";
                    player.Armor = null;
                }
            }

            if (items[1])
            {
                if (!itemsEquipped[1])
                {
                    if (player.Armor == null)
                    {
                        player.Armor = equipPossess.getItem(Equippable.IronArmor);
                        equipPossess.setNumItems(Equippable.IronArmor, -1);
                        armor = equipPossess.getItem(Equippable.IronArmor).itemName;
                    }
                }
                else
                {
                    equipPossess.setNumItems(Equippable.IronArmor, 1);
                    armor = "";
                    player.Armor = null;
                }
            }

            if (items[2])
            {
                if (!itemsEquipped[2])
                {
                    if (player.Armor == null)
                    {
                        player.Armor = equipPossess.getItem(Equippable.ProtoArmor);
                        equipPossess.setNumItems(Equippable.ProtoArmor, -1);
                        armor = equipPossess.getItem(Equippable.ProtoArmor).itemName;
                    }
                }
                else
                {
                    equipPossess.setNumItems(Equippable.ProtoArmor, 1);
                    armor = "";
                    player.Armor = null;
                }
            }

            //The Default Unequip button
            if (items[3])
            {
                if (player.Armor == equipPossess.getItem(Equippable.ProtoArmor)) equipPossess.setNumItems(Equippable.ProtoArmor, 1);
                if (player.Armor == equipPossess.getItem(Equippable.BronzeArmor)) equipPossess.setNumItems(Equippable.BronzeArmor, 1);
                if (player.Armor == equipPossess.getItem(Equippable.IronArmor)) equipPossess.setNumItems(Equippable.IronArmor, 1);
                player.Armor = null;
                armor = "";
            }
        }

        //If the Stone panel is activated. See Armor for full description
        if (changeEquipment[8])
        {
            GUILayout.BeginVertical(GUILayout.Width(320));
            scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(280), GUILayout.Height(120));

            //ALPHA STONE
            if (equipPossess.getNumItems(Equippable.AlphaStone) > 0)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Label(equipPossess.getItem(Equippable.AlphaStone).itemIcon, GUILayout.Height(30), GUILayout.Width(30));
                GUILayout.Label(equipPossess.getItem(Equippable.AlphaStone).itemName + " ("
                    + equipPossess.getNumItems(Equippable.BronzeArmor) + ")");
                items[0] = GUILayout.Button(equipOrNot[0], GUILayout.Width(70), GUILayout.Height(20));
                GUILayout.EndHorizontal();
            }

            //BETA STONE
            if (equipPossess.getNumItems(Equippable.BetaStone) > 0)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Label(equipPossess.getItem(Equippable.BetaStone).itemIcon, GUILayout.Height(30), GUILayout.Width(30));
                GUILayout.Label(equipPossess.getItem(Equippable.BetaStone).itemName + " ("
                    + equipPossess.getNumItems(Equippable.BetaStone) + ")");
                items[1] = GUILayout.Button(equipOrNot[1], GUILayout.Width(70), GUILayout.Height(20));
                GUILayout.EndHorizontal();
            }
            GUILayout.EndScrollView();
            GUILayout.EndVertical();
        }

        //If the Accessory panel is activated
        if (changeEquipment[9])
        {
            GUILayout.BeginVertical(GUILayout.Width(320));
            scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Width(280), GUILayout.Height(120));
            GUILayout.EndScrollView();
            GUILayout.EndVertical();
        }
    }

Nothing stands out as being particularly disastrous here so the problem is probably something subtle. The general approach I would recommend is to comment out the three function calls (not counting the BeginArea/EndArea) one by one to see if one of them in particular is generating the errors. You can then continue this to narrow down the exact sections of code that are causing trouble. With this information, it should be much easier to establish what is going wrong.

I believe your own assessment of the problem is completely correct.

The way I would implement this (besides using a lot less magic numbers :P), is by not changing the state of the inventory directly. Instead of setting (for instance) item[2] like this:

items[2] = GUILayout.Button(equipOrNot[2], GUILayout.Width(70), GUILayout.Height(20));

I would use a Coroutine like this:

if (GUILayout.Button(equipOrNot[2], GUILayout.Width(70), GUILayout.Height(20)))
    StartCoroutine("SetItem", 2);

The Coroutine would look like this:

IEnumerator SetItem(int num)
{
    // wait for end of frame to make sure all OnGUI stuff is done.
    yield return new WaitForEndOfFrame();

    // now follows a minor modification of the handling code you already have
    
    //These if blocks decide what happens with the Equip or Unequip button is pressed
    if (num == 0)
    {
        if (!itemsEquipped[0])
        {
            if (player.Armor == null)
            {
                player.Armor = equipPossess.getItem(Equippable.BronzeArmor);
                        equipPossess.setNumItems(Equippable.BronzeArmor, -1);
                armor = equipPossess.getItem(Equippable.BronzeArmor).itemName;
            }
        }
        else
        {
         //insert the rest of your items code here
         // ...
}

This will separate the drawing from the actual modifications.

And unrelated to your problem:
I’d highly recommend to switching to an array based approach (or at the very least to use an enum instead of ints to identify your different equip slots), as your current implementation looks very difficult to maintain or extend.

Hey guys, thanks for your help. I’ve taken all your suggestions into consideration, and I’ve decided to rewrite my inventory script, with the help of the drag-and-drop inventory script that’s in Showcase. Now, everything’s more modular and a lot easier to maintain, AND it works! :slight_smile:

Solution for anyone else who has this problem: See if you can make any GUILayout elements that may disappear a GUI element. Thanks to GUILayoutUtility.GetRect(), mixing GUILayout and GUI elements together is not that hard.

Thanks again!

This error happens when using the GUILayout container and removing any of its children within OnGUI().

Seems the GUILayout takes into account the whole sub-hierarchy before it starts drawing, and expects the hierarchy to stay the same until it finishes the pass. It doesn’t like any of the child components to be removed until the OnGUI pass is finished.

The fix would be setting a flag within OnGUI (e.g. _removedElementIndex = 3) and process the change later (removing this element inside the Update method).

1 Like

Solutiuon did not work for me.

But I got another:

Actually, if this error is from a mismatch in some data between repaint and some other events, caused by recompile happening in a bad moment(somewhere between those events), there is a simple haxor that could fix this:

public class SomeEditorWindow : EditorWindow 
{
    //your declared variables, etc...

    bool handleRepaintErrors;

    void OnGUI()
    {
        if(Event.current.type == EventType.repaint && !handleRepaintErrors)
        {
            handleRepaintErrors = true;
            return;
        }

        //your ongui code goes here
    }
}

This way, if there is a recompile, the bool will reset to false value. With that, you just skip the first update, where the error occurs, and draw your onGUI code in the next frame.