Wiki
NuclearThroneTogether / Scripting / Language Features
The following are the differences and extensions of scripting language used, compared to GameMaker Language itself:
Script/function definitions
Script/function definition syntax matches the little-known multiscript/extension script file syntax in GameMaker.
That is, each script/function is to be preceded by a #define scriptName
header-line and will be considered to span until the next header or the end of file. For example,
#define init test("Hello!"); #define test trace("Test: " + argument0);
Depending on mod type, certain scripts will be automatically called by the game. Lists of these can be found on the according mod type page.
Named arguments
While you can use the standard argument#
variables to access arguments, there is also a bit of syntax for naming them.
To use it, simply open parentheses after the #define
statement and list the arguments:
#define draw_text_bang(rx, ry, text) draw_text(rx, ry, text + "!");
which would have been exactly equivalent to
#define draw_text_bang draw_text(argument0, argument1, argument2 + "!");
while a small syntactic enhancement, this allows to spend a bit less time typing out short scripts.
Macro definitions
As with GMS2, you can declare mod-wide macros by using #macro name value
syntax,
#macro version 1001 // ... trace(version); // equivalent to trace(1001)
Macros are replaced with their values during compile and are a way of implementing constants or shortcuts without any additional overhead.
Ternary operator
Ternary (inline if-then-else) operator is supported. This means that you can do, for example
draw_set_color(instance_exists(Player) ? c_red : c_blue);
Which would be equivalent to
if (instance_exists(Player)) { draw_set_color(c_red); } else draw_set_color(c_blue);
Array write
Array access behavior is "create-on-write", meaning that when you do
var a; a[0] = 1; a[1] = 2;
it in fact compiles to something like
var a; if (!is_array(a)) a = array_create(0); a[@0] = 1; a[@1] = 2;
You can always explicitly create arrays via array_create
or array declaration syntax (see below) and use [@index]
array access syntax to indicate that you are sure that a variable is an array and there's no need to ensure that it is an array prior to writing.
Array declaration
JavaScript-like array declaration syntax is supported, meaning that you can do
var a = [1, 2, 3];
which is a faster equivalent of
var a = array_create(3); a[@0] = 1; a[@1] = 2; a[@2] = 3;
Accessors
Unlike the regular GML (so far), accessors can be chained, meaning that you can do
var m = [[1, 2], [3, 4]]; var v = m[0][1]; // v == 2
without having to assign into m[0]
into an intermediate variable.
"in" operator
variable_instance_
functions are supported in the scripting language, meaning that you can do
trace(variable_instance_exists(self, "some")); // 0 or 1, depending on whether variable exists variable_instance_set(self, "some", "hi"); trace(variable_instance_get(self, "some")); // "hi"
since variable_instance_exists
is a pretty long name for something that you may want to do often, another thing that is supported is a (variable name) in (instance)
operator, meaning that you can do
if ("time" in self) { // variable exists - increment it time += 1; } else { // variable does not yet exist - give it a default value time = 0; }
for convenience, you can also use not in
:
if ('time' not in self) time = 0;
Lightweight objects
Sometimes you may want to group a bunch of values together, but without the hassle of creating-managing-destroying an instance. In such cases you may want to use lightweight objects instead:
var q = { a: 1, b: "hi" }; trace(q); // { a: 1, b: "hi" } trace(q.b); // "hi" trace("a" in q); // 1
Internally, lightweight objects are stored as 2d arrays, meaning that they are managed by the game automatically (no need to explicitly destroy), and the above sample would be stored as
1 "hi" "a" "b" <id>
(where <id>
is a field pattern id for faster lookup)
Wait-instruction
One of the scripting language's most notable features is the wait-instruction.
To put it shortly, when executed, the program will pause for the given number of frames while the rest of the game continues executing. So,
for (var i = 1; i <= 5; i++) { trace(i); wait 30; }
will count up to 5 while waiting 30 frames (one second) between each step.
This allows to do lots of interesting things. For example, to make an explosive bullet for a custom weapon, you could simply wait
for projectile to be destroyed while tracking it's position:
#define weapon_fire motion_add(gunangle, -4); weapon_post(6, -7, 5); // create and configure a projectile: var qx = x, qy = y; var q = instance_create(qx, qy, HeavyBullet); with (q) { team = other.team; motion_add(other.gunangle + random_range(-5, 5), 1); friction = -0.8; // gradual acceleration image_angle = direction; } // track projectile' position while it exists: while (instance_exists(q)) { qx = q.x + q.hspeed; qy = q.y + q.vspeed; wait 1; } // create an explosion at it's last position once it's gone: instance_create(qx, qy, SmallExplosion);
Overall, wait
is extremely useful for writing sequentially executing code without hassle.
Fork-instruction
Another notable language feature is the fork
instruction.
fork()
acts like a function that, when called, will create a copy of the currently running script. The copy will have it's own (copied) local variables, but will share global variables, arrays, and game context with the original; The copy will execute it's script and finish while the original will continue it's own way; The function returns true
to the copy and false
to the original. For a more visual example,
if (fork()) { trace("fork"); } else trace("orig"); trace("post");
This would output
fork post orig post
since the copy executes before the original script resumes. If you were to add a wait
call into the copy however, the original would resume as soon as the copy pauses:
if (fork()) { trace("fork"); wait 1; } else trace("orig"); trace("post");
which would output
fork orig post [from orig] [1 frame pause] post [from fork]
The most common use case scenario for fork
is executing something involving wait
without interrupting the original program. For that you would insert an exit
statement in the end of fork
's branch to prevent it from executing the rest of the script. For example,
if (fork()) { wait 10; trace("fork"); exit; } else trace("orig"); trace("post");
which would output
orig post [from orig] [10 frame pause] fork
So, if you wanted to give the earlier shown explosive bullet weapon to also have triple-shot (each projectile being tracked and exploding, obviously), you could do that like so:
motion_add(gunangle, -4); weapon_post(6, -7, 5); for (var i = -15; i <= 15; i += 15) if (fork()) { var qx = x, qy = y; var q = instance_create(qx, qy, HeavyBullet); with (q) { team = other.team; motion_add(i + other.gunangle + random_range(-5, 5), 1); friction = -0.8; image_angle = direction; } while (instance_exists(q)) { qx = q.x + q.hspeed; qy = q.y + q.vspeed; wait 1; } instance_create(qx, qy, SmallExplosion); exit; }
To summarize, fork
compliments wait
and even further extends what you can do with it.
In conclusion
While scripting language is overwhelmingly compatible with GML and you can write the code exactly the same way as you would in GameMaker, the new tricks that it brings can be used to do tasks even faster and easier.
Updated