GML Simulating Custom Variable Types



GM Version: GameMaker Studio 2
Target Platform: ALL
Download: N/A
Links: N/A

Use arrays and macros to construct an easy-to-use and robust system for dealing with arbitrary collections of information.

In GML, all of our variables are either real numbers or strings, but it is quite often the case that we are better served by treating variables in other ways, by thinking of variables as entities rather than values. The simplest example of this are the keywords true and false which allow us to simulate boolean variables, or any function that creates something and then returns an id. Setting an empty variable to noone instead of -4 is another example of simulating an "id" variable type to make things easier on ourselves when keeping track of our code. But why stop there?

To begin with, to keep things as simple as possible, our example use-case will be the simulation of a point variable type. In practice, setting up point variables is not usually worth the front-loading, but the intention of this tutorial is to demonstrate a concept that can be applied to any collection of information.

So. First, we set up a macro for the type itself. You can use any number you want, here, as long as it is a unique value. I'm using a symmetrical three-digit number because I find that aesthetic.
#macro POINT 282
When we create an array, the first entry will always be the data type.
#macro IS 0
Then we set macros for the components of the collection of values. These must be numbered from 1 in ascending order.
#macro X 1
#macro Y 2
Note that the macros are optional, but this whole thing is about improving code readability, so it would be a bit incongruous to skip that step.

Next, we create a script that packs up this information into an array. But we don't just want to pack this information into an array. We want to vet it as thoroughly as we can, first, and make sure that what we're packing up is actually a valid point.
("Valid" here meaning whatever you want it to mean. In this example, it means "in the room bounds".)
/// @description point_create()
/// @param x
/// @param y

// Set Defaults
var type = POINT;
var px = 0;
var py = 0;

// Get Arguments
if (argument_count > 0) px = argument[0];
if (argument_count > 1) py = argument[1];

// Vet Data
if (px < 0) px = 0;
if (px > room_width) px = room_width;
if (py < 0) py = 0;
if (py > room_height) py = room_height;

// Pack Up the Data
var data;
data[IS] = type;
data[X] = px;
data[Y] = py;

// Return the Point
return data;
So, now we have a script that stores a point in an array.
p_1 = point_create(512, 256);
What do we do with it?

We abstract everything it applies to that we expect to be using it for. In practice, a point isn't much easier to deal with than a pair of coordinates, but one of the biggest advantages to this methodology is built-in error-proofing, which will become very important when working with larger entities. That is why the array has that first entry.

/// @description point_move()
/// @param point

var point = argument0;

if (!is_array(point)) return false;
if (point[IS] != POINT)
    show_debug_message("Error, Variable passed to point function is not a Point.");
    return false;

x = point[X];
y = point[Y];

return true;
There is one sort of situation in which having simulated a point variable type saves you a line of code, and with more complex types, within which you might be nesting arrays several layers deep, the same principle applies and can often save you whole pages of code.
target_point = point_target(distance, direction);
/// @description point_target()
/// @param distance
/// @param direction

var dist = argument0;
var dir = argument1;

var tx = x + lengthdir_x(dist, dir);
var ty = y + lengthdir_y(dist, dir);

var tp = point_create(tx, ty);

return tp;
So, that's the concept.

I reiterate that actually doing this for points is impractical and highly redundant in most cases, but you can use the basic template to set up handling of arbitrary collections of information, from items in an RPG, to entire units in an RTS, to anything else, if you deem the front-loading effort worth the later convenience.
Ultimately, that trade-off is yours to make.