- Home /
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();
}
}
Answer by Molix · May 03, 2010 at 02:03 AM
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.
Oh I feel so silly! I realise now that my alignment problems were because the control was always being positioned from the top left. I knew this, but I had been trying to fix it through code, not realising I should set it up correctly in the GUIStyle. I've added a GUISkin and set the Label's alignment to $$anonymous$$iddle Centre. It works, but the text is at the top of the dialogueBG image. Using the Y-offsets moves it down. Also, the text wraps at the edges of the control (ie, it runs over the character portraits on either side). Would I need to nest another area inside the first one to fix this? Thanks
If you make a custom style then you don't even need much of that code, you could just do GUI.Label( rect, myDialogue, customDialogStyle ). To adjust the text inside the dialog, use the GUIStyle.padding, e.g. give it a large value for padding.left and padding.right to account for the face images.
Answer by straydogstrut · May 03, 2010 at 03:21 PM
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=)
No problem! Don't forget to specify the custom style in the GUI.Label call in (3) (unless you're actually changing the default label style).
Yeah, i'm changing the default style for the moment, but i'll add a custom style later when we add more stuff to the GUI. Thank you again.
Answer by hungrytom · May 25, 2010 at 02:48 PM
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]);
}
Your answer
Follow this Question
Related Questions
How to put both text and texture inside a label 1 Answer
Unity 5.2 Text Rendering issue 0 Answers
How can i draw text to a Texture2D? 1 Answer
Bullet Points 1 Answer
Render a typed text to a texture: what is the best way ? 1 Answer