Sitemap
Brian Balthazar

Brian Balthazar

General Programming in GameMaker

11 min readJan 23, 2023

--

Press enter or click to view image in full size
General Workflow of GameMaker

General Introduction to GML

In this section, I will try not to assume anything, so I will try to explain things as I understand them. GameMaker will also be referred to as GM for typing. Another thing is that the below examples will use GML (Code) but it can be converted to GML Visual now in GM.

You can use F1 to access the manual, or middle-click on a function to directly access that function.

Preliminary Knowledge

Objects: You can create all types of assets, but one of the most important types is objects. Objects are basically like “blueprints” for a house, and instances (the finished house) are placed in a room, which is where all the interesting things occur. Several instances can be placed in a room, or none at all. Each instance has an ID or identifier that users can manipulate through the room editor/inspectors or code.

Press enter or click to view image in full size
A new object, with the “obj_” prefix

Game Speed: For those who have used room_speed for their projects, it is better to use the game control functions like game_get_speed to determine the number of game frames to run per second (users would say to set the game speed to 60 for 60 frames per second as it is smoother).

Timelines: Timelines can take an amount of time in frames (a fraction of the game speed time) and perform something during that time. If there is a time that is slightly off, or the game speed is changed, then you may have to redo all of them.

Directions: Directions in GM take some getting used to, but 0° is right, 90° is up, 180° is left and 270° is down.

Booleans: True is one or “on”, but can sometimes be extended to any non-zero value, while false is zero or “off”.

sprite_index vs image_index: sprite_index is the sprite name itself (in the list of assets), while image_index is the sub-image (the specific frame) of that sprite. A sub-image is a frame (the specific slice of the animated image) of the sprite and starts from 0.

Clean Up: If coming from GMS1.x, there is a “new” event known as the “Clean Up” event that usually handles things programmers need to clean up (“dynamic” resources created as the game is being played), such as particle effects, buffers, and surfaces.

Conditionals

Conditional logic such as if and else controls the flow of the program’s execution. Code is usually read top-down, or from the first line at the top to the last line at the bottom of the file, but these conditional statements change that flow. To “check” something, like a player’s health going under 0, potentially destroying them, then we check using these statements. Else is seen as a catch-all, where if you did not match anything with an if or an else if, then it reaches the else before continuing execution with the rest of the program.

For conditionals: I always see something like this:

if condition {
return "value when true"
}
else {
return "value when false"
}

However, there are different and potentially cleaner ways to do this. There is a ternary operator that functions like this: (condition) ? value when true: value when false. How I remember this is as if asking a question. If the condition is true, and if it is, then go over to the first value. If it is false, then reach over to the second value.

Some programmers believe that it helps with readability and keeping things concise, while others use normal if statements. Depending on the compiler (which is the program that “compiles” the code you write into machine code, instructions that your computer can understand), it can get converted over the same way, so I take it as a matter of taste. For the example above, there is a way that combats all the extra typing: return (condition). This returns whatever value condition was regardless of if it’s true or false.

If you were replacing a value like this:

var xx;
if (condition) {
xx = 1;
}
else {
xx = 0;
}

You can just do:

var xx = condition;

Types: This works as you are using a boolean type, which is 0 (off) or 1 (on). There are also integers (whole numbers), doubles (basically decimal numbers), and strings (text like what you are reading now, written like “this”).

For a more complicated example:

var xx;
if (condition) {
xx = 89;
}
else {
xx = 65;
}

You can leave the structure like this, or you can use the ternary or even one if statement

Ternary:

var xx = (condition) ? 89 : 65;

If:

var xx = 65;
if (condition) {
xx = 89;
}

There will be no major performance benefit with very small if statements, but as the game becomes more complex, reducing the number of checks will improve performance.

Variables

Variables are used to store information to be referenced and used by your programs. They also provide a means of labeling data (a section of computer memory) with a symbolic name to refer to later.

This is to make our jobs easier as programmers so that we do not have to remember all kinds of different values and improves readability for other programmers.

The computer is not worried about what you name them (except for the times you name a variable with a number or another special character first before letters) as all the variable names get stripped and substituted with the actual values during the compilation/execution of the game anyway.

Local variables are used only on the script or code block where they are defined, but they can be useful as you can access them through other objects (only through the code block), especially using with. Instance variables can be used within the object where it is defined, and can also be seen by other objects when you are accessing that object in their code. Global variables are accessible to all objects and can be modified anywhere.

Scripts (Functions)

Scripts are a nice way of making code cleaner, reusable, and much more flexible. Instead of having the same code in 10 different places, you can put all of it into a script and call it in all of those places, which saves typing, and allows you to change a value in one place instead of 10.

Get Brian Balthazar’s stories in your inbox

Join Medium for free to get updates from this writer.

Recently GM expanded to functions so that users can use one script file to hold several functions and users can call each one individually or set them to a variable for use later. Parameters are also local variables, so you may not need to use argument0 and others anymore.

Parameters vs Arguments: Parameters are the variables that you create next to the name of the function itself, while arguments are what you pass into a function when calling it elsewhere.

function func(local1) {
return 0;
}

vs.

func(0);

“local1” in the top example is a parameter and “0” is an argument in the bottom example.

Loops: Loops like “for”, “while”, and “until” all loop over values for an amount of time (as described here).

For is usually for when you know the number of times to loop, like drawing lives to the screen. For loops check in-line: for (assignment; condition; operation;)

While is usually used to loop for an unspecified number of times, like when you want the user to play a game again, but you don’t know how many times the player will play the game again (and even if you set a high number in a for loop, a hypothetical player can play the game for more than that many times and break the game). While loops check the condition before the loop: while (condition)

Do/Until is similar to while but checks the condition after it has looped at least once: do { statements … } until (condition)

Repeat is the simplest and it may be faster than other loops at times: repeat (condition)

Data Structures (Arrays too): Arrays hold several different values that can be related to each other, including other variables. This can be used for things like inventories. With version 2.3 and above, arrays are much more powerful and are more like arrays from other languages.

Declaring:

arr = [1, 2]

Accessing:

arr[0] // Takes out 1

This was a 1-Dimensional array, but they can be stacked and have arrays within arrays:

arr[1, ["hi"], 2]

Data structures are special types of ways to handle data in a game, but for things like ds_lists and ds_maps, I would just use these new arrays and structs as users do not have to worry about releasing memory the same way as data structures. Before, I usually used a ds_list, which is a list of values, and a ds_map, which is similar to a table.

Memory Leaks: On that note — memory leaks are essentially what happens when a certain process that’s been running on your computer refuses to release the memory that it has been assigned. Sometimes the RAM (memory on your computer) doesn’t free up even long after the executed program’s window has been closed, and that hoarded memory can cause all sorts of problems like slowing down your machine. In extreme instances, it becomes unresponsive or even crashes (by then the operating system would take back all the memory for use again afterward). They happen when you do not clean up dynamic resources like data structures.

Built-in variables: For built-in vars (like speed or gravity), it may be better to limit your use of them as they can lead to very strange or unpredictable bugs (issues) when you try to make more complicated projects. They are also dependent on the game/room_speed, so changing that could lead to having to redo all your other variables if you wanted to change your game’s speed (30 frames to 60).

Usually, experienced programmers want more control over what their code does and want to limit how much GM is controlling things behind the scenes, so they create their variables to imitate the built-in ones. You also have more control over where you can place your code and do not have to worry about creating checks to make sure the variables are working properly.

Surfaces: Surfaces are blank canvases where you can draw different effects. GM itself uses a surface called the application surface that it uses to put on the screen. The benefits of using your defined surface are to manipulate things (could be used to draw text, destruction effects, or other special effects) and draw them to the screen. Surfaces are volatile, which means if something happens to the game window (minimized or someone switches to a different application), the surface could be destroyed, which causes its own set of bugs. Surfaces are also not placed on the screen as usual but are relative to what you are using (usually a camera, or the view of the game).

Shaders: Shaders are two-part programs that run directly on the GPU (graphics card), making it very fast since the GPU is doing the work. A GPU does one thing very quickly, so not only is this great for effects, but it will be very fast for processing as well as long as you are using them correctly. If you have a view (going to the room editor, enabling views, and ticking the “Visible” checkbox on one of the views), it is best to set that shader to the view and not the whole room to prevent wasted processing time. The full shader is composed of a vertex shader program (the one a lot of people don’t touch), and a fragment/pixel shader (the more used one). To use them in an object:

shader_set(firstShader);
// Drawing code
shader_reset();

This tells GM to allow the shader to run, then do any of the drawing code that you want to be affected by the shader, then reset the drawing of the shader.

You can also use uniforms, which can be variables you pass to a shader to manipulate what the shader draws.

If you wanted to find a shader somewhere, make sure it uses the GLSL ES format so that it is compatible with all targets, just in case you wanted to port your game in the future.

Sequences are a visual way to do cutscenes and flashy effects without having to constantly code and rerun the entire project to see if an effect is playing correctly.

Animation Curves go hand in hand with sequences, and they allow for really nice effects, especially bouncing, or easing between transitions.

Filters are built-in shaders that accomplish many of the common effects game developers try to achieve, such as screen shakes, desert effects, or underwater scenes.

Structs: Structures are containers of information. These are different from full objects as they are not heavily processed. They can contain variables, values, and even other structures. You originally had to make things like inventories with arrays or other data structures. Another option is to use structs as they are quite flexible and allow for constructors, which is a way to initialize values when a struct instance is created. It also goes hand-in-hand with sequences, as sequences communicate through structs to manipulate data.

Particles: Effects that can be used to spice up games. They are not processed in the same way as objects, so you can potentially put “tons” without any slowdowns (compared to objects). You must free them when not using them, and you can use the create_effect functions, or create your particles using the built-in system. I typically have all the particle definitions in a script and free the particles I don’t need to use throughout the game. Sometimes emitters may be useful to your needs, where you need a region filled with particles, otherwise, the regular part_particles_create will be needed. All the particles except for the create_effect ones have to be defined before use and freed to prevent memory leaks!

Gamepads/Controllers

This was interesting as the analog stick uses continuous values, but the buttons react as keyboard presses would. In earlier versions of GM, it was necessary to check if the gamepad was connected in the step event (the most common event to run a lot of game code), but now users should not be putting the gamepad check code into the step event. With the 2.3 update and above, users should be using asynchronous events.

Asynchronous activities allow for different events to happen in the background. This means that outside of the flow of the game code itself, GM will update the access to gamepads once one is connected, so users do not have to waste processing time checking for it themselves. It is an event under Asynchronous->Async-System, then using this as a base:

switch (async_load[? "event_type"]) // Switch upon the async_load map to see which event triggered
{
case "gamepad discovered": // Gamepad has been found
var pad = async_load[? "pad_index"]; // Obtain the pad index value from the async_load map
// Set the "deadzone" for the axis
// Set the "threshold" for the triggers
break;
case "gamepad lost": // Gamepad has been removed or not enabled
var pad = async_load[? "pad_index"]; // Get the pad index
break;
}

Deadzones are how far players have to press or move the analog sticks before the input is registered, which is helpful as gamepads can drift, and players may not want that to affect their gameplay.

--

--