Unity: Scaling the GUI based on the screen resolution
Posted by Dimitri | May 12th, 2011 | Filed under Featured, Programming
As hinted by other posts, here, you will find how to properly scale the GUI elements based on screen resolution. As one may have noticed, Unity doesn’t scale the GUI elements based on the screen resolution, requiring a script to do the job, which is explained in this post. I will assume that the reader already knows how to create and render GUI elements in Unity using the MonoBehaviour’s OnGUI() function and GUISkin objects.
The best way to explain how to properly scale a GUI element is through an example. That said, for this post, let’s assume that we wanted a yellow rectangle to be rendered at the top left and bottom right corners of the screen, like this:
The first thing to have in mind when creating a GUI for multiple resolutions is that there are no absolute pixel values, only relative ones. This means that all GUI elements must be positioned at a certain distance from one (or more) screen edge(s). Since it’s not possible to create relative positions in a graphical editing software, there is the need to define a target distance from the screen edges when it has a specific resolution . In this example, we will define that the two GUI elements must have a distance of 20 pixels from the screen’s edges when the resolution is 1920×1080. This could have been any other distance for any other resolution, it’s just a place to start.
Finally, here’s the code that properly scales and positions the GUI based on the screen resolution:
using UnityEngine; using System.Collections; public class GUIScaleExample : MonoBehaviour { //a GUISkin object to draw the GUI image public GUISkin guiSkin; //the GUI scale ratio private float guiRatio; //the screen width private float sWidth; //create a scale Vector3 with the above ratio private Vector3 GUIsF; //At this script initialization void Awake() { //get the screen's width sWidth = Screen.width; //calculate the scale ratio guiRatio = sWidth/1920; //create a scale Vector3 with the above ratio GUIsF = new Vector3(guiRatio,guiRatio,1); } //Draws GUI elements void OnGUI() { //scale and position the GUI element to draw it at the screen's top left corner GUI.matrix = Matrix4x4.TRS(new Vector3(GUIsF.x,GUIsF.y,0),Quaternion.identity,GUIsF); //draw GUI on the top left GUI.Label(new Rect(20,20,258,89),"",guiSkin.customStyles[0]); //scale and position the GUI element to draw it at the screen's bottom right corner GUI.matrix = Matrix4x4.TRS(new Vector3(Screen.width - 258*GUIsF.x,Screen.height - 89*GUIsF.y,0),Quaternion.identity,GUIsF); //draw GUI on the bottom right GUI.Label(new Rect(-20,-20,258,89),"",guiSkin.customStyles[0]); } }
Right at the beginning of the code, a GUISkin object is declared at line 7, to render the yellow square as a GUI element. The float guiRatio variable is where the ratio between the screen’s width and the expected width is stored (line 10). The variable sWidth at line 13 stores the screen’s width. The GUIsF vector will be created using the above ratio, and later on, it’s going to be used to properly scale the GUI elements (line 16).
Inside the Awake() method, all these declared variables are initialized. Line 24 is definitively the most important one. It divides the screen width by the assumed width (in our case 1920) and stores it at the guiRatio variable. After that, the GUIsF scale vector is created. Again, 1920 is just a value that was determined as an example, it could have been set as any other. Alternatively, the height of the screen could be divided by 1080 if that made more sense, as a result, all GUI elements would have been scaled according to the screen’s height.
Moving on to the OnGUI() method, a “GUI.matrix = Matrix4x4.TRS “ line can be seen before each of the draw calls. These lines scale and position the whole GUI system, changing it’s origin and scale by replacing it with a custom 4×4 matrix. The Matrix4x4.TRS creates a Transform, Rotate and Translate 4×4 matrix, taking a Vector3, a Quaternion and another Vector3 as parameters.
The first time the GUI’s 4×4 Matrix is redefined (line 33), the GUI system origin is placed at (GUIsF.x,GUIsF.y,0), without any rotation (Quaternion.identity) and the elements below this line are scaled by GUIsF – a Vector3 composed by(guiRatio,guiRatio,1). Then, the GUI element is drawn in line 35 using this new matrix as origin, placing it near the top left corner of the screen.
For anything that is placed near the bottom and/or right edges of the screen, there’s an additional calculation that needs to be done to correctly modify the GUI system matrix. This calculation takes in consideration the width and height of the screen, so as the GUI element’s width and height. Line 38 is a good example. The origin is first translated to the bottom right of the screen and then, it’s is brought upwards and left just enough to draw the rescaled GUI element. Right after this compensation, line 40 draws the element, -20 pixels away from the origin.
The code described above scales the GUI elements and their distances from the screen edge. Therefore, the distance from the screen’s edge will be only 20px when the screen is at 1920x1080px. Smaller resolutions will result in smaller distances. Below, some screenshots of this script in action:
Here’s a Unity project with everything explained above, both in JavaScript and C#:
Cool code tutorial, I often woundered about the best way to do this. How efficient is this for mobile devices, i.e. Android handsets with different size screen resolutions? Would the constant GUI.Matrix call with the new vector 3 being created every frame cause a possible slow down or bottleneck on mid range hardware?
I think that it will be a costly operation for mobile devices. Maybe it’s better to use the Matrix4x4 SetTRS method:
GUI.matrix.SetTRS(Vector3 vector, Quaternion q, Vector3 vector)
Instead of assigning a new 4×4 Matrix for each element.Hi, is there in the meantime a better solution to this? if not, can you give me more detailed informations about that, cause i want to make a game for android, and at the moment, it is rly battery consuming and i try to improve everywhere i can!
Hi, is there in the meantime a better solution to this? if not, can you give me more detailed informations about that, cause i want to make a game for android, and at the moment, it is rly battery consuming and i try to improve everywhere i can!
this is exactly what I needed! Thanks so much for sharing.
btw if users can change the resolution during the game you will need to put the Awake code into the OnGUI
Thanks again
Exactly what I’ve been trying to wrap my head around for a while now. Thanks a ton.
Thanks!
I couldn’t say I agree with the tutorial presented. Although I’m a newbie, I found that you can position any of the GUI you are attempting to use by positioning it with Screen.width/x, Screen.height/y and so on. I think this could make it less costly for mobile devices. Correct me if I’m wrong (newbie as I said).
Is there a way to get same scaling effect on game objects and GUI texts?