Search Unity

Coding a simple Finder-esque interface

Discussion in 'Immediate Mode GUI (IMGUI)' started by Jessy, Jan 10, 2008.

  1. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I am working on a "game" that uses a list of objects in one column and a list of actions you can perform with a selected object in another column. Select the "as Columns" view in the OS X Finder for an idea of what I'm talking about. I'm thinking that what I need to do is create a bunch of buttons that don't have a border, but as for nice, clean coding to make a (constantly changing) list of actions appear, I'm at a loss.
     

    Attached Files:

  2. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I then figured the Selection Grid might be useful, but I don't want one of the options to be selected all of the time! At first, I want NONE of them selected. As it is, the "Turn On"/"Turn Off" portion of this test script won't work. I REALLY don't want to make a thousand buttons using if statements. Does anybody have any suggestions whatsoever?

    Code (csharp):
    1. private var object : int;
    2. private var objects : String[];
    3. private var action : int;
    4. private var actions : String[];
    5. private var lighter : String[];
    6.  
    7. function Start () {
    8.     objects = ["Lighter", "Lightswitch", "Ladder", "Burnt out bulb", "Light Bulb Drawer", "Bulb in hand"];
    9.     actions = ["Turn on"];
    10. }
    11.  
    12. function OnGUI () {
    13.     object = GUI.SelectionGrid (Rect (0, Screen.height - 250, Screen.width / 4, 250), object, objects, 1);
    14.     action = GUI.SelectionGrid (Rect (Screen.width / 4, Screen.height - 250, Screen.width / 4, 250), action, actions, 1);
    15.     if (actions[action] == "Turn on") {
    16.         actions[action] == "Turn off";
    17.     }
    18. }
     
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Put this at the end of your Start function:

    Code (csharp):
    1.    object = -1;
    --Eric
     
  4. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    That does seem to do it. However, now I am getting yelled at:

    "GUI Error: You are pushing more GUIClips than you are popping. Make sure they are balanced (type:repaint)"

    "IndexOutOfRangeException: Array index is out of range."
     
  5. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    ...Oops. OK, use this instead:

    Code (csharp):
    1.    object = objects.Length+1;
    --Eric
     
  6. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    "NullReferenceException: Object reference not set to an instance of an object"

    This grid does not play nice!
     
  7. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Did you change that code? I tested it and it worked here.

    --Eric
     
  8. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Hah. I put your latest line of code at the beginning of the Start function instead of the end. It's very late where I am...

    :p

    Thanks a bundle, as always.
     
  9. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Alright, the next thing I want to do requires me (I think!) to be able to get the index of an element in an array. I don't know how to look up how to do that. I've read a bit about IndexOf, but that doesn't seem to do the trick. This can't be complicated ...I hope.

    To clarify, I am using a for loop, like

    Code (csharp):
    1. var actions = ["1", "2", "3"];
    2.  
    3. for (var action in actions) {
    4.      notRealCode = actions.indexOf(action);
    5. }
    I've decided to use the following type of thing instead for the time being, but I would still like to know if I can do it the previous way.

    Code (csharp):
    1. for (var action = 0; action < actions.Length; action++) {
    2.      notRealCode = actions[action];
    3. }
     
  10. lgoss007

    lgoss007

    Joined:
    Dec 6, 2006
    Posts:
    88
    The problem with the first is that actions is a string array and arrays don't have the method IndexOf. To use IndexOf, use an ArrayList or something else that implements IList...

    Code (csharp):
    1. var actions : ArrayList;
    2.  
    3. function Start()
    4. {
    5.   actions = new ArrayList();
    6.   actions.Add("One");
    7.   actions.Add("Two");
    8.   actions.Add("Three");
    9.   for (var action in actions) {
    10.     notRealCode = actions.IndexOf(action);
    11.   }
    12. }
    13.  
    notRealCode will be: 0, 1, 2 (index's always start at 0).
     
  11. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Thank you for that information.

    So, I'm trying to get this interface working using tedious if statements. I am hitting a snag because OnGUI keeps the boolean value of a button as the list of buttons is supposed to change. What I want to have happen here, is if the candle is lit, and you turn the lighter off, your list of objects is only the lightswitch and the ladder. But if you turn the lighter off before lighting the candle, your list of actions for the lighter goes back to "Turn on". As you can see, I tried using a coroutine to get this working, but all I get for that is an error:

    "No appropriate version of 'UnityEngine.MonoBehaviour.StartCoroutine' for the argument list '(callable() as System.Collections.IEnumerator)' was found."

    Don't forget to set the GUI Height to something other than zero. (perhaps 250)

    Code (csharp):
    1. var gUI_Height : int;  //the GUI's vertical dimension in pixels
    2.  
    3. private var objects = new Array();  // a list of all currently available objects
    4. private var object : int;  // the object that is selected
    5. // a list of all currently available actions for each object
    6. private var lighterActions = new Array();
    7.  
    8. function Start () {
    9.     objects = ["Lighter"];
    10.     object = objects.length + 1;  // make it so no object is selected to begin with
    11.     lighterActions = ["Turn on"];
    12. }
    13.  
    14. function OnGUI () {
    15.     var utObjects = objects.ToBuiltin(String);  // modify the actions list and still allow a GUI grid
    16.     // background boxes for the four GUI areas
    17.     var rectWidth = Mathf.Floor(Screen.width / 4);
    18.     GUI.Box (Rect (0, Screen.height - gUI_Height, rectWidth, gUI_Height), "");
    19.     GUI.Box (Rect (Mathf.Floor(Screen.width * .25), Screen.height - gUI_Height, rectWidth, gUI_Height), "");
    20.     GUI.Box (Rect (Mathf.Floor(Screen.width * .50), Screen.height - gUI_Height, rectWidth, gUI_Height), "");
    21.     GUI.Box (Rect (Mathf.Floor(Screen.width * .75), Screen.height - gUI_Height, rectWidth, gUI_Height), "");
    22.     object = GUI.SelectionGrid (Rect (0, Screen.height - gUI_Height, rectWidth, gUI_Height), object, utObjects, 1);
    23.     if (object <= utObjects.Length) {
    24.         if (objects[object] == "Lighter") {
    25.             var buttonHeight = gUI_Height / lighterActions.length;
    26.             for (var action = 0; action < lighterActions.length; action++) {
    27.                 var buttonTop = Screen.height - gUI_Height + (action * buttonHeight);
    28.                 if (GUI.Button(Rect (rectWidth, buttonTop, rectWidth, buttonHeight), lighterActions[action])) {
    29.                     if (lighterActions[action] == "Turn on") {
    30.                         lighterActions = ["Turn off", "Light candle"];
    31.                         StartCoroutine(wait);
    32.                     }
    33.                     if (lighterActions[action] == "Light candle") {
    34.                         objects.Push("Lightswitch", "Ladder");
    35.                         lighterActions = ["Turn off"];
    36.                     }
    37.                     if (lighterActions[action] == "Turn Off") {
    38.                         if (lighterActions.length == 1) {
    39.                             object = objects.length + 1;
    40.                             objects.Shift();
    41.                         }
    42.                         else {
    43.                             lighterActions = ["Turn on"];
    44.                         }
    45.                     }
    46.                 }
    47.             }
    48.         }
    49.     }
    50. }
    51.  
    52. function wait () {
    53.     yield;
    54. }
     
  12. freyr

    freyr

    Joined:
    Apr 7, 2005
    Posts:
    1,148
    You have to call StartCoroutine like this:
    Code (csharp):
    1. StartCoroutine( wait() );
    2.  
    Although it will not do what I think you expect it to do. StartCoroutine will start the wait coroutine and return immediately. Thus the yield inside wait() will have no effect on the flow of the OnGUI function.
     
  13. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    This page tells me that yield will wait one frame. I thought that was all that I needed, but I'm still confused about this whole GUI.Button mess.

    http://unity3d.com/support/documentation/ScriptReference/index.Coroutines_26_Yield.html

    Perhaps what you are telling me is that I can not delay the effect of button pressing, which I want to do, as the Button itself is changing within a frame. That would not be good.

    ...That doesn't sound worded right. I'm sorry. Here is what I am observing:

    I press a button with certain wording on it. I never see the wording that should appear afterwards, and the button seems to act as it it is pressed again with the new word. This only makes sense if the changes to the wording occur in the same frame as the button being depressed. As such, I am trying to force some kind of effect so that the new buttons are created, but are not immediately in a down state.
     
  14. freyr

    freyr

    Joined:
    Apr 7, 2005
    Posts:
    1,148
    Yes the yield will wait one frame. The problem is that the purpose of StartCoroutine is to start a coroutine in the background and then return immediately. The coroutine will be detatched from thereon.

    What you really want is to avoid modifying the action array while looping through it. Instead, you should create a temporary variable called newLighterActions and assign the new list to that variable and then copy it to lighterActions after the loop has exited:


    Code (csharp):
    1. if (objects[object] == "Lighter") {
    2.    var buttonHeight = gUI_Height / lighterActions.length;
    3.    var newLighterActions = lighterActions;
    4.    for (var action = 0; action < lighterActions.length; action++) {
    5.       var buttonTop = Screen.height - gUI_Height + (action * buttonHeight);
    6.       if (GUI.Button(Rect (rectWidth, buttonTop, rectWidth, buttonHeight), lighterActions[action])) {
    7.          if (lighterActions[action] == "Turn on") {
    8.             newLighterActions = ["Turn off", "Light candle"];
    9.             StartCoroutine(wait);
    10.          }
    11.          if (lighterActions[action] == "Light candle") {
    12.             objects.Push("Lightswitch", "Ladder");
    13.             newLighterActions = ["Turn off"];
    14.          }
    15.          if (lighterActions[action] == "Turn Off") {
    16.             if (lighterActions.length == 1) {
    17.                object = objects.length + 1;
    18.                objects.Shift();
    19.             }
    20.             else {
    21.                newLighterActions = ["Turn on"];
    22.             }
    23.          }
    24.       }
    25.    }
    26.    lighterActions =  newLighterActions;
    27. }
     
  15. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Holy cow, now I'm in business. Thank you so much!