GML Programming Patterns: Classes: Using Arrays and Objects for OOP Paradigms

Part 2 of GML Programming Patterns Series

A complaint often made about using GameMaker Studio is that the language doesn’t include built-in support for Objects in an OOP sense. Now, if you’re not quite familiar with Object-Oriented Programming, you might want to do a little reading up on it before continuing, but this article still may give you some ideas on better ways to organize your own code.

Motivation

The most important question to ask yourself when you’re looking at this is, “What do I want out of classes? Why do I want to use them?” My answer might not be your answer, and that’s okay, but my motivation is primarily: Modular Code.

I try to adhere to the Single Responsibility Principal as much as possible to keep things neat and clean. It also makes it much easier to try to keep dependency on other “classes” or bits of code low so that the “modules” of code can be picked up from one project and dropped in another with minimal conflict or need for additional classes to come with it. All of this effort is to fulfill two purposes… the code should be easily re-usable (throughout multiple projects, if applicable) and it should be easy to maintain. If a class only has one responsibility it prevents it from getting too large and out of control… in GML in particular, I find myself getting buried under objects and scripts fairly easy, so anything to keep things small and neat helps my headspace.

So, with these goals packed up and ready to go, I’ve found two approaches that work fairly well. Each has their strengths and weakness, so I mix-and-match depending on my situation.

Objects as Classes

If you’re familiar with OOP already, it’s pretty easy to draw the parallel between GameMaker Studio 2‘s object resource and the OOP concept of a class. Objects have instance variables, which resemble members of a class, and they have events, which can resemble member functions. While there are a limited amount of User Events you can tack onto an object, there’s really not much else for customizing your own “member function” within the object definition. To provide more “member functions” you can write script_functions that are expected to be called by an instance of the object, or using the with() keyword:

1
2
3
4
var _enemy_instance = enemy_create(); // enemy_create calls a form of instance_create
with (_enemy_instance) {
enemy_set_target(_player);
}

This syntax allows object functions to just call script as if they’re built-in “member functions” of the object, but for external callers, the syntax is weird a bit of “tribal knowledge” in that you have to know to use with. Well-documented code definitely helps, but I personally dislike doing anything self-documenting code can’t handle.

My favorite method of doing this is to pass the instance as the first parameter of the function:

1
2
var _enemy_instance = enemy_create(); // enemy_create calls a form of instance_create
enemy_set_target(_enemy_instance, _player);

Doing it this way, you run into the situation of the instance calling these functions with id. Since most of the functions are going to use with internally with the passed instance you’re redundantly withing yourself. I do wonder if GML optimzes this out in YYC, I’ve never tested.

Any of these methods work, so whichever fits your coding style best is one to try working with but REMEMBER - it’s important that you establish some sort of standard for whether you’re expecting to pass the instance or call the functions with it, and to remain consistent with it. Otherwise, you’ll end up in a situation where you’re not quite sure how to use your own code, and you’ll need to be looking up your script code just to remember how it expects things to be called. A headache, tedious, and sometimes even overwhelming.

So that all sounds good, right? Pretty much got yourself a class definition through objects that you can instantiate with instances… it’s a nice little package, and definitely worth it if you need a class with a step hook, a draw hook, or maybe a clean up hook.

Since I do have a drive for modular code, I’m particularly bothered by how “far apart” GameMaker Studio 2 places objects and scripts… I’d love to nest the “object scripts” with the object itself. You CAN create custom views and place them together which is nice, but the feature has been a bit too cumbersome for me to work it into my workflow… so I’ve come up with a different sort’ve work around though I prefer it - it keeps the objects guts in the script resources. Instead of writing code in the events, I just call __system_name_event_create etc, and write that function. It helps keep all of the code together in the tree that you stubbornly won’t make a custom view for.

There are some downsides to using objects however:

  • Events have a bit of an overhead over just a script_execute call, so I shy away from using the user events (in fact, I’ve never used alarms either).
  • With an object comes all of its instance variables and whatever overhead is gained by it existing on lists. You can deactivate the instance but if you do remember that you lose the ability to use with with the instance, and it’s essentially “off the grid” losing much functionality.
    • (by lists, I mean, for each Step/Draw event, there is most likely some overhead to iterate over the object, even if you’re not necessarily using all the events)

Arrays as Classes

Another option when deciding how to create your classes is to use a combination of an array and an enum. I tend to favor this method myself. Arrays are quick and easy to make, and fast to access their element values. With some practice, accessing members via array elements eventually becomes a second nature.

In order to define a class, use an enum, ending it with Num so we know how many members our class has. We can then use array_create to instantiate the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum ERect {
L = 0,
T,
R,
B,
Num
};
///@function rect_create(_x, _y, _w, _h)
///@desc example function, we're not really going to do the
/// whole function header, are we?
var _x = argument0,
_y = argument1,
_w = argument2,
_h = argument3;
var _rect = array_create(ERect.Num);
_rect[@ ERect.L] = _x;
_rect[@ ERect.T] = _y;
_rect[@ ERect.R] = _w - _x;
_rect[@ ERect.B] = _h - _y;
return _rect;

This example will get us a nice rect_create function that creates the array object and returns it. While you can’t write actual member variables, you can fake “member functions” for these array objects by passing the array object as the first parameter - mimicking some prototype class based languages such as Lua:

1
rect_grow(_rect, _new_width, _new_height)

Since arrays are passed by reference, you do need to be careful for subtle bugs when you forget to use the accessor operator (_rect[@ ERect.L] vs _rect[ERect.L]), and you’ll need to write your own dedicated cloning functions if you want to be able to copy your classes. Generally, array_copy is ok, but remember your deep copies!

Using array objects like this allows you to keep them entirely within a script resource, organized in their own group as you’re wont to do. This satisfies the drive for modular code for me much more than an object with scripts that are rather detached from it…

Some additional downsides of using arrays include:

  • Arrays don’t have events, obviously. If you need to clean up your object when it’s finished with, you’ll need to manually script some mechanism to handle this situation.
  • Arrays can’t be iterated using with
  • Arrays can’t be the target of any of the collision functions (i.e., remember, you’ll lose place_ functions).
  • Arrays re-allocate on resizing - it shouldn’t be too much of an issue since you should be creating your arrays with the fixed size from its enum.

So Which Should I Use?

Depending on the situation, I’ll use both of these types of classes. If “class” needs any sort of collision or drawing, I’m really talking about an object… and I’ll heavily consider using an object as a class in this situation. If it’s a more lightweight feature, most likely a mixin, then I’ll use an array object. Array objects are generally more suited for small, but plentiful “tasks” or bits of data.

Advanced Topic: Mixins

Woah woah, “mixins?” You’re just gonna drop that one in there casually? Alright - so one final trick that I love to do that I wanted to mention was the concept of mixins. Basically, a set of functions to add functionality to another class or, basically, object.

The method of implementing mixins in GML that I use is simply writing a set of similar script functions, giving them an init and destroy set of functions to be called in an object’s create and clean up events (or event scripts tee-hee). For instance, I commonly use a mixin I call mover to give basic movement to instances everytime I start a new project (it’s much better than rewriting it for the 90th time). It’s got a set of functions like this:

1
2
3
4
5
6
7
8
///@function mover_init();
__mover_start_x = 0;
__mover_start_y = 0;
__mover_target_x = undefined;
__mover_target_y = undefined;
mover_step();
mover_destroy();

The destroy function is useful if your mixin needs to use datastructures and needs to clean them up… and you can also give your mixins a step or whatever hook you need since they’re technically living on an object.

If you’re going to use mixins, it’s another thing to consider standardizing - since mixins declare their own instance variables in this method, you’ll want to make sure you have some sort of system of prefixing them as to not conflict with normal instance variables… you’ll notice I threw in the __mover variables as one example of a way you can prefix them, borrowing the __ of private functions from the naming standard.

What sorts of things can I do with mixins?

I use mixins in a manner that I’d call loose mixins. The primary thing they do is instrusively attach to the instance they’re init’d to, in that the programmer using the mixin has an understanding that some instance variables are going to be used up, and they’ll most likely be prefixed with the mixin‘s name. With this, mixins can be individual components that require things like a tick (or a step as it is in GML) without having to be their entirely own object.

A couple of examples for things I’ve used:

  • the mover mixin: mover has several functions like mover_move_to which, when used in addition of mover_step will move the instance to the position. it includes easing options and speed options, as well as some light optional 2d side-view physics .
  • the animator mixin: animator_set_animations takes a map of EAnimationData array objects, using either strings or enums as keys. animator_play allows the instance to track which animation is playing, query if an animation has completed, as well as tracking an animations position / looping via animator_step