Skip to content
Justin Behanna edited this page May 31, 2021 · 3 revisions

On a glance, GameMaker macros might seem pretty similar to C macros, but not exactly:

  • They are token-based, not text-based.
    This is mostly a good thing and saves you from a couple strange errors.
  • GML macros do not support arguments.

The second part is a bit of a shame - in C/C++, macros can be used for ad-hoc implementations of language features, ranging from simple shortcuts to generating entire fields and types. However, that can be fixed with a bit of preprocessing.

The premise

Given

#mfunc view_get_xview(i) camera_get_view_x(view_camera[i])
var vx = view_get_xview(0);

this would be translated to and from

//!#mfunc view_get_xview {"args":["i"],"order":[0]}
#macro view_get_xview_mf0  camera_get_view_x(view_camera[
#macro view_get_xview_mf1 ])
var vx = view_get_xview_mf0 0 view_get_xview_mf1;

Or, for an example of something you cannot do with a script,

#mfunc swap(a, b) { var __swap = a; a = b; b = __swap; }
var a = 1, b = 2;
show_debug_message(string(a) + " " + string(b));
swap(a, b);
show_debug_message(string(a) + " " + string(b));

<->

//!#mfunc swap {"args":["a"," b"],"order":[0,0,1,1]}
#macro swap_mf0  { var __swap = 
#macro swap_mf1 ; 
#macro swap_mf2  = 
#macro swap_mf3 ; 
#macro swap_mf4  = __swap; }
var a = 1, b = 2;
show_debug_message(string(a) + " " + string(b));
swap_mf0 a swap_mf1 a swap_mf2  b swap_mf3  b swap_mf4;
show_debug_message(string(a) + " " + string(b));

The syntax

Pretty straightforward really,

#mfunc <name>(<...arguments>) <...code>
#mfunc <name>(<...arguments>) as "<type>" <...code>

then, each use of argument by-name inside code will be replaced by the passed in expression.

The second version allows you to specify how your macro should be highlighted - e.g. if you are essentially adding a new keyword, highlighting it accordingly is nice:

#mfunc case2(a, b) as "keyword" case a: case b
switch (get_integer("Pick a number!", 0)) {
    case2(1, 2): show_message("You picked 1 or 2!"); break;
    default: show_message("You picked something else!");
}

also see Finding what to style on discovering token types.

If you would like variable arguments, you can use ...:

#mfunc log(tag, ...) show_debug_message(tag + ": " + string([...]))
log("test", "hi!", "hello!"); // -> show_debug_message("test: " + string(["hi!", "hello!"]))

Multi-line macro functions are also supported:

#mfunc view_get_yview(i) \
	camera_get_view_x(view_camera[i])

show_debug_message(view_get_yview(0));

Special variables

Macro functions support a number of magic variables in @@name format:

  • @@__FILE__: Name of the current file as a string
  • @@__HERE__: Name of the current resource, literal (can be used to self-reference)
  • @@__DATE__: Date of changing the file via GMEdit
  • @@__TIME__: Time of changing the file via GMEdit
  • @@__LINE__: Line that the macro-function was called from
  • @@__LINE_STR__: Same as above, but as a string
  • @@argument, @@argument_count, @@argument#: Lets you use script arguments inside your macros
  • @@yourArgName: Turns the argument "value" into a string

Examples:

#mfunc log(msg) show_debug_message("[" + @@__FILE__ + ":" + @@__LINE_STR__ + "] " + string(msg))
log("hi!"); // -> show_debug_message("[scr_test:2] hi!");

#mfunc dump(val) show_debug_message(@@val + " is " + string(val))
var hi = 1; dump(hi); // -> show_debug_message("hi is " + string(hi));

Concatenation

You can use pre##yourArgName, yourArgName##post, or pre##yourArgName##post to form new identifiers based on arguments. This can be handy for forming temporary variables

#mfunc argument_pack(arr) for (var arr = array_create(@@argument_count), _i_##arr = 0;\
	_i_##arr < @@argument_count; _i_##arr++) arr[_i_##arr] = @@argument[_i_##arr]

Which, when used like

argument_pack(args);
show_debug_message(args);

would make variables called "args" and "_i_args" accordingly.

Limitations

  • #mfunc does not work in GMS1 due to IDE denying to compile any "non-value" macros.
  • You cannot stitch together GMEdit-specific syntax (e.g. lambdas) because #mfunc is disassembled into GML macros.
  • Rearranging mfunc arguments does not currently auto-update other uses across the project (only in the active tab), so be careful with that.