The GUI layer is a powerful beast, and you can do all kinds of cool things with it, but it's also one of the most misunderstood things in GameMaker. With the recent changes to it in the 2.1.5 update, I thought it would be a good idea to go back over the basics, and explain some of the more advanced features it has....
Before we start, lets chat about the GUI layer, the events and what they let you do. The GUI layer was first added back in days of GM8.1 - a long time ago now - and it was added because developers were having to counteract scrolling and zooming in their views in order to display the on screen UI (things like scores, items, messages etc..). This was pretty nasty and depending on the game could make putting a sprite at a fixed point in space at a specific size, a real headache for developers.
With all this in mind, we introduced the GUI layer so that you could draw at a fixed size and location without having to worry about what your game views were doing. You could now scroll, zoom, and even rotate, to your hearts content, and still print text with a single line to the screen at the size you wanted. The GUI layer was also at a different resolution from the main game, as we realised that developers may want to have a hires UI, but a lowres game. If you think about some RPG style games, they want the game art to be in a certain style and resolution - possibly for a retro feel - but still have a nice hires UI for items, mana, spells, scores and all the rest of it. So we allowed you to specify what resolution you wanted.
So, now you know the reasoning behind it, let's discuss some GUI layer basics. As of version 2.1.5, the GUI layer sizes itself to be the same as the application surface, and this is mostly good news for everyone. This means when you create a game - no matter what resolution you pick; 320x200, 1920x1080, 64x64 or whatever - your GUI will match that pixel size, There are a few exceptions to this, which I'll discuss later, but for now let's consider these 2 images which we'll be using as sprites in our test project:
The image on the left, we'll place in a room that is 320x200 in size, and the image on the right we'll draw in the GUI layer using the Draw Gui event. If we then run this, we'll get the image below:
Now, this shows exactly what we'd expect. The GUI layer has it's "pixel" size set to the same as the
application_surface, which itself is set to the size of the view port (which in this case, happens to be the size of the room). If we scale this window, then things start to change... This is because the initial GUI layout allows you to adapt your UI to a changing amount of screen/window space available. This means in that an RPG (for example) you can have that hires display to show more of the game, moving the UI around to free up more screen space. This is actually quite desirable in lots of games, particularly on a PC, as the user usually has a lot of space to play with, and this lets them see more of the game, and less of the UI.
As you can see from this image, I've doubled the size of the window by stretching it up at the corner (this requires enabling Allow window resize in the Graphics option of the Windows Game Options), and the UI has stayed in the top left of the window. This means that I'll have to move UI elements around to better fit. You do this (usually) by binding elements to an edge of the screen, so as the window changes size, you draw from the right-hand edge using an offset value like the width of your sprite, or from the bottom edge with an offset the height of your sprite. This lets you free up space in the middle so the user can see more of the game, and less UI is getting in the way.
NOTE: The manual has a list of GUI specific functions that can help when offsetting images on the GUI layer.
So what if you don't want that, and just want to layout the UI on the GUI layer so that it always appears in the same place, and the same size? Well this is pretty easy too.
display_set_gui_size lets you do just that:
Simply calling this function with the size you want to set your GUI to will then tell GameMaker to maintain this size even when the window (or display) changes size, as illustrated in the following image:
You'll notice 2 things here: First the size and aspect ratio are correct, and second, it doesn't match pixel-perfectly. There are some (very slight) offset pixels, particularly noticeable around the red square, where a little green from the background isn't quite matching up. This shouldn't be a huge shock to you, as both displays are setup using entirely different processes and matrices, and the maths just comes out with very small fractional differences. For an on screen display, however, this isn't a big issue, but it's worth remembering - especially if you're trying to do an effect to mask the game using the GUI layer.
For most people, this is all you'll need - set the GUI display size and then forget about it. But there is more power here if you want to use it. Lets remove the GUI size function and increase the VIEW PORT size. To do this, we add some views to our room like this:
When we now run the project, we get the image below...
Now, just like when we resized the game window, the GUI size now no longer matches the game scale. As mentioned previously, this is because the GUI is set to the size of the
application_surface at the start or the game - or rather, what the app surface should start at, which in this case is x3 the size of the room. The reason the app surface isn't 320x200 anymore, but the size of the view port (960x600) is that you could have multiple view ports active here, meaning we need the resolution to display each view correctly. Also, each view port could be a different size/scale, but as the window size sets the actual number of pixels we have, the
application_surface is set to the overall size of the display. This size is based on the bounding box of ALL active views ports in the first room.
The simplest way to get things sized correctly is to make a first room that is the size you want, then switch to another room to actually play the game. I tend to have a room first to initialise global variables and set things up anyway, so having it also specify the game window size isn't a huge issue.
Again, if you want to size your GUI to something else, you can simply use the
display_set_gui_size specified above, and you should be good to go.
In this demo image, We have a 320x200 room on a 640x400 view, and I've manually set the GUI size to 320x200. This means no matter how much I scale the window. the GUI will stay the same size. The image below shows a much wider window, and the GUI and room are matching nicely.
This brings up another thing to be aware of... The GUI layer "origin" (0,0) always starts at the top left of the game screen, where the
application_surface is drawn to. This is simply so you can position things like scores easily in relation to what's being drawn for your actual game, but that's not the whole story, Unlike the game - which renders into the
application_surface and is then positioned inside the window, either filling or using aspect ratio correction - the GUI layer can render to the WHOLE of the window space. So if you have a game at 320x200 and don't setup any GUI size and then resize the window, the GUI size will change to reflect this extra space. For example, if you have a 320x200 game then make the window 640x200, the GUI size will be 640x200 and not 320x200. This is shown below:
So why do we do this? Well, if you're doing a 4:3 game (for example), then you may well be running on a wide screen monitor or device, and as such might want to put some art/banners/backgrounds down the side of your game so it's not just black bars showing.
If you need the GUI size to STAY the same size each time, then you can set the GUI size as using the function we talked about above, and then it'll be that size each time you run the game or change room - even though you could still draw using a negative number to the space at the side. The image below shows the GUI size staying the same, even though the window has been resized, but we're still able to draw the ball outside using negative coordinates:
display_set_gui_size( 320,200 ); draw_sprite( sBall,0, -64,100 );
So, how can we get the actual window size, but keep the GUI locked to the 320x200 that we want? Well, you can get the distance offset for the left and right sides by getting the
application_surface location, and subtracting that from the window size, or you can work out the scale difference and use the
display set gui maximize function. This is an incredibly powerful function that lets us basically control the matrix being used to render the GUI layer. You can even specify the scale and offset dynamically, changing it throughout the GUI rendering process.
var app = application_get_position(); var w = (app-app)/320; var h = (app-app)/200; display_set_gui_maximize(w,h,app,app);
Using the code above means your GUI will stay the correct size and return the actual width of the display in the scale of pixels your GUI layer is using. From here you can then draw things to the left and right easily. Bear in mind that (0,0) is the top/left of the game space, which means (320 - GUIsize) / 2 is the offset for the right and left, with half being negative and the other half being 320 + n.
Lastly, the GUI layer can also be told to use ALL the available space, and go into "native" pixel mode. When you do this, pixels on the GUI layer are no longer scaled but will be 1:1 with the native pixel size of your monitor (or desktop resolution if you're in windowed mode). This means if your game is 320x200, and you're on a 1920x1080 monitor, your GUI size would be 1920x1080 and your pixels would be the native monitor pixel size. This gives you total control over the GUI as it means you can draw small text/icons over a lowres game if you want, and it also means you can dynamically resize based on the window size, allowing the player to see as much as possible without having to scale graphics.
The image above shows what happens after we call
display set gui maximise without passing in any parameters (like this).
This sets the origin to (0,0) in the window/display, and gives you the whole space to play with. You can also pass in
1, 1, app, app as arguments if you want to keep the 1:1 pixel size, but keep the origin at the application surface so that drawing is always based from the app surface location. You'd need to do this at the start of the Draw GUI event, as this location could change each frame as a window resizes.
display set gui maximise works in real time. This means that you can set it, draw something then set it again, then draw something, and again and again. This allows you to actually change the origin dynamically to (for example) scroll things on and off the screen if you want, or use the scale and origin to wobble the whole UI when you get hit by a bullet. It's incredibly powerful but does take some getting used to to get the most from it.
So... there you go. For the average user, I'd recommend simply using
display_set_gui_size to be whatever you're expecting the GUI to be, and it'll stick to that, and you'll never have to care again. But if you want an adaptive display, or if you want to do some cool effects, then it's worth remembering the rest of the GUI commands and how you can use them.