Scaling For Devices: Part 1 - The GUI Layer

Posted by Mark Alexander on 5 September 2014

This article is part one of a two part series in which we will explore the different ways to scale your game to fit different device screens. Scaling your game can be a headache for many users and hopefully through these articles we can help to make it a simpler task and provide you with a framework within which to get the correct results every time.

To get things started, we are going to discuss the GUI layer. This is an ideal starting point for discussion as it is not influenced by views nor (directly) by the application surface, so the calculations are easier and it gives a good introduction to the techniques you'll need to later scale the game itself to fit any display dimensions.

Note that if you are unsure of what the GUI layer is, you should check out the manual, and have a read at the following tech blogs before continuing: "New Draw GUI Event" and "Changes To The GUI layer And The New Application Surface"

Aspect Ratio

All our scaling will be based on the aspect ratio of the game and of the display. On windows, this is normally one and the same thing, as the game window will be created to be the size of the first room in your game (or the view port of the first room in your game). Please note that for this article we are assuming that the rooms (or view ports) in your game are always the same size, and in general you should always try to design your game around a fixed width and height as changing window sizes does not give a pleasant user experience and is more complex to code for.

So, what is the aspect ratio? Well, it's simply the value that you get from dividing the width of an area by the height of the area. For example, a typical monitor size would be 1280x800, which gives an aspect ratio of 1280/720:1 which is 1.77:1. This is the fairly common 16:9 ratio. Now, you could just make your game window that size, since it's a common aspect ratio, but common does not mean unique. There are a great many aspect ratios out there as the following image shows:

Image courtesy of Wikipedia

And to think... You have to support most of these (plus a few more besides)! There are various solutions to this problem, however, which we will outline below, but the important thing to know is that once we have the aspect ratio of our display, we can use it to modify the "base" values for our game's width and height to maintain the correct game aspect ratio and ensure correct scaling.

Keep Aspect Ratio

If you open up the Global Game Settings for your game, on each of the target platforms you have the option to Keep Aspect Ratio under the "Graphics" tab. This is the simplest way to scale your game to fit any display size, and all it does is add black bars to the outside of the game view, so that the game is always shown at the correct aspect ratio no matter what.

As you can see, the visible area of the room is exactly the same in both cases, but the image has been "padded" on the sides to fill the available window or display space.

If you choose to do nothing to the GUI layer, then this will also be created at the room (or view port) resolution and size, and will be positioned at the (0,0) position of the application surface - in other words the top left corner of the area where the game is being drawn. We can then use fixed values to set the GUI layer size using display_set_gui_size(w, h). This function will set the width and height for the GUI layer, and it will also set the GUI to be drawn within the area of the application surface. So, if you set the gui layer to 640x480, and your game room is 1024x768, the 640 pixels along the width (and the 480 along the height) will be stretched to fit the 1024x768 area.

This means that you can now draw your GUI elements like score, HUD, text, etc, and be sure that they will always appear the same. You can use the specific GUI functions display_get_gui_width/height() to position things within the gui layer too, so something like this would be used to position four elements at each corner of the screen:

draw_sprite(sprite0, 0, 64, 64);
draw_sprite(sprite0, 0, display_get_gui_width() - 64, 64);
draw_sprite(sprite0, 0, display_get_gui_width() - 64, display_get_gui_height() - 64);
draw_sprite(sprite0, 0, 64, display_get_gui_height() - 64);

Even if you don't resize the GUI layer in any way, you can always use these functions to position things relative to the current size.

You can also draw "outside" of the defined GUI area, with a negative value or a value outside of the width and height. Although out of the defined bounds of the GUI layer, the images will still be drawn, and when you have aspect ratio correction on and you draw outside the GUI area, you will see that you are drawing over the black bars that GameMaker: Studio has added. So, you can also use the GUI layer to create a better visual appearance for you game by masking these bars using sprites or backgrounds.

Full Scale

But what happens when you have your game set to Full Scale in the Global Game Settings? Well, the GUI layer will react the same way as before, being set to the default width and height values of the room or view port size, only now anything drawn outside of the GUI area will not be visible. The problem here is that the GUI may be stretched giving the wrong aspect ratio. For example, a GUI layer set to 640x480 on a 1270x720 will make everything stretch along the width as it scales to fit.

The green boxes above, should really be square but have been stretched with the GUI layer to fit the display. To get around this, we can set the GUI size dynamically based on the aspect ratio of the device screen, such that either the height or width will be a fixed value, and the opposite value will be variable.

To do this we need to decide which value (width or height) we want to be fixed, and which values we want to be variable. Which ones you choose will depend greatly on the game you are making, and how you want to position the GUI elements, but a general rule of thumb is that for landscape you want a fixed height/variable width and for portrait you want a fixed width and variable height, but in reality you can just use whatever works best for your game.

Once you have decided on the size of your GUI layer, you will need to create a script with something like the following:

var base_w = 640;
var base_h = 480;
var aspect = display_get_width() / display_get_height();
if (aspect > 1)
    display_set_gui_size(base_h * aspect, base_h);
    display_set_gui_size(base_w, base_w / aspect);

In the code example, our base size is 640x480 and the script will adapt depending on whether the device is in landscape or portrait mode. To get an idea of what this does, lets imagine that the device we test on is 1152x768. The device aspect ratio is 3:2, but our GUI base aspect ratio is 4:3, so stretching will occur unless we do something. We want to maintain the GUI height of 480, so we multiply it by the display aspect ratio to get the corrected width, which is (3/2) * 480, or 720. So our GUI layer is now 720x480.

In this way we can scale our GUI proportionally to fit any screen size, and be sure that our images and text are always being drawn at the correct position. It is important that you understand this method as it will be used in the second part of this tutorial to scale the game to fit the device screen too.

Maximising The GUI

The GUI layer is a lot more versatile than you may think thanks to the function display_set_gui_maximise(). Calling this function will (if you provide no arguments) set the GUI layer to be 1:1 with the display, and move the (0,0) position to the top left corner of the display too. This means that if you have aspect ratio correction on, you can also use this function to draw outside the of the game window over the black bars, or to create dynamic UI elements that are positioned over the full screen rather than just the application surface.

However, you can't set the GUI size directly anymore using the display_set_gui_size() function, as it will reset the GUI layer origin to the top left corner of the application surface again. This is where the xscale and yscale arguments for the maximise function can be used, as we can scale the GUI layer so that the visible area still has the required resolution and aspect ratio, enabling us to dynamically draw elements over the whole of the display.

To do this, you would have code similar to the following (which in itself is similar to that which we used for setting the GUI layer size, above):

var base_w = 640;
var base_h = 480;
var aspect = display_get_width() / display_get_height();
if (aspect > 1)
    ww = base_h * aspect;
    hh = base_h;
    display_set_gui_maximise((display_get_width() / ww), (display_get_height() / hh), 0, 0);
} else { //portrait ww = base_w; hh = base_w / aspect; display_set_gui_maximise((display_get_width() / ww), (display_get_height() / hh), 0, 0);
// An example of how you would then draw this to the GUI layer... 
draw_sprite(sprite0, 0, 64, 64);
draw_sprite(sprite0, 0, ww - 64, 64);
draw_sprite(sprite0, 0, ww - 64, hh - 64);
draw_sprite(sprite0, 0, 64, hh - 64);

The above will draw four sprites to the corners of the display like those in the image below:

It is worth noting that the maximise function also has x and y offset values. This means that you can move the GUI layer around and display different parts in the visible screen area. Normally, for example, to have text and sprites move around the screen, you would need to have separate objects and then control when and where they appear in code, but with the GUI maximise function, you can simply draw them all to the GUI layer and then move that around using the offset values.

display_set_gui_maximise(xscale, yscale, 0, 0);
//draw something
display_set_gui_maximise(xscale, yscale, -500, 0);
//draw something
display_set_gui_maximise(xscale, yscale, 0, 180);
//draw something

The above "pseudo code" example shows how you can achieve this, and you'll also find that it is much faster to draw GUI elements like this as it is the view matrix that is changing position and not a bunch of objects with a load of code.


Hopefully the mysteries of the GUI layer have been revealed and you can now create your own scripts or code blocks to deal with scaling it correctly on devices. The important thing to take away from this article is that the GUI layer is versatile and can be resized, scaled, and positioned to adapt to any device size with only a few lines of code, and that as long as you base your calculations around the device screen aspect ratio you can't go far wrong.

In Part 2 of this article, we'll look at how to apply what you have learned here to the game itself, and show different ways that you can scale the playable area to fit on different devices.





Back to Top