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 !
man I just love you