Variable references in GameMaker

A tutorial about variable references in GameMaker.

Sometimes you might want to pass an instance variable by reference rather than by value - to have a script modify instance variable(s) passed to it without having to return them, or have an instance save a reference to variable(s) that it should change on other instance, or doing anything else that would usually imply using pointers.

But it might seem like GameMaker does not support such things... or does it? Well, we'll make it.

Note

If you are using a version of GameMaker that came out in 2020 or later (GMS2.3+ / GM2022+), check out the updated post on doing this more conveniently with structs.

The idea

In languages without conventional pointers, reflection can be commonly used to pack a context and variable name to a small structure for later use. GameMaker supports simple reflection via variable_instance_ functions (also see my blog post about this).

Other option is to make the variable in question a one-element array instead, and work with it via v[0] / v[@0] = value.

We'll discuss the first option here.

Simple implementation

In GMS1 you'd make a script called pack (previously mentioned),

/// pack(...values)
var arr = array_create(argument_count);
for (var i = 0; i < argument_count; i++) arr[i] = argument[i];
return arr;

to be able to do

var myref = pack(self, "x")

to pack a pair of values into a tiny array.

In GMS2, array declaration syntax was introduced, which means that you can just do

var myref = [self, "x"];

Then, you'd have one script for getting the value from reference, called ref_get:

/// ref_get(ref)->value
/// @param ref
/// @return value
return variable_instance_get(argument0[0], argument0[1]);

and a script for changeing the value by reference, called ref_set:

/// ref_set(ref, value)
/// @param ref
/// @param value
variable_instance_set(argument0[0], argument0[1], argument1);

And that's the basics - you can now do

x = 100;
var my_x = [self, "x"];
show_debug_message(ref_get(my_x)); // 100
ref_set(my_x, 200);
show_debug_message(x); // 200

Advanced implementation

Let's suppose that just getting/setting instance variables is not enough in your case - you want to also be able to reference global variables, or arrays, or data structure fields, or other things.

With a bit of thoughtful design, you can have all of that.

So, first, our reference mini-arrays are going to gain a prefix-item - the script determining what to do with the actual thing. So ref_get becomes

/// ref_get(ref)->value
/// @param ref
/// @return value
return script_execute(argument0[0], argument0);

and ref_set becomes

/// ref_set(ref, value)
/// @param ref
/// @param value
script_execute(argument0[0], argument0, argument1);

now, we can introduce pairs of scripts for each kind of reference - one to assemble a reference-structure, and one for the actual getter/setter implementation.

So, for instance variables you'd have acc_var,

if (argument_count > 1) {
    variable_instance_set(argument0[1], argument0[2], argument1);
} else return variable_instance_get(argument0[1], argument0[2]);

and ref_var to assemble a reference to it,

/// ref_var(inst, varname)
/// @param inst
/// @param varname
/// @return ref
return [acc_var, argument0, argument1];

(in GMS1 you'd have pack(...) instead of [...] as per above)

and could use it like

x = 100;
var my_x = ref_var(self, "x");
show_debug_message(ref_get(my_x)); // 100
ref_set(my_x, 200);
show_debug_message(x); // 200

For global variables you'd similarly have acc_global,

if (argument_count > 1) {
    variable_global_set(argument0[1], argument1);
} else return variable_global_get(argument0[1]);

and ref_global,

/// ref_global(varname)
/// @param varname
/// @return ref
return [acc_global, argument0];

and could use it like

global.some = 1;
var g_some = ref_global("some");
show_debug_message(ref_get(g_some)); // 1
ref_set(g_some, 2);
show_debug_message(global.some); // 2

Or arrays as acc_array,

var arr = argument0[1];
if (argument_count > 1) {
    arr[@argument0[2]] = argument1;
} else return arr[argument0[2]];

and ref_array,

/// ref_array(array, index)
/// @param array
/// @param index
/// @return ref
return [acc_array, argument0, argument1];

and could use it like

var arr = [1, 2, 3];
var arr_1 = ref_array(arr, 1);
show_debug_message(ref_get(arr_1)); // 2
ref_set(arr_1, 4);
show_debug_message(arr[1]); // 4

Overall, you get the idea.

Conclusion and things to consider

On a closing note, since there is always going to be some form of overhead from making arrays, calling scripts, and pulling variable values by name, you should consider whether you actually need references.

For example, in-game debug tools and editors are a good place for this, as the approach allows to easily link up references from various sources for presentation/editing.

Allowing a movement behaviour script to be ran with different sets of velocity variables isn't a good place for this, neither in terms of optimal performance nor simplicity of resulting code.

Have fun !

Related posts:

One thought on “Variable references in GameMaker

Leave a Reply to Anti-Mage Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.