Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more

Tech

Best Practices When Coding In GameMaker: Studio

Posted by Mark Alexander on 22 August 2014

In this article we are going to cover some "best practices" for when you are using the GameMaker Language (GML) to code your game, and at the same time explain a little bit about the inner workings of GameMaker: Studio. Before we continue, however, it is worth noting two very important points:

  1. This is a guide, and not the be-all-and-end-all-definitive-100%-perfect method to write your game! The things mentioned here are generally more on the organisational and micro-optimisation scale and should be incorporated into your coding habits when you feel comfortable with GML and think that they are appropriate.

  2. If your game runs fine and you are happy with things as they are, then don't rush to change everything just to squeeze a few extra FPS out. You have to strike a balance between readable, flexible and modular code with the time and energy required to change things, as well as the overall gain at the end. Basically, if it isn't broken, don't fix it and keep what you learn here for your next project.

With that said, lets move on and look at some general tips for writing good GML code that you can apply at any time...

Programming Style

When it comes to writing code everyone has a style. The style you program in is the way you place your brackets, how you indent the lines, and how you declare and name variables etc... and is essential to making your code clear and readable to other people and to your future self when you have to come back to a project after a time on something else.

There are many programming styles, and some would argue that theirs is the best one to use, but the truth is that almost any style is fine as long as you are consistent when using it and it is clear and obvious what everything is and does.

When writing code, you should realise that when compiling your final game GameMaker strips out comments, removes unnecessary line breaks and white space, substitutes in constant values and generally compresses your code down as part of the process. This means that you can add as much space around your code as required and don't need to worry about adding in an empty line or a comment to let your code "breathe".

Use Local Variables

Continuing on from the above point about programming style, one thing that a lot of beginners do is to cram as much into one line of code as possible. For example:

draw_sprite(sprite_index, image_index, x + lengthdir_x(100, point_direction(x, y, mouse_x, mouse_y)), y + lengthdir_y(100, point_direction(x, y, mouse_x, mouse_y)));

While not completely unreadable, it is inefficient (the point_direction() function is called twice) and it is messy and awkward to look at. It would be far better expressed as:

var p_dir = point_direction(x, y, mouse_x, mouse_y);
var local_x = x + lengthdir_x(100, p_dir);
var local_y = y + lengthdir_y(100, p_dir);
draw_sprite(sprite_index, image_index, local_x, local_y);

The memory and resources required to create those local variables are tiny, and are far outweighed by the instant benefit you (or anyone else reading the code later) gets from its clarity. The same idea should be applied to scripts too, where you should assign the input arguments to local variables at the top, as seeing a script full of generic "argumentX" variables is very confusing and easy to make mistakes with.

Local variables are fast to process so make the most of them and if an expression appears in a code block or script two or more times, think about creating a local variable for it. An important thing to note, especially when using YoYo Compiler targets, is that if you reference a global variable various times in a script or code block, it is better to assign it to a local variable at the start of the code and then reference that local variable, as global look ups are more expensive.

Arrays

Arrays are fast to use and require less memory than data-structures, but they can still be optimised further. When you create an array, memory is allocated to it based on it's size, so you should try to initialise an array to its maximum size first, even if you plan on filling it later. For example if you know you need an array to hold 100 values, you would initialise it like this first:

array[99] = 0;

This allocates the memory for it in one "chunk" (with all array values being set to the default value of 0) and helps keep things fast, as, otherwise, every time you add a new value to the array the memory has to be re-allocated again.

NOTE: On the HTML5/JS targets this does not apply! Arrays should be initialised from 0 for those targets. You can easily get around this by checking the os_browser variable, for example:

if (os_browser == browser_not_a_browser)
{
    array[99] = 1;
}
for(var i = 0; i < 100; i++)
{
    array[i] = SET_VALUE_TO_SOMETHING;
}

You can also free up the memory associated with an array by setting the variable used to equal 0. So, to clear the array from the code example above you would simply use:

array = 0;

Also note that arrays are passed by reference, but will copy the whole thing when a change is made (copy on write). So, if you pass an array to a script, you are passing a reference to the original array, and any values read from it, will come from the original source.This is nice and fast, but... if you need to modify any of the array values, the array itself is duplicated at the point of the write, and any changes made need to be returned from the script, or they will be lost. This is much slower, and consumes more memory, so be careful how you use arrays in scripts. However, you can also avoid this behaviour by using the special array accessor "@", as this gives direct access to the underlying array, and avoids the copy on write behaviour. For example:

// Call a script, passing our array
script(array);

// In the script we can do:
a = argument0;
a[0] = 100;        // The array will be copied and the copy modified
a[@0] = 100;       // This will modified the ORIGINAL array directly

Data Structures

In GameMaker: Studio data-structures have been optimised to be a lot faster than previous GameMaker versions. They still need to be cleaned up (destroyed) when not used to free memory, and they can still be slower than, say, arrays, but the ease of use and the extra functions for dealing with the data they contain can often out-weigh the minimal speed difference, so don't be afraid to use them in your games.

It should be noted that of all the updated data structures, ds_maps in particular are now lighting fast, both to read from and write to, making them an excellent option for all types of tasks.

You can also use accessors to help cleanup your code, and make it much cleaner, and easier to read.

 

Collisions

There are multiple ways to deal with collisions in GameMaker: Studio, but most of them come with a bit of CPU overhead attached. The collision_ functions, place_ functions, and instance_ functions all rely on bounding box checks with all instances of a given type within the room, and there is little in the way of optimisation built into the engine for this. It gets worse if you start to use precise collisions as not only will you be doing bounding box checks, you will also be doing per-pixel checks which is very slow indeed. Mike Dailly wrote an article on this here which is well worth a read: The Hazards Of Precise Collision Detection

That is not to say that you shouldn't use these functions, as they can be very handy. however you should know which ones to use and when, as they all work slightly differently and will have different speeds. Rough rule of thumb is that the place_ functions are faster than the instance_ functions which are faster than the collision_ and point_ functions, so read the manual and make sure to choose the most appropriate.

Alternatively, look into creating a tile-based collision system, which can be created using a 2D array or a ds_grid. These will be very fast and will help boost your game's speed. However if you are using oirregular terrain or walls and objects that don't align to a grid they may not be appropriate. If you want an example of this at work, check out the LAN Platformer Demo that comes with GameMaker: Studio.

Texture Swaps and Vertex Batches

If you switch on the debug overlay, you will see that there are two figures in brackets at the top of your screen when testing. The first is the number of texture swaps being done, and the second is the number of vertex batches. A number of factors will influence these figures and you will never get them down to (0) (0) as the engine requires one or two of each every step, but you should aim to get these values down as low as possible.

For texture swaps, the best and most efficient way to do this is to optimise how your sprites and backgrounds are stored on the texture page. This is done from the sprite properties, and you can create texture pages in the Global Game Settings. If you have a number of images that are only used in the main menu (for example), then put them on a separate texture page. The same if you have level specific images, or the player and the enemies etc... Basically you want to group them by use so that the swaps are reduced as much as possible.

Vertex information is sent in "batches" to the GPU for drawing, and in general the larger the batch the better. So "breaking" the batch should be avoided when drawing, as that will increase the number of vertex batches sent to the GPU. There are a number of things that will break the batch, with the main ones being blend modes, setting the draw colour, setting the draw alpha, and drawing the built in shapes and primitives.

So, if you have a number of bullet instances that draw using the bm_add blend mode (for more information on blend modes see here), you will be creating a new vertex batch for each one, which is definitely a bad thing! Instead, have a controller object in your game that draws all the bullets instead, like this:

draw_set_blend_mode(bm_add);
with (obj_BULLET)
{
      draw_self();
}
draw_set_blend_mode(bm_normal);

In this way, all the bullets will be drawn in the same batch. This method can be applied to alpha and draw colour too.

Another way to reduce these numbers is to disable the "Use for 3D" option for sprites (and backgrounds) unless absolutely necessary. Each 3D image is sent to it's own texture page and batched differently, so having these images on a regular texture page is better. You can then get the UVS coordinates using the sprite_get_uvs() function and store them in variables for use later. It may be a small amount of extra code, but the boost you will get is worth it. Note that this method will not permit texture repeats! As with all these tips, if it makes your life more difficult to change it and your game runs fine, don't worry about it too much....

You can find further optimisations for drawing in the article Optimising The Draw Pipeline.

Particles

Particles are great for effects and once you know how to set them up and use them, you'll find that they are an indispensable part of your games. However they too can be optimised to work better. Currently the built-in particle sprites are all stored on their own texture page, which means that when you burst a number of particles, the texture swaps can increase greatly. To get around this you can load the sprites into GameMaker: Studio as you would any normal sprite (you can find them in the &appdata%/GameMaker-Studio/Windows8/html5game/particles/ folder) and then set them using the part_type_sprite() function.

Using additive blending, alpha blending and colour blending on the particles will also lower performance, especially on mobile targets, so if you don't need it, don't use it! This is particularly true on the non WebGL HTML5 target, as having multi-coloured, fading particles will require a lot of image caching and will be very slow. But, since particle sprites can be animated, you can create an animated sprite that has sub-images which change colour and then use that for the particle. It will still look like a gradual colour change, but not require the constant creation of cache images.

You can find out more about particles from the tech blog A Quick Start To Programming Particles.

Summary

Hopefully you'll have learned a thing or two from this article about how GameMaker works and how to improve your games performance. There are other more general things that can help too:

  • don't be afraid to use the trigonometric functions as (contrary to popular belief) they are pretty fast, especially when compared to particles, collisions, strings, etc...
  • don't put code that is not for drawing things in the draw event
  • use an alarm to call any code that doesn't have to be called every step in the step event

But as we mentioned at the start of the article, all these optimisations are optional, and if your game runs fine with 60 vertex batches and 80 texture swaps, then don't worry about it too much! Just keep these things in mind when programming your next game...

Back to Top