The Basics Of Scaling - The GUI Layer


The Basics Of Scaling - The GUI Layer

This article is part one of a series in which we will explore the different ways to scale your game to fit screens of different resolutions. 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 start with this article will be discussing 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 see the section on the Draw Event in the manual, and maybe have a read at the tech blog GUI Layer Secrets before continuing.


ASPECT RATIO

All our scaling of the game window will be based on the aspect ratio of the game and of the display or game window. Now, on desktop platforms, 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 viewport of the first room in your game) - however on mobile or HTML5 targets this can vary greatly between devices and browsers which we need to deal with.

NOTE: For this article we are assuming that the rooms (or viewports) 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 1920x1080, which gives an aspect ratio of 1920/1080:1 which is 1.77:1. This is a fairly common ration known as 16:9. Now, you could just make your game window that size, since it's a common aspect ratio, but commonly does not mean unique. There are a great many aspect ratios out there as the following image shows:

Aspect Ratios

And to think... You have to support most of these, plus a few more besides! But don't dismay, as there are various solutions to this problem which we will outline below. The important thing to know is that once we have the aspect ratio of our display, we can then 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 Game Options for your game, on each of the target platforms you will 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.

Keep Aspect Ratio Examples

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 or top and bottom 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 viewport) 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, within any of the black bars. 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 - for example - and your game room/viewport is 1024x768, then the 640 pixels along the width (and the 480 along with the height) will be stretched to fit the 1024x768 area.

NOTE: If you are unsure of what we mean when we talk about the application surface then please see the manual for more information.

Setting the GUI layer size in this way means that you can now draw your GUI elements like score, HUD, menu text, etc... and be sure that they will always appear the same, regardless of any changes to the game camera or game room. You can also use the GUI layer specific functions display_get_gui_width() and display_get_gui_height() to help position things within the GUI layer too, so something like this would be used to position four elements at each corner of the screen:

var _gw = display_get_gui_width();
var _gh = display_get_gui_height();
draw_sprite(sprite0, 0, 64, 64);
draw_sprite(sprite0, 0, _gw - 64, 64);
draw_sprite(sprite0, 0, _gw - 64, _gh- 64);
draw_sprite(sprite0, 0, 64, _gh - 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 bounds of the width and height. Anything set to draw outside of those values or at less than (0, 0) 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 2 has added. So, you can also use the GUI layer to create a better visual appearance for your game by masking these bars using sprites or backgrounds.


FULL SCALE

That covers keeping aspect ratio, but what happens when you have your game set to Full Scale in the Game Options? Well, the GUI layer will react the same way as before, is set to the default width and height values of the room or viewport size, only now anything drawn outside of the GUI area will not be visible. The additional 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:

Full Scale Stretched GUI Layer

The green boxes in the image 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 or display, 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 with 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)
    {
    //landscape
    display_set_gui_size(base_h * aspect, base_h);
    }
else
    {
    //portrait
    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, let's imagine that the device we test on has an 1152x768 display. 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 a ñater tech blog 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, but without having to use negative values or values greater than the set GUI width and height.

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)
    {
    //landscape
    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:

Maximised Gui Layer

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</pre>

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 GUI view matrix that is changing position and not a bunch of objects with a load of code.


SUMMARY

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 different devices and display sizes. 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 the next article, we'll look at how to apply what you have learned here to the game view itself, and show different ways that you can scale the playable area to fit on different devices and displays.