One of the most frequently asked questions about making games with GameMaker Studio 2, is how to optimise them so that they run as efficiently as possible. Unfortunately there is no easy answer to this! Each game is different and how you code them will vary depending on your own needs for the project... however there are a few general "rules of thumb" that can be followed in all projects which will help you to get the most from GameMaker.
One of the most common causes of slow-down and stuttering in a game is the graphics pipeline, so you want to make sure that you draw everything in the most efficient way possible. Below you can find some tips on how to achieve this.
GameMaker Studio 2 stores all your game graphics on Texture Pages. A texture page is simply a single image with all the game graphics spread out in such a way that they can be "pulled" from it at run time to draw on the screen.
Now, when you have created many graphics for your game they may start to take up more than one texture page and you can have them spread out over several different ones. This means that for GameMaker Studio 2 to draw them on the screen, it may have to perform texture swaps to get the correct sprite from the correct page, which is not a problem when it's only a couple of pages, but when they are spread out over of a lot of pages, this continuous swapping can cause lag and stutter.
How to avoid this? Well, you can create Texture Groups for your game and then flush and pre-fetch the unused graphics from texture memory at the start of the room, meaning that only those graphics you are going to actually use are held in memory.
To start with, you should open the Texture Group Editor from the Tools menu at the top of the IDE. Here you can create the texture groups groups that you need - for example, if you have some graphics that only appear in the Main Menu, create a group for them. If you have a series of sprites and backgrounds that only appear in a single level/room, then make a group for them, etc...
Once you have your groups, you can then go through the sprites and backgrounds and assign each one to a specific group from here using the "Add Resource" button, or you can go to each sprite and set the texture group from the Group drop-down menu in the asset window:
With that done, you have already optimised your game quite a bit as this will limit the texture swaps needed, since all the graphics for a specific room should now be on the same page.
As for flushing the unneeded pages from memory, you would do this in the Create Event of the very first instance of each room (this can be set from the room editor) using the function
This function clears all image data from the texture memory. Note that this may cause a brief flash or flicker as the new textures are loaded for the first time when the draw event is run, and so, to avoid this, you should also "initialise" the textures in the same event, by simply calling a pre-fetch function (one for each of the needed pages). This will not be seen since it is in the Create Event, but will prevent any glitches or flickers when the game graphics are actually drawn. Your final Create Event would look something like this:
Remember! You only need to pre-fetch ONE graphic for each texture page! You can actually see how the sprites are packed onto each texture page from the Game Options for each platform.
This is done by clicking the "Preview" button in the Graphics section of the game options, and permits you to see how the finished texture pages will look. In this way you can see if all the images are on one page or multiple, and decide on how your groups and pages should be created and assigned.
When drawing, GameMaker Studio sends off "batches" of graphics data through the pipeline for drawing, and obviously you want to keep this number as low as possible. Normally it's not something you would need to worry about, but if you are using blend modes for drawing, then each call to set the blend mode breaks the current texture batch and multiple calls from multiple instances can have an adverse affect on your game.
How to solve this? Try and use just one instance to set the blend mode and draw everything that is required. For example:
with (obj_HUD) draw_sprite(spr_Marker, 0, mx, my);
with (obj_Player) draw_sprite(spr_HaloEffect, 0, x, y);
with (obj_Cursor) draw_self();
That would set the blend mode for a single batch call, instead of having three separate ones for each of the instances referenced.
NOTE: Other things that can break the batch are drawing shapes, using the draw healthbar functions, using a shader and changing render targets.
Alpha Blending and Alpha Testing
There are two draw functions included in GameMaker Studio 2 which are often over-looked, but they can both dramatically speed up the draw pipeline. They are:
How can these help? Well, the first one can enable alpha testing which basically checks the alpha value of each pixel and if it is above the blend threshold (a value from 0 to 255), then it is drawn. Essentially this "throws away" any pixel with an alpha lower than the test value, meaning that it is never drawn (as even a pixel with zero alpha is still "drawn" normally), and is an excellent way to speed up games that have retro, pixel art graphics with no alpha gradients. Note that you can set the alpha test reference value using the function gpu_set_alphatestref().
The alpha blend function plays a different role, and can be used to switch off all alpha blending. This will mean that any sprites or backgrounds with alpha will be drawn completely opaque. This function is designed to be used at any time in the draw pipeline, so if you are drawing a background manually and it has no alpha then you can switch off alpha blending, draw the background, and then switch it back on again for all further drawing. On some games this can give a massive speed boost, so if you are drawing something that doesn't require alpha, consider switching this off (note that it can be enabled and disabled as often as required with very little overhead).
Adding Assets At Runtime
Loading sprites from an external source can be done in GameMaker Studio, as can creating new assets using functions like sprite_add(). However each new asset that you create in this way will also create a new texture page, meaning that (for example) adding 10 new sprites will create 10 new texture pages! And each time you draw these sprites it is a new texture swap and a break in the batch to the graphics card.
As you can imagine, this is not very efficient, and so (unlike previous versions of GameMaker) it should be avoided, with all the graphic assets being added to the game bundle from the IDE. Note that you can use these functions for adding/creating small numbers of things and they won't adversely affect performance, but adding many, many images in this way should always be avoided as it will have an impact.
When adding sound to GameMaker Studio 2, there are a number of available options for the format and quality of the final output sound file. These should be set automatically for you following these basic rules:
- If it is a sound effect (or any short sound bite of only a few seconds), then it should be uncompressed.
- If it is a sound effect but larger than a few seconds, or if it is only used very occasionally in the game, then it can be compressed.
- If it is a large sound effect and used frequently in the game it should be compressed (uncompressed on load).
- If it is music it should be compressed (streamed from disk).
Apart from the compression and streaming options, you also have settings for the sound quality. these should be set to be as close as possible to the settings used to create the original file that you are adding. So if your MP3 track is 22,050Khz and 56kbps, those are the settings you should use for the quality. If you are unsure about the actual values to use, then leave it as the default values that GameMaker Studio 2 sets for you.
Giving advice on coding can be difficult, as each person has their own opinion about things and what works for one, may not work for another. But there are certain things that should be noted when working with GameMaker Studio 2 that are true for everyone.
Early Out If
GameMaker Studio 2 has an "early out" evaluation of if. Consider the following code:
if mouse_check_button(mb_left) && mouse_x > 200 && global.canshoot == true
Here we are evaluating three different expressions and if they are all true then the rest of the code will run. However, if any one of them returns false, then the code won't run. The great thing about this is that if the first one is false, then the rest are not even checked, meaning that when creating "if" statements with multiple checks, put the most expensive one last, and try to put the least likely one to evaluate true first to make the most of this "early out" system.
Using global variables is a fine way to have controller variables that are accessible to all instances. However it should be noted that script calls which reference them (especially when compiling to the YYC) can be slowed down by multiple lookups of global variables. For example, consider this script:
if place_meeting(global.px, global.py, argument1) instance_destroy();
The issue here is that each iteration of the repeat loop has to look up the values for the global variables, which is very slow. To avoid this, you should always assign any global variables that are going to be used like this to a local variable. So our code example would become:
var xx = global.px;
var yy = global.py;
if place_meeting(xx, yy, argument1) instance_destroy();
As explained above, a local variable is "local" to the script or code block that it has been created in, and they have a very fast look-up time. This means that they are an ideal option to store any function call values or operations that need to be used repeatedly in a code. For example, if you have to draw something relative to the view center, calculate the point once and store it's coordinates in a couple of local variables for use later:
var xx = camera_get_view_x(view_camera) + (camera_get_view_width(view_camera) / 2);
var xx = camera_get_view_y(view_camera) + (camera_get_view_height(view_camera) / 2);
draw_sprite(spr_Crosshair, 0, xx, yy);
draw_text(xx, yy, dist);
In that simple example code we have halved the operations being done, simply by assigning to local variables first. In large code blocks this can be a significant optimisation, and you should always look at ways in which your code can be compacted to have the lowest number of operations or function calls. It's also worth noting that any variable lookup done on any instance will benefit from being stored in a local variable if used more than once in any code, and this is especially true when compiling using the YYC.
One simple optimisation trick for arrays is to initialise them in reverse order. In this way GameMaker Studio 2 will assign memory for the whole array in a block, rather than "bit by bit". So, for example, if you are just wanting to initialise the array to 0, instead of a loop you can do:
myarray = 0;
and that will create a 100 value array, cleared to 0. Should you need to assign values to each of the array indices then use a loop, but start from the last value of the array, for example:
for(var i = 255; i > -1; --i;)
myarray[i] = make_color_hsv(irandom(255), 150, 255);
It is important to note that this is not the case for HTML5, as it treats arrays differently. This means that you should initialise them from 0 upwards, and not in reverse for this platform.