Place Inventory Into GUI Button Grid

--- EDIT --- (Illustration added)

Hello,

I'm working on a basic Inventory, so far, all it does is:

User selects a specific object from the scene (eg: knife), this is then added as a GUI button in the inventory.

All I'm struggling with is places the Items/GUI Buttons in a grid format. All that happens right now, is if I pick up a knife - a GUI button is added to the inventory, then if I pick up something else, that knife button is simply 'replaced' by that.

What I want, is:

User picks up item/object, this is then added to inventory as button. User then picks up another object, and a button is placed, say 50 pixels downward, and so on.

Here is an illustration to get an idea: alt text

Script:

 var knife : GameObject;
static var knifeInventory : boolean = false;

var password : GameObject;
static var passwordInventory : boolean = false;

private var inventoryPanel : boolean = false;

var selGridInt : int = -1;
var objGroup = new Array(GameObject);
var objItemNames = new Array(String);

function Update(){
    if(knifeInventory == true){
        knife.gameObject.SetActiveRecursively(false);
        selGridInt ++;
        objGroup[selGridInt] = knife;
        objItemNames[selGridInt] = "Knife";
    }
    else {
        knife.gameObject.SetActiveRecursively(true);
    }

    if(passwordInventory == true){
        password.gameObject.SetActiveRecursively(false);
        selGridInt ++;
        objGroup[selGridInt] = password;
        objItemNames[selGridInt] = "Password";
    }
    else{
        password.gameObject.SetActiveRecursively(true);
    }

}

function OnGUI(){
if(Input.GetButton("Jump")){
    if(inventoryPanel == false){
        if(GUI.Button(Rect(10,70,120,30),"Show Inventory")){
            inventoryPanel = true;
        }   
    }
    else if(inventoryPanel == true){
        if(GUI.Button(Rect(10,70,120,30),"Hide Inventory")){
            inventoryPanel = false;
        }   
    }
    if(inventoryPanel == true){
        //selGridInt = GUI.SelectionGrid (Rect (10, 120, 120, 400), selGridInt, objItemNames, 1);
        GUI.BeginGroup (new Rect (10, 120, 120, 400));
        GUI.Box(Rect(0,0,120,400),"");
        GUI.Button(Rect(0,10,120,30), objItemNames[selGridInt]);
        GUI.EndGroup();
    }

}   
}

EDIT: After reading your edit, I think the Dictionary option is better suited for your needs. And the first code block should work without any problems. I got a harmless error when running, but it shouldn't be a problem.

First of all, you cannot create an array like this: `new Array(GameObject)`. That isn't a possible argument. Logically, you want to store an object with a name so a dictionary makes the most sense because it associates a value (in this case a reference) with a Key such as its name. So I decided to do this thing called Debugging to find the problems with the script I posted earlier and it turns out that it works splendid. Go figure :). So I fixed the bugs (why it is `string` in C# and `String` in js is beyond me) and now it should work fine.

public var items = new System.Collections.Generic.Dictionary.<String,GameObject>();
//Create the dictionary full of a name and a GameObject associated with it.
private var curItem : System.Collections.Generic.KeyValuePair.<String, GameObject>;
//This will store the current item.

public var buttonHeight = 50;
public var width = 100;

function AddItemToInventory (item : GameObject) {
     items.Add(item.name, item);
}

function OnGUI() {
        var curIndex = 0;

        GUI.BeginGroup (new Rect (10, 120, 120, 400));

        for(var item : System.Collections.Generic.KeyValuePair.<String, GameObject> in items) {

            //iterate through all the items in the dictionary.
             var rect : Rect = new Rect(0, buttonHeight * curIndex, width, buttonHeight);
             //Create a rect for the button.  It is offset more for each item in the array.
             if(GUI.Button(rect, item.Key)) {
                  curItem = item;             
                  //USE ITEM
                  RemoveItem(item);
                  //if the user clicks the button, select it as the current item.
             }
             curIndex++;
             //increase the offset counter.
        }

        GUI.EndGroup();
}

//I added this function because I was getting an error deleting the KeyValuePair during the iterator 
function RemoveItem (item : System.Collections.Generic.KeyValuePair.<String, GameObject>) { 
     yield WaitForEndOfFrame;
     items.Remove(item.Key);
}
//This code was in the comments, but I moved it up here so that everyone could see it.

function RemoveItem (item : GameObject) { 
     yield WaitForEndOfFrame;
     items.Remove(item.name);
}

This script should do exactly what you need. Call `AddItemToInventory()` to add an item. Then in the GUI, the script iterates through the dictionary and for each item it does the following:

  • Create a rectangle that will be our final box. Offset each time so that it isn't on top of the one before it.

  • Create the button. It simply displays the name of the item in the box.

  • If the user clicks the button, do whatever you want with it, then remove it from the Dictionary.

  • Then it won't show up in the list anymore. And all the items will move up accordingly.

I figured I leave the list up since I had originally posted it and someone might find it useful. This one creates 2 lists, one storing the object and another storing its name which is a strange design concept, but it would get the job done. It displays a GUI toolbar that will display all the names of objects in the list. But you would then have to do something accordingly and I don't think this way will do what you want as well as the other.

public var items : System.Collections.Generic.List.<GameObject>();
//Create a list of all the items in our inventory

private var itemNames : System.Collections.Generic.List.<string>();
//Create a list of all their names for the GUI.

public var curItemID : int;
//Use this to find the item in your array.

function AddItemToInventory (item : GameObject) {
     items.Add(item);
     itemNames.Add(item.name);
//fixed this line.
}

function OnGUI() {

        GUI.BeginGroup (new Rect (10, 120, 120, 400));

        curItemID = GUI.Toolbar(someScreenRect, curItemID itemNames.ToArray() );
        //This will automatically add another box for each item you have

        GUI.EndGroup();
}

Don't know if this helps or not:

var inventory : Array;
public var emptyTex : Texture;
var iconWidthHeight = 20; 

// Create the an 8x8 Texture-Array 
function Awake() 
{ 
    inventory = new Array(8); 
    for( var i = 0; i < inventory.length; i ++ ) 
    { 
        inventory *= new Array(8);* 
 *}* 
*}* 
*function OnGUI()* 
*{* 
 *var texToUse : Texture;*
 *//Go through each row* 
 *for( var i = 0; i < inventory.length; i ++ )* 
 *{* 
 *// and each column* 
 _for( var k = 0; k < inventory*.length; k ++ )*_ 
 _*{*_ 
 _*texToUse = emptyTex;*_
 _*//if there is a texture in the i-th row and the k-th column, draw it*_ 
 <em>_if( inventory*[k] != null )*_</em> 
 <em>_*{*_</em> 
 <em><em>_texToUse = inventory*[k];*_</em></em>
 <em><em>_*}*_</em></em> 
 <em><em><em>_GUI.Label( new Rect( k*iconWidthHeight, i*iconWidthHeight, iconWidthHeight, iconWidthHeight ), texToUse );_</em></em></em>
 <em><em>_*}*_</em></em> 
 <em><em>_*}*_</em></em> 
<em><em>_*}*_</em></em> 
<em><em>_*```*_</em></em>
<em><em>_*<p>Not that great at arrays myself and this script is not mine I found it here:*_</em></em>
<em><em>_*<a href="http://forum.unity3d.com/threads/11167-Laying-out-Icons-In-an-inventory-that-will-rearrange-to-fit" rel="nofollow">http://forum.unity3d.com/threads/11167-Laying-out-Icons-In-an-inventory-that-will-rearrange-to-fit</a></p>*_</em></em>
<em><em>_*<p>hope this helps you</p>*_</em></em>
<em><em>_*<p>Scribe</p>*_</em></em>

The GUI gets cleared every time OnGUI gets called, so any previous Button calls you made are lost forever. This is why your boxes appear to disappear -- your rendering focus changes as the selection goes up. OnGUI also gets called every render update, instead of every change. (As opposed to Flash, which remembers what you render, and can be configured to only update when you make changes to the UI). This is kind of inefficient but has it's benefits in the long run.

So, how can you accomplish what you want?

Say you have an array of strings that you want to draw in a grid. (Doing strings for simplicity, you can change it to whatever you want, and have the handling be slightly different.)

This code would render those as selectable buttons in a grid: (As testing, I suggest copying and pasting then tire code into a new script, and adding that script to an object, then running, to see how it runs)

Note that button "0" will be invisible as it is initially selected.

//Depending upon what array type you use, slightly different solutions may be needed.
//I'll go ahead and use a normal JS array.
var arrayOfStrings = new Array();
var selected = 0;//This is not used how you use it. It is the player-decided selection.

function Start()
{
    //Initialize our vars.
    selected = 0;
    arrayOfStrings = new Array();

    //Add a whole bunch of strings from 0-50.
    for (var i = 0; i < 50; i++)
    {
        arrayOfStrings.Add("" + i);
    }
    //Remove the 13th because it's unlucky.
    arrayOfStrings.RemoveAt(13);//All indexes still start at 0, but we can do 13 because we have the number "0" in the array.
    //Remove the last one because he slept in today.
    arrayOfStrings.RemoveAt(arrayOfStrings.length - 1);

    //Remove 30 because he marks the age you become old.
    //We don't want to become old.
    arrayOfStrings.Remove("30");
}

function OnGUI()
{
    selected = DrawStringArray(arrayOfStrings, 7, new Vector2(50,25), selected);//Call oru helper function! :)

}

//Call this in OnGUI when you want your buttons to be displayed.
function DrawStringArray(arr, rowLength, itemSize, sel)
{
    //arr is an Array object
    //Row Length is how many items are displayed in a row.
    //itemSize is how large an item is.
    //sel is the currently selected item, and is not necissarry for basic function.

    for (var i = 0; i < arr.length; i++)
    {
        //First, calculate the XY position.
        var x = 0;
        var y = 0;
        x = i%rowLength;//This basically loops the number back to 0 when it hits rowLength. (It's the remainder operator)
        y = Mathf.Floor(i/rowLength);//This means that every time i hits rowLength, y will be one greater. We Mathf.Floor it to make sure any decimal pointi s truncated -- Thereby making it snap, and not smoothly go down.
        var rect = new Rect(x*itemSize.x, y*itemSize.y, itemSize.x, itemSize.y);
        //From here on, you can do whatever you want, but I will continue programming just ot give an example

        if (sel == i)//We ae about to draw the selected one.
        {
            //DoSomethingSpecialWhichIAmNotCreativeEnoughToDoMySelf
        }
        else
        {
            sel = (GUI.Button(rect,arr*)?i:sel);//This is an interesting way of do it. It is equivelant to:*
 _/*_
 _if (GUI.Button(rect,arr*) == true)*_
 _*{*_
 _*sel = i;*_
 _*}*_
 _*else*_
 _*{*_
 _*sel = sel;*_
 _*}*_
 <em>_*/_</em>
 _*}*_
 _*}*_
 _*return sel;*_
_*}*_
_*```*_