Search Unity

[Solved] How to best create a resizeable GUI

Discussion in 'Immediate Mode GUI (IMGUI)' started by Story Specialist, Feb 15, 2010.

  1. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Hey all,

    I'm a Unity beginner (and forum newcomer) and am currently using Unity for my graduation game design project. I absolutely love it! I don't consider myself a programmer at all, and still I feel I can do everything I want to do with this wonderful engine.

    Apart from trying to design a good dialogue system, I am trying to think of the best way to make a relative GUI to support this. (Basically, I want the GUI to have: an avatar window to see who's talking, a speech window, and a window that holds either narrator text or dialogue buttons). A GUI that changes appropriate with changing of resolutions and of which I can enable/disable certain parts.

    Now, I've read a bunch of references, topics and tutorials and there seem to be a lot of ways to accomplish this. I would like to know which way I could use best (as a beginner).

    Currently, this is part of my experiment to make a dialogue GUI.
    Code (csharp):
    1. var message : TextAsset;
    2. var Soundscape : GameObject;
    3. var guicharacters = true;
    4. var characterTalking = "Sophia";
    5. var characterAvatar : Texture2D;
    6. var characterSpeech : TextAsset;
    7.  
    8. function Awake () {
    9.  
    10. //check if soundscape = on
    11. if (MainScreen_GUI.musicToggle == true) {
    12.     print ("Woohoo!");
    13.     Soundscape.audio.Play();
    14.     }
    15. }
    16.  
    17.  
    18. function OnGUI () {
    19.  
    20. //Message box
    21. GUI.Box (Rect (Screen.width * 0.02, Screen.height * 0.8, Screen.width * 0.96, 120), "Messages");
    22. GUI.Label (Rect (Screen.width * 0.05, Screen.height * 0.83, Screen.width * 0.90, 110), message.text);
    23.  
    24. //character box
    25.  
    26. //if statement here
    27. if (guicharacters == true) {
    28.     //avatar box
    29.     GUI.Box (Rect (Screen.width * 0.02, Screen.height * 0.02, Screen.width * 0.13, Screen.height * 0.21), characterTalking);
    30.     GUI.Label (Rect (Screen.width * 0.03, Screen.height * 0.04, Screen.width * 0.145, Screen.height * 0.185), characterAvatar);
    31.     //speech box
    32.     GUI.Box (Rect (Screen.width * 0.16, Screen.height * 0.02, Screen.width * 0.82, Screen.height * 0.17), "Speech");
    33.     GUI.Label (Rect (Screen.width * 0.17, Screen.height * 0.05, Screen.width * 0.80, Screen.height * 0.17), characterSpeech.text);
    34. }
    35.  
    36. //gui toggle
    37. guicharacters = GUI.Toggle (Rect (240, 325, 100, 20), guicharacters, " GUI On/Off");
    38.  
    39. }
    As you can see, I use a lot of Screen.height, etc. But I've also seen Automatic GUILayouts, and GUI matrices. Would these techniques be more efficient to use? I find that using such simple math solutions as I do know, lacks the controls I have with the absolute/static GUI (in where you simple state the pixels in the Rect).

    I'm pretty sure there's a better way to do this, but I can't seem to get my head around it.

    Also, the toggle is just a test thing, to hide/show the GUI. Is this the best way, or am I better of using other methods?

    I'd really appreciate some insight into this :)! If there is any need for more information, I'll gladly provide it.

    -Veliremus
     
  2. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    I'm also new to Unity, but since no one replied to your question:

    Most easy to implement and hopefully easy on hardware too (since it's the build-in approach), seems to be the GUI.matrix approach. I use it like this:

    Code (csharp):
    1.  
    2. //example for resolution independent GUI scaling
    3. var resolution_ratio : float;
    4. var actual_height : float;
    5. var native_height : float = 800;
    6.  
    7. actual_height = Screen.height; //my tests show getting Screen.height is slow, do not include into functions called every frame (like OnGUI)
    8. resolution_ratio = actual_height / native_height;
    9.  
    10. function OnGUI () {
    11.  
    12. GUI.matrix = Matrix4x4.TRS (Vector3(0, 0, 0), Quaternion.identity, Vector3 (resolution_ratio, resolution_ratio, 1));
    13.  
    14. // now define your GUI.Labels, GUI.Buttons, etc. here
    15. }
    16.  
    GUI.matrix applies to all the internal rectangles used within GUI functions (including mouse overs, etc.).

    However if you want to use a non GUI call e.g. Input.mousePosition etc. the values are in the current resolution so you will have to apply the resolution ratio manually, like shown here: http://forum.unity3d.com/viewtopic.php?t=35588

    Further performance tips are here, but they are maybe a little bit advanced and they seem to not agree on some of the points: http://forum.unity3d.com/viewtopic.php?t=42422

    Hope this helps. If any of the more experience users has something to add to this, I would really appreciate it.

    P.S.: it's a shame how much confusion is on the forums about how to use the GUI and the GUI performance. In comparison with other engines and development languages I used it's as the rest of Unity: dead easy to use, all the necessary functionality build-in and reasonably well performing.

    Maybe the confusion comes from newbies answering newbie questions :)
     
  3. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Thank you for your reply :). I think the confusion, at least on my part, comes from the many options and different ways to make a scalable GUI. Plus I rarely touch any form of programming/scripting, so it can be hard for me to see which way is the most efficient.

    Okay, once I enter all this code, will I be able to define all my Rects in the normal way? (eg. 20, 20, 100, 100?)

    Also, the native_height is now set to 800. Why is that? Is it an example of a native height and should I determine my own native height first?
     
  4. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Okay, so I tried it, and it seems to work but on a high resolution a specific GUI.Box takes up less space in the screen than on a lower resolution. What I'm trying to accomplish is that it takes up the same space (so a box that takes up 50% of the screen should take up 50% of the screen on higher resolutions as well). I had sort of accomplished this by making things like this:

    Code (csharp):
    1. GUI.Box (Rect (Screen.width * 0.02, Screen.height * 0.02, Screen.width * 0.13, Screen.height * 0.21), characterTalking);
    But that didn't give me the same amount of control I had with absolute rectangles. Does this make sense? I've got the feeling that I'm being very vague.
     
  5. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    Hi there. It's pretty clear from you description what you are trying to achieve: elements have same size on the monitor, not matter what resolution set.

    The GUI.matrix takes care of this.

    I can imagine the issue is you don't update the actual_height with the Screen.height after you change resolutions. How do you test the multiple resolutions? If you just re-size the Game Window while the game is playing in Unity, it won't work (as the actual_height variable is filled only once at the beginning). If you want to test it in the Game View, change the code like this:

    Code (csharp):
    1. function OnGUI () {
    2. actual_height = Screen.height;
    3. resolution_ratio = actual_height / native_height;
    4. //now declare your GUI elements
    5. }
    This way the actual_height is updated every frame (as OnGUI is called every frame, but the code before OnGUI is not, it's called just at the beginning of the program). As mentioned before it's draining performance this way, that's why I called Screen.height only once at the beginning in my previous post and then only when changing resolutions (e.g. with Screen.SetResolution)

    Of course feel free to choose your own native resolution: it's the resolution your assets work best with (or the one you like to work with and determine the GUI element sizes with).

    Hope this helps.
     
  6. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Still doesn't seem to work right. Switching between 800x600 and 1024x768 goes well, I think. But if I switch to 1280x1024, the box takes up less space of the screen that it did with the other resolutions. Here's my code:

    Code (csharp):
    1. var instructions : TextAsset;
    2. var message = "...";
    3. static var musicToggle = true;
    4. static var soundscapeToggle = true;
    5. static var style : int;
    6. var resolution_ratio : float;
    7. var actual_height : float;
    8. var native_height : float = 768;
    9.  
    10. function Awake () {
    11. }
    12.  
    13. function OnGUI () {
    14.  
    15.     //set up scaling
    16.     actual_height = Screen.height;
    17.     resolution_ratio = actual_height / native_height;
    18.  
    19.     //set up scaling matrix
    20.     GUI.matrix = Matrix4x4.TRS (Vector3(0, 0, 0), Quaternion.identity, Vector3 (resolution_ratio, resolution_ratio, 1));
    21.    
    22.     //instructions
    23.     GUI.Box (Rect (100, 100, 600, 200), "Instructions");
    24.     GUI.Label (Rect (110, 120, 580, 290), instructions.text);
    25.  
    26.     //resolutions
    27.     GUI.Box (Rect (100, 305, 130, 150), "Resolutions");
    28.     if (GUI.Button (Rect (115, 325, 100, 20), "1024x768")) {
    29.         Screen.SetResolution (1024, 768, true);
    30.         native_height = 768;
    31.         message = "Resolution set!";
    32.     }
    33.  
    34.     if (GUI.Button (Rect (115, 350, 100, 20), "1280x1024")) {
    35.         Screen.SetResolution (1280, 1024, true);
    36.         message = "Resolution set!";
    37.         native_height = 1024;
    38.     }
    39.  
    40.     if (GUI.Button (Rect (115, 375, 100, 20), "Windowed")) {
    41.         Screen.SetResolution (800, 600, false);
    42.         message = "Resolution set!";
    43.         native_height = 600;
    44.     }
    45.    
    46.    
    47.     //Audio Toggles
    48.     GUI.Box (Rect (235, 305, 135, 150), "Audio Settings");
    49.     musicToggle = GUI.Toggle (Rect (240, 325, 100, 20), musicToggle, " Music On/Off");
    50.     soundscapeToggle = GUI.Toggle (Rect (240, 350, 150, 20), soundscapeToggle, " Soundscape On/Off");
    51.  
    52.     //Message Box
    53.     GUI.Label (Rect (100, 480, 200, 20), "Messages");
    54.     GUI.TextArea (Rect (100, 500, 200, 20), message);
    55.  
    56.     //writing styles
    57.     GUI.Box (Rect (375, 305, 250, 150), "Writing Styles");
    58.     if (GUI.Button (Rect (380, 325, 180, 20), "Narrator style (2nd person)")) {
    59.         message = "Narrator style set!";
    60.         style = 0;
    61.     }
    62.     if (GUI.Button (Rect (380, 350, 195, 20), "Inner Thoughts style (1st person)")) {
    63.         message = "Inner Thoughts style set!";
    64.         style = 1;
    65.     }
    66.     if (GUI.Button (Rect (380, 375, 210, 20), "Minimal Narrator style (2nd person)")) {
    67.         message = "Minimal Narrator style set!";
    68.         style = 2;
    69.     }
    70.  
    71.     //go button
    72.     GUI.Box (Rect (630, 305, 70, 150),"Done?");
    73.     if (GUI.Button (Rect (635, 325, 60, 50), "Go!")) {
    74.         message = "Launching!";
    75.         Application.LoadLevel (1);
    76.     }
    77.  
    78.     //print toggles
    79.     print ("Music: " + musicToggle + "Soundscape: " + soundscapeToggle);
    80.  
    81.  
    82. }
     
  7. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    Too bad I don't have time to look into this properly, but a few quick tips after looking at your code:
    - don't change the native size after you change the resolutions. That's the whole purpose of it.
    - another issue can come from different aspect ratio between the tested screen resolutions 1024x768 (aspect ratio: 4:3), 1280x1024 (aspect ratio 5:4) Try to test in windowed mode with the same aspect ratio (e.g.: 1024x768 | 800x600 | 640x480). I didn't test it yet but for different aspect ratios: one solution may be to add a resolution_ratio_x (actual width / native width) and resolution_ratio_y (actual height / native height) and use both in the GUI.matrix call.

    The GUI.Matrix works. Try to edit your code: on clicking the button change the resolution_ratio variable directly, without changing the resolution (e.g. one button: resolution_ratio *= 1.1, the other resolution_ratio /= 1.1). You will see the whole GUI scale up and down.

    Just a note in case some people read this who DON'T check Screen.height every frame: a resolution switch does not happen immediately; it will actually happen when the current frame is finished. So you have to check the Screen.height in the next frame after the Screen.SetResolution has been called.
     
  8. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Not setting the native reso over and over again certainly seems to have worked to some extend :).

    Okay, how do I do that aspect ratio thing then?

    Code (csharp):
    1. GUI.matrix = Matrix4x4.TRS (Vector3(0, 0, 0), Quaternion.identity, Vector3 (resolution_ratio, resolution_ratio, 1));
    would be (?):

    Code (csharp):
    1. GUI.matrix = Matrix4x4.TRS (Vector3(0, 0, 0), Quaternion.identity, Vector3 (resolution_ratio_x, resolution_ratio_y, 1));
    Or is that not right?
     
  9. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    It would seem the problem remains... Tried the new approach and while it does indeed improve the quality of the scaling on 5:4, the boxes still take up different amounts of screen space.

    Can you help me out?
     
  10. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    I run a few quick tests with the two separate resolution_ratio_x and y and it works correctly. The GUI elements occupy the same size on my monitor (I have a monitor with native res 1280x1024).

    note: I disabled the text asset to make it work on my side without the resource text file.

    Here is your updated code:
    Code (csharp):
    1.  
    2. var instructions : TextAsset;
    3. var message = "...";
    4. static var musicToggle = true;
    5. static var soundscapeToggle = true;
    6. static var style : int;
    7. var resolution_ratio_x : float;
    8. var resolution_ratio_y : float;
    9. var actual_height : float;
    10. var actual_width : float;
    11. var native_height : float = 768;
    12. var native_width : float = 1024;
    13.  
    14. function OnGUI () {
    15.  
    16.    //set up scaling
    17.    actual_height = Screen.height;
    18.    actual_width = Screen.width;
    19.     resolution_ratio_y = actual_height / native_height;
    20.     resolution_ratio_x = actual_width / native_width;
    21.    
    22.  
    23.    //set up scaling matrix
    24.    GUI.matrix = Matrix4x4.TRS (Vector3(0, 0, 0), Quaternion.identity, Vector3 (resolution_ratio_x, resolution_ratio_y, 1));
    25.    
    26.    //instructions
    27.    GUI.Box (Rect (100, 100, 600, 200), "Instructions");
    28.    GUI.Label (Rect (110, 120, 580, 290), "instructions");
    29.  
    30.    //resolutions
    31.    GUI.Box (Rect (100, 305, 130, 150), "Resolutions");
    32.    if (GUI.Button (Rect (115, 325, 100, 20), " 1024 x 768")) {
    33.       Screen.SetResolution (1024, 768, true);
    34.       //native_height = 768;
    35.       //resolution_ratio /= 1.1;
    36.       message = "resolution_ratio: " + resolution_ratio_x + " , " + resolution_ratio_y;
    37.     }
    38.  
    39.    if (GUI.Button (Rect (115, 350, 100, 20), " 1280 x 1024")) {
    40.       Screen.SetResolution (1280, 1024, true);
    41.       //resolution_ratio *= 1.1;
    42.       message = "resolution_ratio: " + resolution_ratio_x + " , " + resolution_ratio_y;
    43.       //native_height = 1024;
    44.    }
    45.  
    46.    if (GUI.Button (Rect (115, 375, 100, 20), "800 x 600")) {
    47.       Screen.SetResolution (800, 600, true);
    48.       message = "resolution_ratio: " + resolution_ratio_x + " , " + resolution_ratio_y;
    49.       //native_height = 600;
    50.    }
    51.    
    52.    
    53.    //Audio Toggles
    54.    GUI.Box (Rect (235, 305, 135, 150), "Audio Settings");
    55.    musicToggle = GUI.Toggle (Rect (240, 325, 100, 20), musicToggle, " Music On/Off");
    56.    soundscapeToggle = GUI.Toggle (Rect (240, 350, 150, 20), soundscapeToggle, " Soundscape On/Off");
    57.  
    58.    //Message Box
    59.    GUI.Label (Rect (100, 480, 200, 20), "Messages");
    60.    GUI.TextArea (Rect (100, 500, 200, 20), message);
    61.  
    62.    //writing styles
    63.    GUI.Box (Rect (375, 305, 250, 150), "Writing Styles");
    64.    if (GUI.Button (Rect (380, 325, 180, 20), "Narrator style (2nd person)")) {
    65.       message = "Narrator style set!";
    66.       style = 0;
    67.    }
    68.    if (GUI.Button (Rect (380, 350, 195, 20), "Inner Thoughts style (1st person)")) {
    69.       message = "Inner Thoughts style set!";
    70.       style = 1;
    71.    }
    72.    if (GUI.Button (Rect (380, 375, 210, 20), "Minimal Narrator style (2nd person)")) {
    73.       message = "Minimal Narrator style set!";
    74.       style = 2;
    75.    }
    76.  
    77.    //go button
    78.    GUI.Box (Rect (630, 305, 70, 150),"Done?");
    79.    if (GUI.Button (Rect (635, 325, 60, 50), "Go!")) {
    80.       message = "Launching!";
    81.       Application.LoadLevel (1);
    82.    }
    83.  
    84.    //print toggles
    85.   // print ("Music: " + musicToggle + "Soundscape: " + soundscapeToggle);
    86.  
    87.  
    88. }
     
  11. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Thanks, man :). It works brilliantly. Turns out I made a typo (defined the same var twice and switched them around the wrong way in the GUI.Matrix).

    Just need to test it on one more screen and then it's all good. Thank you very much :D.
     
  12. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    You are welcome, I'm glad it works for you too.
     
  13. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Yup, double checked on that other screen, so thank again.

    This would basically work for widescreen too, right?
     
  14. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    Basically yes, still proper testing is needed to see if the result is really what you want.
     
  15. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Revisiting this topic, after doing other things for my project first...

    I've noticed that the quality of the scaling is pretty low. I've made screenshots:



    As you can see, when the matrix is on, it is correctly scaled and what not, but pretty ugly (even though the native resolution in the script is the same, and the player, too, is set to that resolution). Kinda blurry, as well.

    As that normal, or am I doing something wrong?
     
  16. Andrej-Vojtas

    Andrej-Vojtas

    Joined:
    Jan 12, 2009
    Posts:
    67
    In the native resolution, you should get no GUI.matrix scaling, thus no blurring. Maybe you set the player to the desired resolution, but get something else in reality.

    If there is scaling going on, the blurring is normal. You can try if you get better results when using custom fonts, testing different font sizes and turning bilinear filtering on the font textures.
     
  17. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    When I change the native_resolution in the script, it doesn't have any effect; everything is still in the same place... That shouldn't happen, right?
     
  18. Story Specialist

    Story Specialist

    Joined:
    Feb 12, 2010
    Posts:
    36
    Hm... here's a weird thingy: I make a new scene in Unity, and then set native to 1280x1024 and that does work. Quite nicely scaled too...

    Seems like Unity is still using the old 1024x768 on my other scene...

    EDIT: Changing it in the Property Inspector does work. How come the script keeps the old value?
     
  19. joelmgallant

    joelmgallant

    Joined:
    Mar 9, 2009
    Posts:
    113
    If you have public variables defined in a script, the inspector values take precedence over the originals defined in the code.
    Changing it in the code will only affect new instances of the script - they end up being more like defaults.