This blog post is a continuation from The Basics Of Scaling - The GUI Layer where we looked at different methods to scale the GUI layer so that it can be adapted to the different screen sizes and aspect ratios of any given device or display. This was a relatively simple thing to do since the GUI layer permits resizing and doesn't require views nor surfaces to work. But what about scaling the game itself? Making your game fill the whole display is easy, but making it fill the display and look correct (ie: no blurring or stretching) is not, so in this tech blog we will cover various different methods that you can choose from to scale the game properly.
Before continuing, it is worth noting that there is no "one size fits all" solution to this problem, and a lot will depend on how your game is created and what features it uses. However, we hope that the following information gives you at least the basics to be able to create your own scaling solutions without too much pain!
Before we get to the scaling part, it is worth noting that your game will be initialised to the size of the first room in your game if you are not using viewports and cameras, and if you are using them, then it will be scaled to the first viewport size, or - if you are using more than one view - the size of the bounding box of all views; So, if you have an initial game room that is 640x480 then all further rooms will be scaled to fit that area, and the same is true for views, with each room being squashed or stretched to fit the initial room's viewport settings.
The image below shows a game where the initial room was a different size to the required viewport:
This is important to note as 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.
Knowing this gives you a quick way to scale things to a different size, as you can simply create an empty first room that is 1290x720 - for example - and then all further rooms can be 256x144 and the game will be drawn scaled up automatically. The same for viewports and cameras. In your first room, you can set the viewport to a fixed size and all further rooms will be scaled to fit. However, this is far from optimal for a number of reasons, not least of which is that the application surface for each room will also be set to this larger size, which means you may be forcing the game to draw a larger surface than necessary. This method can also distort your graphics and is not really adaptable to different devices or displays. So let's move on and see what else we can do...
KEEP ASPECT RATIO
As with our article on the GUI layer, for the actual game we are going to start by exploring the Keep Aspect Ratio option in the "Graphics" section of Game Options for each platform. This adds black bars to "pad" the game and make it fit the display while maintaining the game's aspect ratio with respect to the device or display aspect ratio (if you are not familiar with the term "aspect ratio", please read the previous article on scaling).
When using this option you could simply go no further and leave GameMaker Studio 2 to do all the work for you, but this is not the most efficient option, since GameMaker cannot decide for you how things should be done and must go with a solution that will work for most people on most platforms, and - let's be honest - no one likes a modern game that has been padded in this way! So, what can we do here? Well, the main issue will be the size of the application surface (if you are not familiar with the application surface, please see the manual for more information).
Basically, you want to have the application surface at a 1:1 resolution with the display size, or have it scaled up to fit. Why up and not down? In general scaling up is faster for GPUs. Say - for example - your game is designed around 1024x768, and it's being played on a device with a resolution of 800x480. GameMaker Studio 2 will default to creating the application surface at the original size, which will then be scaled down for drawing to the display, which we don't want. We can get around this by using the following in the room creation code:
var base_w = 1024;
var base_h = 768;
var aspect = base_w / base_h ; // get the GAME aspect ratio
if (display_get_width() < display_get_height())
var ww = min(base_w, display_get_width());
var hh = ww / aspect;
var hh = min(base_h, display_get_height());
var ww = hh * aspect;
surface_resize(application_surface, ww, hh);
Now, with aspect ratio correction on in the Global Game Settings, our application surface will be set to the correct width and height up to a maximum of 1024x768, which is our base resolution. If the display is larger than this, then we let GameMaker scale it up automatically as we don't want to resize the application surface to be greater than it needs to be (drawing more pixels than necessary). Note that the application surface hasn't actually been rendered yet, but you can call the resize function in the room start or create events of an instance without issues, and in the first render frame it will be the size you set.
Ideally, you want your game to scale to use the full display dimensions on the user's device, as this makes the most of the available space and gives a much better playing experience for them. However, simply checking the Full Scale option in the Game Options is not enough since all it does is stretch the game to fit the screen, ignoring the aspect ratio of the device, which leads to stretched and ugly graphics, as well as possible pixel doubling if the application surface is created at a higher resolution than required.
In general, all solutions to this issue will rely on the use of viewports and cameras to create a cropped (or expanded) view of the room, and then you will resize the application surface to be the size of the viewport itself. Even if your game is a series of single rooms with no views active you will need to do this, as the only other solution would be to resize your rooms dynamically to fit the screen, which would also require dynamic object placement etc... It's not impossible, and indeed for some games, it may be an ideal solution, but in general, the best method is simply to switch on viewports and let them do the scaling for you.
So, we need to do two things to get this working correctly:
- Set up the viewport and camera to adapt to the display aspect ratio.
- Set the application surface to be the correct size required for our game, based on the viewport.
To do this you will need to get the aspect ratio of the base game size and the aspect ratio of the display. The base game size is the ideal height and width that you want your game to be shown at, but we are calling it the "base" size because the final width and height will actually vary depending on the display aspect ratio. Why is this? Because what we want to do is take one of the values for width and height and maintain it, while we crop or expand the other value to fit the screen.
For example, take our 1024x768 game on an 800x480 device. We will set the viewport height to be 768, and then multiply that by the aspect ratio of the display (800/480 = 1.66 = 5:3) so that our resulting width is actually (800/480) * 768 = 1280. That's an extra 128 pixels on either side of the base width, and so you will have to take this into consideration when designing the game. The opposite will also be true and if our example game was run on an SXGA resolution device (1280x1024) then we would have (1280/1024) * 768 = 960, so we are losing 32 pixels from either side.
TIP: To prevent too much extra view space being added to your game, you can set the base width and height to be less than the designed width and height. For example, our 1024x768 game could have a base width and height of 960x640. This means that the view could be cropped a little along the height, but expanded a little along the width and so the overall view area doesn't change too much, only it's proportions.
Let's have a look at some code for this:
var base_w = 1024;
var base_h = 768;
var max_w = display_get_width();
var max_h = display_get_height();
var aspect = display_get_width() / display_get_height();
if (max_w < max_h)
var VIEW_WIDTH = min(base_w, max_w);
var VIEW_HEIGHT = VIEW_WIDTH / aspect;
var VIEW_HEIGHT = min(base_h, max_h);
var VIEW_WIDTH = VIEW_HEIGHT * aspect;
camera_set_view_size(view_camera, floor(VIEW_WIDTH), floor(VIEW_HEIGHT))
view_wport = max_w;
view_hport = max_h;
surface_resize(application_surface, view_wport, view_hport);
If you've followed this and the previous tech blog fully, then you'll notice that this code is pretty much the same as we used when we kept the aspect ratio in the Game Options and when we set the GUI aspect ratio, only now we have expanded it a bit to set the views for the game room. The camera view size is set to maintain the aspect ratio and the viewport is set to the display size, and then finally the application surface is resized to get a 1:1 ratio with what is drawn (it will be stretched to fit the viewport automatically, always scaling up).
As you can see, scaling your game is completely dependant on the aspect ratio of the display being used, and there is no way you can make a "one-size-fits-all" solution, due to the fragmented state of device screens (especially on Android). This scaling solution gives a "best fit", and can easily be adapted to suit your own base game resolution. Note that you can call this code in a script so that you can simply call the script at the start of each room. You can even add a final piece of code to have this automatically set all the views in all your rooms so that you only need to call it once at the start of the game:
var _check = true;
var _rm = room_next(room);
var _rprev = _rm;
while (_check = true)
var _cam = room_get_camera(_rm, 0);
var _newcam = camera_create_view((1024 - VIEW_WIDTH) div 2, (768 - VIEW_HEIGHT) div 2, VIEW_WIDTH, VIEW_HEIGHT);
room_set_camera(_rm, 0, _newcam);
room_set_viewport(_rm, 0, true, 0, 0, VIEW_WIDTH, VIEW_HEIGHT);
if _rm = room_last
_check = false;
_rprev = _rm;
_rm = room_next(_rprev);
This code will just loop through every room in your game, destroy the default camera, and then create a new camera that is correct for the display size and aspect ratio, as well as enable a viewport in the room for the camera to act on.
Hopefully, this tech blog has cleared up a bit of the mystery behind scaling for different display sizes and enables you to get your games looking as they were designed on most platforms! Note though that the HTML5 target will require a bit of extra work as it presents a unique set of problems when trying to scale your game, so In our next article, we'll be taking a look at games designed for that platform and suggesting ways to scale them correctly.