Correctly position GUILayout controls

For the game we're working on, several NPCs deliver lines of text dialogue. We need to show a graphic with character portraits on either side and space for the dialogue text in the middle.

Since I don't know how big this graphic will be at the moment, i've been looking at using GUILayout for its auto-sizing features. However, it seems the only way to place a GUILayout control involves knowing its size; I need to centre it horizontally on screen and vertically near the bottom of the screen, but I cannot find a straightforward way of doing this. I'd also like to be able to wrap the text since it needs to stay within the middle area of the graphic.

I've been playing around with something like the following but it's not quite right. I've also seen people using `FlexibleSpace()` but I haven't had much luck there either. I'll be honest, despite all the good things i've read about it, i'm not finding the GUI system very intuitive compared to the way we position guiText/guiTextures. If someone could help me get my head around this i'd be extremely grateful.

var areaWidth : float;
var areaHeight : float;
var ScreenX : float;
var ScreenY : float; 

var dialogueBG : Texture2D; 

private var showDialogue : boolean = false; 

function Start(){
  ScreenX = ((Screen.width * 0.5) - (areaWidth * 0.5));
  ScreenY = ((Screen.height) - 200); 

}

function OnGUI(){ 
  if (showDialogue) {
    GUILayout.BeginArea (Rect (ScreenX, ScreenY, areaWidth, areaHeight), dialogueBG);  

    GUILayout.Label(myDialogue); 

    GUILayout.EndArea();
  }

} 

I'm trying to picture what you're describing, and it sounds like you've almost got it. It sounds like you can just wrap your label in a horizontal section, surrounded by flexible space; as long as the Label style centers the text (rather than left justifying) you should be all set:

function OnGUI()
{
  GUILayout.BeginArea(Rect (ScreenX, ScreenY, areaWidth, areaHeight), dialogueBG);  

  GUILayout.BeginHorizontal();

  GUILayout.FlexibleSpace();

  GUILayout.Label(myDialogue); 

  GUILayout.FlexibleSpace();

  GUILayout.EndHoriztontal();

  GUILayout.EndArea();
}

However, if the only reason you're using GUILayout is because of the unknown image size, you could just refer to dialogueBG.width and dialogueBG.height so your code will automatically adjust to whatever the size is.

So I discovered immediately that GUIAreas cannot be nested. After much head scratching and reading the docs over and over, I realised I could override the width of my label. So now i'm using the following code. Although my previous code was centred after I tweaked the GUISkin, it shuffled over when I specified the width, so Molix's code above is need to pad it out on either side (as far as I understand it).

[EDIT] Re. Molix's last comment, there are other options which i'll put here too.

1) Previous approach using GUILayout and explicitly setting Label's width:

function OnGUI() { 
    GUI.skin = customSkin;

    if (showDialogue) {

        GUILayout.BeginArea(Rect (ScreenX, ScreenY, areaWidth, areaHeight), dialogueBG);  
            GUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            GUILayout.Label(myDialogue, GUILayout.Width(200)); 
            GUILayout.FlexibleSpace();
            GUILayout.EndHorizontal();
        GUILayout.EndArea();

    }
}

2) Same as above but using the padding settings of the GUISkin to define the boundaries for the text (remove GUILayout.Width). Setting a background style in the GUISkin will cause the texture to get squashed, so i'm setting it through code as above.

3) Using GUI.Label with the GUISkin settings used to set the background image and padding amount:

GUI.Label(Rect (ScreenX, ScreenY, areaWidth, areaHeight), myDialogue);

I'm going with option 3 since this will be easier for my artist to tweak the settings himself.

Thanks for the help Molix=)

Hi,

I just successfully positioned GUILayout.Vertical groups of elements containing variable textual content at absolute coords using the GUILayoutUtility.GetLastRect() method.

The trick was to have my OnGUI() loop just using GUILayout.BeginVertical, ... to lay out my GUI elements on the first frame.

Then use

Rect gui_rect = GUILayoutUtility.GetLastRect();

to get the bounding rectangle of the Vertical group box. This gui_rect can then be stored in an instance variable and used the next time OnGUI gets called to enclose my vertical group in an area using:

if(gui_rect.width>0) GUILayout.BeginArea (
    new Rect(desired_position.x, desired_position.y,
    gui_rect.width,gui_rect.height),areastyle);
    GUILayout.BeginVertical();
    ...
    GUILayout.EndVertical();
if(gui_rect.width>0) GUILayout.EndArea ();

And from now on the GUI elements will be layed out at the desired_position coords with the appropriate dimensions.

...

OK So there is one last thing.. There seems to actually be a bug/feature of GUILayout that means that the GUILayoutUtility.GetLastRect() method returns height=width=1 on the first frame. So I added this correction(/hack) to fix it so the appropriate dims are captured...

if(gui_rect.width>1 && gui_rect.height>1)
    gui_rect = new Vector2(gui_rect.width,gui_rect.height);
else
    gui_rect = new Rect(0,0,0,0);

Here's the complete code for my actual method for better illustration (please note that this code contains some user-defined classes and bits that you don't need to know about - sorry I'm lazy):

private Rect buildOverlay(Overlay o) {
    GUIStyle areastyle = new GUIStyle(style);
    areastyle.normal.background = null;
    if(o.dims[0]>=0 && o.dims[1]>=0) GUILayout.BeginArea (new Rect ((int)o.pos[0],(int)o.pos[1],(int)o.dims[0],(int)o.dims[1]),areastyle);
        GUILayout.BeginVertical();
            // Top
            GUILayout.BeginHorizontal();
                GUILayout.Box("",tl);
                GUILayout.Box("",tp);
                GUILayout.Box("",tr);
            GUILayout.EndHorizontal();
            // Middle
            GUILayout.BeginHorizontal();
                GUILayout.Box("",lt);
                GUILayout.Box(o.txt, style);
                GUILayout.Box("",rt);
            GUILayout.EndHorizontal();
            if(o.close_btn_txt!=null) {
                GUILayout.BeginHorizontal();
                    GUILayout.Box("",lt);
                    GUIStyle button_style = new GUIStyle(style);
                    button_style.alignment = TextAnchor.MiddleRight;
                    if(GUILayout.Button(o.close_btn_txt, button_style)) o.IsDying = true;
                    GUILayout.Box("",rt);
                GUILayout.EndHorizontal();
            }
            // Bottom
            GUILayout.BeginHorizontal();
                GUILayout.Box("",bl);
                GUILayout.Box("",bm);
                GUILayout.Box("",br);
            GUILayout.EndHorizontal();
        GUILayout.EndVertical();
            if(o.dims[0]>=0 && o.dims[1]>=0) {
                GUILayout.EndArea ();
            }
            else {
                Rect gui_rect = GUILayoutUtility.GetLastRect();
                if(gui_rect.width>1 && gui_rect.height>1) o.dims = new Vector2(gui_rect.width,gui_rect.height);
            }
    return new Rect(o.pos[0],o.pos[1],o.dims[0],o.dims[1]);
}