Nine Simple Coding Tips When Coding With GameMaker


Nine Simple Coding Tips When Coding With GameMaker

Nobody’s perfect, right? Judi Dench has starred in bad films, Michael Jordan missed a basket or two, and Hideo Kojima couldn’t come up with a sensible name for a character if his life depended on it.

Whether you’re a complete novice or a grizzled veteran, there’s always room for improvement, and coding with GameMaker is no exception. 

With that in mind, let’s take a look at a few best practice changes to your GameMaker workspace that can help improve your game’s framerate and simplify your code.
Die-Hardman from Death Stranding
Death Stranding’s Die-Hardman, created by Hideo Kojima

This article includes

Before we begin...

The suggestions we’re about to make are rule-of-thumb best practices that can help you organise and optimise your game’s code, but there’s never a hard-and-fast right or wrong answer.

It all comes down to what makes your life easier and your projects more manageable, so see these tips as guides, rather than instructions.

It’s also important to remember that you shouldn’t try and squeeze a few extra FPS out of your game if it’s already running perfectly well. What you learn here may be more useful for your next project than your current one

With the disclaimers out of the way, let’s get going, shall we?

Level Up Your GML Code, GameMaker Coaching

Coding style

When it comes to writing code, everyone has a style.

The way you place your brackets, how you indent the lines, and how you declare and name variables are all examples of the stylistic differences between coders.

It’s essential to make your code clear and readable to other people, and to your future self. You don’t want to come back from a two-week holiday in the Bahamas and wonder what on Earth this clump of coding means.

There are many programming styles to take inspiration from, but almost any style is fine as long as it’s consistent and clear.

Example function
In this example script, you can see that it uses the JSDoc style comments to clearly explain what the first function does. The coding style is consistent, with tab indents, underscores used for local vars, etc.

Make use of automatic code folding, which allows you to fold any blocks that use curly brackets {}. This makes it easier to navigate a script and hide sections you’re not working on.

Block folding
Fold your code blocks to navigate large scripts easily

You can also use #region and #endregion to section off parts of your code manually. You can comment on regions, which you can read more about in the GameMaker Manual.
Example Of Using Regions
These things improve your code’s readability, but don't affect its usage. GameMaker strips out comments, removes unnecessary line breaks and whitespace, substitutes in constant/macro/enum values, and compresses your code when it compiles your game.

This means that you can add as much whitespace around your code as required without needing to worry about keeping your comments short or only using them sparingly.

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, for example) 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 negligible, and greatly improve the clarity of your code. It’s a no-brainer, really.

The same idea should be applied to functions too, where you should assign sensible names to arguments, and use clear formatting and local variables where required to make it as readable as possible.

Clean vars

Make your code cleaner with local variables

Local variables are fast to process in-game, so make the most of them. If the same function call or calculation appears in a code block two or more times, think about creating a local variable for it.

When using the YoYo Compiler (YYC) targets, if you reference global or instance variables multiple times in a script or code block, assign them to a local variable at the start of your code and reference that local variable for greater performance.

Arrays

Arrays are easy to use and require less memory than data structures, but they can still be further optimised.

When you create an array, memory is allocated to it based on its size. You should always try to initialise an array to its maximum size first, even if you don't plan on filling it until later on.

For example, if you know you need an array to hold a maximum of 100 values, you should initialise it to 100 slots straight away, using the array_create() function:

array = array_create(100, 0);

This allocates the memory in one "chunk", with all array values set to the default value of 0 to help keep things fast. If you don’t do it this way, every time you add a new value to the array, the entire memory has to be reallocated again.

You can free up the memory associated with an array by setting its variable to some other value, like undefined. This way the reference to that array is lost, and if it was the last reference, the Garbage Collector will free its memory.

array = undefined;

Data structures

Current GameMaker data structures run much faster than in previous GameMaker versions.

They still need to be destroyed when not used to free memory, and they can still be slower than arrays, but their simplicity and extra functions for dealing with the data they contain often outweigh the minimal speed difference, so don't be afraid to use them in your games.

It should be noted that of all the data structures, DS Maps in particular are lightning-fast, both to read from and write to, making them an excellent option for all types of tasks.

We’ve already mentioned accessors for arrays, but they’re also available for data structures, which can help clean up your code and make it much easier to read.

Structs and constructors

A struct allows you to store multiple variables inside it. Such variables can also hold functions (known as methods), making structs a powerful way to store multiple, related properties in one place, with functions for interacting with those properties.
Struct example
What makes them even more powerful, is the ability to create templates for your structs. Such templates are created using constructor functions.

A constructor lets you define the variables and functions a struct will have. Call new() to create a new struct from that template, which will have all of the defined variables and functions, ready to be read and modified.

Constructor example
Structs and constructors make certain programming patterns easier, and also make your code cleaner, as you have less “stray” variables, which are instead encapsulated into structs.

Collisions

There are multiple ways to deal with collisions in GameMaker, and most of them come with a bit of extra CPU overhead attached. 

The collision_ and point_ 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, but you will also be doing per-pixel checks, which is very slow indeed. We have an older tech blog on this which is well worth a read:

Collision Example Using Circles

That’s 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 for every situation.

Alternatively, look into creating a tile-based collision system, which can be created using the tilemap functions or even a custom 2D array or DS Grid.

 These will be very fast and will help boost your game's speed. However, if you are using irregular terrain or walls and objects that don't align to a grid they may not be appropriate. 

You can find a very simple video tutorial on tilemap collisions from Shaun Spalding:

GameMaker Studio 2: Tile Collisions

Texture swaps and vertex batches

If you switch on the debug overlay in GameMaker, you’ll 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 performed, and the second is the number of vertex batches.

A number of factors will influence these figures and you’ll 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.

The Debug Overlay Bar

Texture swaps

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 Texture Group Editor.

For example, if you have a number of images that are only used in the main menu, put them together on a separate texture page. The same applies to level-specific images, the player and the enemies, etc.

Basically, you want to group them by use so that the swaps are reduced as much as possible. Additionally, to help keep VRAM optimised, you can use the different prefetch and flush to load and remove textures from memory as required.

NOTE: As mentioned at the start of this article, if your game runs fine with a decent FPS then don't worry too much about texture swaps...especially when creating projects on the desktop targets

These optimisations are best when used with large games or on low-end mobile devices, and when used incorrectly can they actually negatively impact performance.

Vertex batches

Vertex information is sent in "batches" to the GPU for drawing, and in general the larger the batch the better. You should avoid “breaking" the batch 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, including:
  • Blend modes
  • Setting the draw colour
  • Setting the draw alpha
  • 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 example, you’ll 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, like this:

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

NOTE: This doesn't just apply to using bm_add - any blending change will break the batch and will give a performance hit .

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

In fact judicious use of the functions gpu_set_blendenable(), gpu_set_alphatestref(), and gpu_set_alphatestenable() can greatly improve performance and can be enabled/disabled as required throughout your project code, although they may not be appropriate for all types of graphics or projects.

NOTE: If you don't require the instance to draw itself when using a controller in this way then you can simply add a comment into the Draw Event to suppress the default drawing, or make the instance invisible (although this will prevent all code running for the instance in all draw events).

Another way to reduce these numbers is to disable the Separate Texture Page option for sprites unless absolutely necessary.

Each image stored in this way is sent to its 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 coding 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 always implement some of the ideas into your next project.

Particles

Particles offer a very quick and efficient way to create dynamic effects in your game.

However, it's worth noting that using additive blending, alpha blending and colour blending on the particles may lower your game’s performance, especially on mobile targets, so if you don't need it, don't use it.

Additive blending in particular can greatly increase your vertex batches and should be used with care.

On the non-WebGL HTML5 target, having multi-coloured, fading particles will require a lot of image caching and will be very slow.

However, since particle sprites can be animated, you can create an animated sprite that has sub-images which change colour and use that for the particle instead. It’ll 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 Guide To GameMaker Particles.

Surfaces

Ever since the 2.2.3 Beta update, GameMaker has allowed you to switch surfaces on and off the depth buffer, which can significantly optimise your game.

When you just use surfaces as normal, GameMaker will actually create a surface and an accompanying depth buffer to ensure proper depth sorting when drawing anything in 3D.

However, for most 2D games, this extra depth buffer simply isn’t needed, so it takes up extra memory space and processing time. This is where the function surface_depth_disable() comes into play...

This function can be called before you create a surface to disable the depth buffer generation, and all surfaces created afterwards won’t have a depth buffer created for them.

You can enable/disable this function as required, and can even call it once at the start of the game to disable depth buffers for all subsequent surface calls (in most 2D games, this is fine).

It won't always provide a major performance boost, but it's certainly something to keep in mind if your game relies heavily on surfaces.

BONUS TIPS

Let’s wrap things up with a little Brucie Bonus. Here are a couple more general coding tips that can help improve your productivity:
  • Don't be afraid to use the trigonometric functions. Contrary to popular belief, they’re pretty fast, especially when compared to particles, collisions, strings, etc.

  • Don't put code that isn’t for drawing things in the Draw events

  • Use Alarms or Time Sources to run any code that doesn't have to be called every step, instead of just adding it into the Step event anyway

As we’ve mentioned a few times now, all of these optimisations are optional and if your game runs fine with 60 vertex batches, 80 texture swaps, and additive blending, don't worry about it too much.

The important thing is creating a system that optimises your workflow, the accessibility of your code, and the stability of your game.


Written by Gurpreet S. Matharoo

Technical Writer at GameMaker, Gurpreet creates documentation and tutorials, to make game dev easier for you. He loves using the computer to bring new things to life, whether it's games, digital art, or Metal music.