GameMaker Studio 2.3: New GML Features

Now that the 2.3.0 Beta of GameMaker Studio 2 is available, you will be able to see there have been a number of significant changes and additions to the GameMaker Language, as well as a number of changes and additions made to the IDE. In this tech blog we'll be going over the changes to GML and we'll give a brief overview of the new language features available. In a follow up tech blog we'll cover the changes to the IDE.
ARRAYS
The first change to the GameMaker language that we want to discuss is a change to how arrays work. Previously, GML only permitted 1D or 2D arrays, eg:
array_1d[0] = "hello"; array_1d[1] = "world"; array_2d[0, 0] = "hello"; array_2d[0, 1] = "world";
However, this is no longer the case and now all arrays are 1D. So how do we create a 2D array now? Well this is done by chaining arrays together to essentially create arrays within arrays. Here is a small example:
array[0][0] = 1; array[0][1] = "hello"; array[0][2] = 55.5; array[1][0] = sprite_index; array[1][1] = "world"; array[1][2] = -67.89;
The above is now a multi-dimension array with 2 dimensions to it, but you are no longer limited to just 2 dimensions and can create arrays of 3 or 4 or more dimensions as required, eg:
array[0][0][0] = 1; // A three dimensional array
This change means that all platforms will now work the same (including HTML5) and enables much larger arrays, both in number of items as well as number of dimensions. The change also means that the following functions have been deprecated:
array_length_1d() array_length_2d() array_height_2d()
And the following new functions have been added:
array_length(array) array_resize(array, new_size);
Note that the old functionality (and the deprecated functions mentioned above) will still work but under no circumstances should new projects use the old syntax, as these will eventually be removed from the language.
CHAINED ACCESSORS
Another change to GML is the ability to chain accessors. Much like you can now chain arrays together, you can chain the different Data Structure accessors together too, making accessing the data from nested structures much easier.
A simple example of this would be a DS grid, where each grid entry is a ds list. Previously you'd have to first retrieve the index of the list from the grid, and then access the data in the list, but now it's as simple as doing something like this:
variable = grid[# 0, 0][| 5];
This would retrieve the data from the 6th position in the DS list that is being held in the grid (0, 0) position. Other examples of use are:
// Access a grid that has been added to a list that is part of a map: var _a = data[? "lists"][| 0][# 0, 0]; // Access an array nested in a list from a function and modify it: data[| 0][@ 10] = 100; // Access a map nested in a grid nested in a list nested in an array: data[0][| 10][# 3, 4][? "key"] = "hello world";
FUNCTIONS, SCRIPTS AND METHOD VARIABLES
Above we outline some changes to the GameMaker Language, but now it's time to talk about the good stuff... what's been added! To start with we want to talk about in-line functions and scripts. Previously, a script was a single resource that was created on a global scope and used to create a single custom function which would then be called using the script name as the function name. This is no longer the case and scripts, while still global in scope, are now a type of "container" for one or more functions.
What this means is that when you compile your project, scripts are no longer called individually, but are actually all run at the start of the game in the global scope, so all variables that are defined in the script (outside of function definitions) will be considered global. That said, you should still use the
global
The reason for this change is that scripts can now contain multiple functions. If you've only used GML to code projects then the idea of functions may be something new to you, but think of them as being the same as scripts, only explicitly assigned to a variable. This variable is called a method and is used to call the code that is in the function, the same as you would have previously called the script name. This is easier to visualise with an example, so let's look at one. Consider this simple script
move_follow()
/// @function move_follow(_object, _speed); /// @param {index} _object The Object to follow /// @param {real} _speed The speed to follow at var _o = argument0; var _s = argument1; if (point_distance(x, y _o.x, _o.y) > 0) { direction = point_direction(x, y, _o.x, _o.y); speed = _s; } else speed = 0;
Now, however, this would be defined as a function like this:
/// @function move_follow(_object, _speed); /// @param {index} _object The Object to follow /// @param {real} _speed The speed to follow at function move_follow(_object, _speed) { if (point_distance(x, y _object.x, _object.y) > 0) { direction = point_direction(x, y, _object.x, _object.y); speed = _speed; } else speed = 0; }
or like this:
/// @function move_follow(_object, _speed); /// @param {index} _object The Object to follow /// @param {real} _speed The speed to follow at move_follow = function(_object, _speed) { if (point_distance(x, y _object.x, _object.y) > 0) { direction = point_direction(x, y, _object.x, _object.y); speed = _speed; } else speed = 0; }
You would call this function just as you would have called the script:
move_follow(obj_Player, 5);
So, as we mentioned above, scripts can now contain multiple functions and these should be defined with the format shown above where you have the
@function <name>(<arguments>)
Above, we briefly mentioned that functions as variables were called method variables, but it should be explained in a bit more detail. Basically, when you create a function with a variable like this, you are creating a method variable, and they can have different scopes. Consider this function:
foo = function() { ... }
In the case of a script a variable called "foo" is declared at global scope
If the function is declared in an event then the variable "foo" is on the instance that ran that event
If the function is declared in an event using the
keyword then the variable "foo" is local to the event onlyvar
If "foo" is declared inside a
then it is declared on thewith
that is active at that timeself
This means that if you have some code that you need to use only within a loop in an alarm event (for example), then you can define it as a local scope method variable at the start of the event and then use it within the loop without the need to clutter up the script assets with it.
It is worth noting that while the variable will be in the chosen scope, the actual function will be bound to the scope that it was initially defined in. Going back to script functions, these are all global scope and are considered "unbound" (ie: they are not associated with any instances), but if you have a script function that creates another function within it and then you call this script from an instance, the function used within the script will be bound to the instance. In general this is not something you ever need to think about but for more complex operations with method variables it's worth taking into consideration. This also applies when using other constructs like
with
with
The following new functions have also been added to deal with methods:
is_method(variable); method(instance_id, function);
Check the manual for details fo how these functions should be used.
STRUCTS
The next new feature that we want to discuss in GameMaker Studio 2.3 is the creation of structs. A struct is - to put it simply - a variable that holds a collection of other variables - you can think of it as a kind of "light-weight object". The variables that a struct holds can be of any data type and these variables can be read from and written to after the initial struct declaration, and you can also add more variables to a struct after it has been declared. It should also be noted that the contents of a struct are independent of the instance or script that created it, and as such you can - if you wish - use the built-in variable names like
image_index
x
y
<variable> = { <variable> : <value>, <variable> : <value>, etc... };
So, an example of this in practice would be:
mystruct = { a : 20, b : "Hello World" };
The above creates an instance scope struct in the variable "mystruct" and populates it with some values (structs can be created at local, instance and global scope, just like any other variable). Note that you don't have to populate the contents of a struct when it is created initially and you can create an empty struct by simply doing this:
mystruct = {};
This struct can then be added to at a later point in the game code. Here is an example of a struct with various variables and data types:
var _xx = 100; mystruct = { a : 10, b : "Hello World", c : int64(5), d : _xx + 50, e : function( a, b ) { return a + b; }, f : [ 10, 20, 30, 40, 50 ] };
You'll notice in the above code that you can also define methods and use runtime functions in structs, and that you can use expressions consisting of any variable previously defined within the context of the struct itself, as well as any variable defined within the scope of the struct itself.
Once a struct has been defined, you can access the data within using the "point" notation, like this:
mystruct = { a : 20, b : "Hello World" } mystring = mystruct.b + string(mystruct.a);
You can also perform operations on the variables within a structure or use them in functions, just as you would any other variable. For example:
mystruct.a += 1; mystruct.b = mystruct.a + 20; mydir = point_direction(mouse_x, mouse_y, mystruct.xx, mystruct.yy);
Finally, structs can have other structs nested inside of them, like this:
mystruct = { a : { aa : "This is an example" }, b : { bb : "And another one" }, };
To access such nested structs you would still use the point notation, like this:
var _str = mystuct.a.aa + " " + mystruct.b.bb; show_debug_message(_str);
When a struct is no longer required it can be removed from memory using the
delete
Also note that structs can be created using functions, which requires the use of the
new
constructor
Vector2 = function(_x, _y) constructor { x = _x; y = _y; static Add = function( _other ) { x += _other.x; y += _other.y; } }
Here we are creating the function
Vector2
constructor
v1 = new Vector2(10, 10);
Now the variable v1 will reference a struct with the variables x and y and the static method variable
Add
Note that there are other additional functions for structs, specifically:
instanceof(variable); is struct(struct_id); variable_struct_exists(struct_id, variable); variable_struct_get(struct_id, variable); variable_struct_set(struct_id, variable, value); variable_struct_get_names(struct_id); variable_struct_names_count(struct_id);
Check the manual for details of how these functions should be used.
EXCEPTIONS, TRY, CATCH AND FINALLY
The final new language feature that has been added in the 2.3 update that we want to discuss here is the ability to control to a much greater degree how errors are handled when they are encountered in your code as well as the ability to generate your own error messages.
To start with we have the new
throw
throw <expression>;
The expression used can be a value or a string or any other data type, and this will then generate an exception error which is - by default - shown on the screen, and on closing the error message the game will end. For example calling this:
throw "Hello World!";
will cause the following unhandled exception error to be shown:
This is the default way the error can be handled, but you can "take over" this error message and use your own handler code by calling the new function
exception_unhandled_handler()
Apart from the ability to throw your own errors and handle them using functions, we also have the new
try
catch
finally
At it's most basic the
try
try { <statement1>; <statement2>; etc... }
However, having a
try
catch
try { <statement1>; <statement2>; etc... } catch(<variable>) { <statement1>; <statement2>; etc... }
What
catch
try
{ message : "", // a string that is a short message for this exception longMessage : "", // a string that is a longer message for this exception script : "", // a string that describes where the exception came from stacktrace : [ "", "" ], // an array of strings that is the stack frame the exception was generated }
A simple example of use is shown below:
var a = 0, b = 0, c = 0; try { c = a div b; } catch( _exception) { show_debug_message(_exception.message); show_debug_message(_exception.longMessage); show_debug_message(_exception.script); show_debug_message(_exception.stacktrace); }
It may be that you want to run some code regardless of whether an exception was thrown or not, and so for that you can add in a
finally
finally
finally { <statement1>; <statement2>; etc... }
It is worth noting that you can have any combination of these together, ie:
try
finally
try
catch
try
catch
finally
finally
break
continue
exit
return
SUMMARY
Well, we've come to the end of this rather long blog post, and as you can see we've been working hard to improve the GameMaker Language and bring it up to par with other more common programming languages, We hope that these new features are of use to you, and that they help your workflow and enable you to make bigger and better games! Note that all the features discussed here are available from GameMaker Studio 2.3 onwards, and we recommend that you read the manual fully before using any of them, as there are details and nuances related to their use that we just haven't got the space to talk about here. Also note that the 2.3 update includes a number of changes and improvements to the GameMaker Studio IDE which we'll discuss a separate blog post.
Happy GameMaking!
