csanyk.com

video games, programming, the internet, and stuff

Tag: GML

A tale of two GML scripts: code optimization through iterative development

Today I wanted to share two versions of a function that I wrote, in order to show how my iterative approach to software development works when I am doing code optimization to improve performance.

This example comes from my iMprOVE_WRAP asset. It’s a function that returns the shortest distance (taking into account the wrap capabilities of the calling object) between the calling instance and a target object.

The first implementation works, in that it correctly does what it’s supposed to do, but I never released it, because I wasn’t satisfied that it was good enough code to ship.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
///iw_distance_to_object(target_obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v,)
 
///@description Returns the distance_to_object from an improve_wrap object calling this function to another instance. 
///Compares all relevant points for the iw_object and returns the nearest distance, taking the wrap range into account.
///@param target_obj id of the target object to determine the distance to.
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)
 
 
//get the distance from the nine virtual positions
//return the shortest distance
var obj = argument[0];
var iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
    iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right;
var tempx, tempy, shortest;
var x1, y1, x2, y2, range_width, range_height, do_wrap_h, do_wrap_v;
 
//keep track of original location of target object
tempx = x;
tempy = y;
 
//set up wrap range
x1 = min(argument[1], argument[3]);
y1 = min(argument[2], argument[4]);
x2 = max(argument[1], argument[3]);
y2 = max(argument[2], argument[4]);
range_width = x2 - x1;
range_height = y2 - y1;
 
do_wrap_h = argument[5];
do_wrap_v = argument[6];
 
//check distances
//check center
iw_distance = distance_to_object(obj);
 
if do_wrap_h && do_wrap_v //wrap vertical and horizontal
{
  //check corners
  x = tempx - range_width;
  y = tempx - range_height;
  iw_distance_up_left = distance_to_object(obj);
 
  y = tempx + range_height;
  iw_distance_down_left = distance_to_object(obj);
 
  x = tempx + range_width;
  iw_distance_down_right = distance_to_object(obj);
 
  y = tempy - range_height;
  iw_distance_up_right = distance_to_object(obj);
 
  //check left and right
  y = tempy;
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);
 
  //check up and down
  x = tempx;
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);
 
  shortest = min(iw_distance, iw_distance_up, iw_distance_down, iw_distance_left, iw_distance_right, 
                iw_distance_up_left, iw_distance_up_right, iw_distance_down_left, iw_distance_down_right);
}
if do_wrap_h && !do_wrap_v //do_wrap_h
{
  //check left and right
  x = tempx - range_width;
  iw_distance_left = distance_to_object(obj);
  x = tempx + range_width;
  iw_distance_right = distance_to_object(obj);
 
  shortest = min(iw_distance, iw_distance_left, iw_distance_right);
}
 
if do_wrap_v && !do_wrap_h //do_wrap_v
{
  //check up and down
  y = tempy - range_height;
  iw_distance_up = distance_to_object(obj);
  y = tempy + range_height;
  iw_distance_down = distance_to_object(obj);
 
  shortest = min(iw_distance, iw_distance_up, iw_distance_down);
}
if !do_wrap_h && !do_wrap_v
{
  shortest = iw_distance;
}
 
//return calling instance to original location
x = tempx;
y = tempy;
 
return shortest;

Let’s take a moment to appreciate this function as it’s written. It’s well-structured, documented, and expressive. First we declare a bunch of variables, then we do stuff with the variables, then we get our answer and return it. And this gives a correct result…

So what’s wrong with the above? It’s an inefficient approach, which checks each virtual position of the wrapping object. If the calling instance wraps vertically and horizontally, it has to temporarily move the calling instance 9 times and check the distance from each of 9 virtual positions, then return it back to its original position, only to return the shortest of those 9 virtual positions.

There’s also a lot of code duplication.

Still, it’s not horrible code. But it’s up to 9x slower than the distance_to_object() function it’s based on, if you’re wrapping in both directions, which will probably be common. I didn’t think that was good enough.

Rather than check each virtual location to see which is the shortest distance, we just need to know whether the horizontal and vertical distances are more than half of the width and height of the wrap region. If they are, then it’s shorter to go around the wrap. To know this, you simply take the x and y values of the two positions, subtract one from the other, and compare to the size of the wrap range. Once you know which virtual position is the closest one, you can temporarily place the calling instance there, and use distance_to_object() to get that distance. Put the calling instance back where it was, and then return the distance.

I realized as well that depending on whether the calling object wraps in both directions, you may not need to check for a wrap shortcut in the horizontal or vertical. So we can potentially avoid doing some or all of the checks depending on whether the do_wrap_h and do_wrap_v arguments are true or false. As well, this means we can avoid declaring certain variables if they’re not needed, which conserves both execution time as well as RAM.

I usually create local script variables in a var declaration, and assign the arguments to them so the code will be more readable, but I wanted to avoid doing that so that this function could be as lean and fast as possible. This might be an unnecessary optimization, but that’s hard to predict since I have no way of knowing ahead of time how this function might be used in a future project. In a project with many wrapping instances, it could very well be called many times per step, and every optimization could be critical. Since the script is intended to be included as a function in an extension, once I have it working properly it shouldn’t be opened for future maintenance, so making the script readable is not as important. So I opted to remove the local variable declarations as much as possible and just use the argument[] variables directly.

Also, to ensure that the wrap range is defined properly, in the non-optimized version of this function, I declare x1, y1, x2, y2 and assign their values using min() and max() so that (x1, y1) is always the top left corner, and (x2, y2) is always the bottom right corner of the wrap range. Technically for this function, we don’t care precisely where the wrap range is, only what the width and height of the wrap range are. That being the case, I can further optimize what I have here, and rather than use min and max, I can just take the absolute value of the difference of these two values.

It turns out that the process I went through to optimize this function is pretty interesting, if you care about optimizing. So I’ll go into greater detail at the end of this article about the approach I took to get there. But for now, let’s skip ahead and look at the finished, optimized function. Here it is, re-implemented, this time doing only the minimum amount of work needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
///iw_distance_to_object(obj, x1, y1, x2, y2, do_wrap_h, do_wrap_v)
 
///@description iw_distance_to_object returns the shortest distance in room pixels between two objects in the wrap range, 
///taking into account the horizontal and/or vertical wrap properites of the calling object.
///@param obj the id of the target object
///@param x1 left x boundary of wrap range
///@param y1 top y boundary of wrap range
///@param x2 right x boundary of wrap range
///@param y2 bottom y boundary of wrap range
///@param do_wrap_h set whether the horizontal wrap is on (true) or off (false)
///@param do_wrap_v set whether the vertical wrap is on (true) or off (false)
 
 
if !(argument[5] || argument[6]) //not wrapping actually
{
 return distance_to_object(argument[0]);
}
else
{
 //We're going to figure out which virtual position is the nearest to measure from
 //To do that, we have to compare the h-distance and v-distance of the calling instance and the target position
 //If this distance is <half the range size, then the original position of the calling instance is closest
 //Otherwise we have to use one of the virtual positions
 //Then we're going to temporarily put the calling instance in that location, get the distance, and put it back 
 
 //arguments
 var tempx = x, tempy = y;
 
 if argument[5] //do_wrap_h
 {
   var range_width = abs(argument[3] - argument[1]);
   if abs(x - argument[0].x) > (range_width * 0.5)
   {
     x -= sign(x - argument[0].x) * range_width; 
   }
 }
 
 if argument[6] //do_wrap_v
 {
   var range_height = abs(argument[4] - argument[2]);
   if abs(y - argument[0].y) > (range_height * 0.5)
   {
     y -= sign(y - argument[0].y) * range_height;
   }
 }
 
 var d = distance_to_object(argument[0]);
 
 //return calling instance to where it was
 x = tempx;
 y = tempy;
 
 return d;
}

We don’t need to measure all nine distances to know which is the shortest; we can tell by comparing the direct distance to the size of the wrap zone — if it’s less than half as big as the wrap zone, the direct distance is the shortest. If not, then we need to wrap. We can check the x and y axes separately, and if both are called for then we can just combine them.

The second function should be much faster to execute, and uses less RAM. How much faster? Well, let’s do a test project using my Simple Performance Test and compare.

Download the iMprOVE_WRAP distance_to_object test project

It turns out that the improved code runs about 50% faster than the old code! That’s a measurable and worthwhile improvement. Although, that said, the old function ran well enough that I could have released it, and likely it would not have been a problem for many uses, particularly in Windows YYC builds.

Appendix: Optimization through Iteration

(more…)

GameMaker Studio 2 impressions: Object editor

The most notable change in the Object Editor is that sub-windows are “chained” to the main form, in what YoYoGames is calling “Chain view”.

GMS2 Object Editor

The idea is that different parts of the Object editor should all be visible, not overlap each other, connected visually.

The main Object window shows the object’s basic properties: the Name, Sprite, Collision mask, and Visible/Solid/Persistent/Physics properties, as you can see. Chained to it are the object’s Events, and the Code Editor (or DnD Editor) will be chained off of the Events sub-panel. If your object happens to be a Physics object, or has Parents or is a Parent, then the Parent and Physics sub-panels will also chain themselves to the main Object editor form.

GMS2 Object Editor chain

This takes some getting used to, and occupies quite a lot of space on screen, which for users with smaller displays can make it a problem to work with Objects inside of a Workspace.

Fortunately, Object Editor windows, like any other window, can be broken out of the main GMS2 window and maximized, to fill up the entire screen if desired. Users will either love or hate Workspaces and Chain View windows, and if you’re one of the ones who hates them, you’ll need to get used to breaking the editor out into its own window and maximizing it, as this seems to be your only recourse for now. There’s a few Preferences in the Text Editors section that will make this easier for you, should you want to configure them:

GMS2 Text Editor Preferences

The GameMaker Community Forums have been very active in discussing the UX issues created by the new UI, though, so don’t be surprised if YYG do make a few changes in future updates.

DnD or GML?

The Object Editor comes in two flavors: Drag-n-Drop (DnD) and Code Editor (GML). Which variant you get is currently determined when you create a new Project, but you can switch at any time. Most users will probably prefer to create GML projects and work in the code editor, but beginners, younger users, and non-programmers may prefer the DnD option.

Probably the most important feature of either variant is its interface for defining actions in your Object’s events.

I’ll be focusing mainly on the GML version, since that’s what advanced users will use. But briefly, Drag-n-Drop has been completely overhauled in GMS2.

The new Drag-n-Drop system

Vastly expanded in GMS2, there are now DnD equivalents to just about every function in GML. Unfortunately, this means that there are vastly more icons needed to represent all of these new DnD actions, making them harder to learn. Similar to Chinese or Japanese, where every written word has its own symbol, there’s a DnD icon for every GML function. While it’s reasonably easy to pick up a DnD library with a small number of actions, this quickly becomes unwieldy as the number of actions grows. Unfortunately I expect this will have the undesired effect of making DnD too complex to use for beginners and non-programmers, making it questionable how valuable the DnD system will be in the future. Learning to code by typing out instructions isn’t that hard, and is arguably the better way to learn in the first place. But it’s nevertheless true that for certain people, they feel intimidated by programming or typing, and an intermediary step of using DnD like “training wheels” until the new user has an understanding of GameMaker’s fundamentals and is ready to move on to GML, has been one of GameMaker’s defining features.

In GMS1.x and earlier, DnD Actions were iconographic representations of special GML functions that started with action_ for example, action_set_hspeed(number). These functions were mostly redundant, being equivalent to other GML functions and expressions, for example hspeed = number;

The action_ GML functions are obsolete in GMS2, and are no longer needed. DnD Actions can convert directly into GML with a single menu command. This is a one-way conversion, and should help users who want to “graduate” from DnD programming to GML programming. Formerly, in previous versions of GameMaker, there was no way to convert DnD to GML code, other than to manually re-write everything. If you try to convert GML into DnD, rather than a sequence of DnD actions, you’ll get your GML code wrapped up in an Execute Code DnD Action, and the Object Editor will switch to DnD mode, allowing you to continue programming with DnD actions. While not particularly useful for advanced GMS users who are already familiar with programming in GML, it’s a nice improvement to the way the DnD system works.

GML Code Editor

The new GML code editor is still somewhat rough, but shows promise of numerous improvements. Indenting is standardized, to 4 spaces per tab by default, although this is configurable, and there are subtle guidelines showing where tabs will align to in the background. Row lines are numbered, again configurable if you don’t want to see them.

GMS2 Code Editor

The most obvious difference is the new color coding for syntax. This may take a bit of getting used to, but at first I found that my code looked very rainbow-y, and I found this to be somewhat of a distraction at first, but after a few days I found that I had adjusted. Every color is customizable, if you want to bother with that.

Auto-completion and hinting is improved in the new editor. All project variables, macros, etc. are included, not just the built-in GML keywords.

GMS2 Code Editor AutoSuggest

The completion hints at the bottom of the Code Editor window are very helpful to remember all the arguments that must be provided to a function, in the right order. And for any scripts which you author, if you use JSDoc commenting, you can provide hints for your own functions as well.

GMS2 Code Editor Completion Hint

Rough Edges

Cursor navigation keys are either different from standard Windows text editors, or else not yet fully implemented. I’m accustomed to, and very reliant upon, using Home|End|Page Up|Page Down|Shift|Control|Arrows to move the cursor about the window, to select text, and for copy/pasting. In the GMS2 code editor, these keyboard shortcuts do not all work as expected, which can be pretty annoying.

In most text editors, Home and End keys will make the cursor jump to the 0th or last position in a row, or if Ctrl+Home|End is pressed, the 0th or last position in the file. Presently, Home and End do not appear to be supported at all in GMS2.

The Arrow keys move the cursor around the document one character at a time, and if Shift is held down, the characters that the cursor passes over are then selected. Holding Ctrl down will speed the cursor up, moving it a word or a paragraph at a time.

For some reason when selecting text using Ctrl+Shift+Arrow, with the horizontal arrows, the selection gets “stuck” at the beginning/end of a row, and will not advance beyond that unless Ctrl is briefly released. This is a relatively minor annoyance, but should nonetheless be corrected. Normally, Ctrl+Shift and the Left or Right Arrow key will select to the next word, and will wrap lines if it reaches the end of a line.

Up or Down Arrow will move the cursor up or down a row, and with Ctrl+Shift held down, should move up/down to the next blank line. This is standard behavior in pretty much every text editor I’ve used in Windows, or Mac OS for that matter, but it is not the behavior in GMS2 at the time of this writing. I am hopeful that this will be addressed before the end of the Beta.

But by far the biggest thing that users are complaining about in the Community Forums has been the way the IDE wastes space in its default configuration, due to the way Workspaces and the Chain View UI work. Fortunately, breaking out the Code Editor into its own, maximized window is an easy workaround to this problem, and largely addresses it to my satisfaction.

Apart from these issues, I like the new UI for the Object Editor, and the Code Editor very much.

GameMaker data structures: a cautionary tale

When you work with data structures in GameMaker, you have to be very careful with references to a data structure that has been destroyed.

When you create a data structure in GMS, the ds_*_create() function returns an integer value which is the id of the data structure. You then use this id whenever working with that data structure.

When you destroy the data structure, this frees up the memory that the data structure used, and the data structure can no longer be accessed.

If you take that id that belonged to the destroyed data structure, and test to see if the data structure exists, using the ds_exists() function, it should return false.

The operative word being “should”. It doesn’t always.

Why not? Because, the GameMaker runtime reuses the ids of data structures, and if you create a new data structure, it will hand out that id that belonged to the data structure that was just destroyed.

To illustrate the example, we’ll use a ds_stack:

var a = ds_stack_create();
ds_stack_destroy(a);
 
var b = ds_stack_create();
show_message(string(ds_exists(a, ds_type_stack)));

You would think that ds_exists(a, ds_type_stack) would return false. But it doesn’t, because ds_stack b just moved into ds_stack a‘s old address.

Apparently, the correct practice — which is not mentioned in the manual — is to immediately clear the value of a after destroying the ds_stack that it points to, like so:

ds_stack_destroy(a);
a = undefined;

If you do that, then a no longer points to an address that can potentially be assigned to new data structure, and so you’re safe, even if b has the same address as a once did.

Of course there’s still a problem if multiple variables all point to that same destroyed data structure. This is probably a somewhat rare circumstance, but could possibly by the case in some circumstances, for example if you have a global variable, or controller object, holding a data structure that is accessed by multiple instances of some common object. Keeping track of every variable that references the destroyed data structure and clearing all of them when the data structure is destroyed is not an easy thing to do.

Update: Thanks to @YellowAfterLife for the very clever tip!

In other words, do this:

//creation of stack:
a[0] = ds_stack_create();
//aliases to the stack
b = a[0];  //copy of stack id stored in a[0] -- don't do it this way!
c = a[@0]; //reference to stack id stored in a[0]
 
//destroy the data structure AND clear the variable
ds_stack_destroy(a[0]);
a = undefined; // this destroys the array a[], and therefore kills all references to the ds_stack.
 
//create a new stack, let's see what happens with our aliases
d[0] = ds_stack_create();
 
ds_exists(a[0], ds_type_stack); //returns false, because the array was destroyed.
ds_exists(b, ds_type_stack);    //returns true, but misleadingly so, because when we created d[0], 
                                //we re-used the same id that had been used by the stack referenced by a[0], 
                                //and copied to b by value. 
ds_exists(c, ds_type_stack);    //returns false, because c references a[0] rather than storing a copy of the id it had stored.
ds_exists(d[0], ds_type_stack); //returns true, because we created ds_stack d[0].

It’s debatable whether the re-use of handles like this is in fact a good practice, but at the moment it’s the way GameMaker works, and has worked, for years. But the discovery that they are re-used (and so soon after being destroyed) is a bit of a shocker.

More information.

Fun enums for game development

Most video games have a counting, ranking, or level system of some sort. It’s often good to have a thematic flavor to your counting systems. This way, rather than calling your level progression by boring old regular numbers, you can give each level number a meaningful, or flavorful, name. This can add character or meaning to your game world, or it can be used to help disguise the underlying math, resulting in a game where the underlying mechanics are masked away from the player, leading to a more mysterious experience where they need to experiment and discover. What’s cooler: a “level 3 sword?” Or “the sword of autumn?” What’s more powerful: the knight sword or the rook sword?

To that end, I thought I’d demonstrate a few example enums that you can use to spice up your game design.

ENUMerate all the things!

Alphabet {A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z}

Greek Alphabet {Alpha, Beta, Gamma, Delta, Epsilon, Zeta, Eta, Theta, Iota, Kappa, Iota, Kappa, Lambda, Mu, Nu, Xi, Omicron, Pi, Rho, Sigma, Tau, Upsilon, Phi, Chi, Psi, Omega}

Military Phonetic Alphabet {Alpha, Beta, Charlie, Delta, Echo, Foxtrot, Golf, Hotel, India, Juliett, Kilo, Lima, Mike, November, Oscar, Papa, Quebec, Romeo, Sierra, Tango, Uniform, Victor, Whiskey, Xray, Yankee, Zulu}

Army (simplified) {Private, Specialist, Corporal, Sergeant, Officer, Lieutenant, Captain, Major, Colonel, General}

Navy (simplified) {Seaman, Petty Officer, Ensign, Lieutenant, Commander, Captain, Admiral}

Chess {Pawn, Knight, Bishop, Rook, Queen, King}

Rainbow {Red, Orange, Yellow, Green, Blue, Indigo, Violet}

Elements {Earth, Air, Fire, Water}

CardRank {Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace, Joker}

CardSuit {Clubs, Diamonds, Hearts, Spades}

Zodiac {Aquarius, Pisces, Aries, Taurus, Gemini, Cancer, Leo, Virgo, Libra, Scorpio, Sagittarius, Capricorn}

Seasons {Spring, Summer, Autumn, Winter}

Months {January, February, March, April, May, June, July, August, September, October, November, December}

Weekdays {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}

Lunar Cycle {New, WaxingCrescent, FirstQuarter, WaxingGibbous, Full, WaningGibbous, ThirdQuarter, WaningCresent}

Some of these may not necessarily have a natural order to them, but might just be a set of categories which you could use to organize something in your game. That something could be worlds, or items, or creature types, or powers, or anything else you can think of.

What else?

What else is there? Probably a whole lot! You could probably make a good basis for a game system out of gemstones, or metals, or barnyard animals, or anything, really!

If you know of a set of things that would make a good enum that I’ve left out, leave a comment below.

Using enums in GameMaker Studio

Enums are covered in the manual in the article on Data Types.

The basic syntax is as follows:

First, you have to declare the enum.

enum <variable>{<constant> [= <value>]}

You create the enum using the enum keyword, naming the enum <variable>.  Then, in brackets, you list all the constants that make up the values in the enum.  You can explicitly declare values for the enum constants, or you can leave them implied, in which case GameMaker will assign them integer values starting at 0, and increasing in order through the collection. You can even use expressions rather than a static value.

To access the enum constants, you use the syntax enum_variable.constant.

In your code, there are all kinds of situations where using an enum is potentially useful.  Here’s a few things to look for in your code that might signal a good opportunity to use an enum:

Conditionals for checking state

Consider the following ways of checking the state of some object:

//check state using string matching
if state == "state_idle" {//do state actions}
//check state using literal numbers
if state == 0 {//do idle actions}
//check state using variable
state_idle = 0;
[...]
if state == state_idle {//do state0 actions}
//check state using enums
if state == enum.value {//do state actions}

Out of these, using enums is the best approach.  Why are enums better?

Conditional checking by string matching is much slower than number matching.  Your string value provides semantic meaning to the reader, making the code easier to understand when a human reads it.  But when the program goes to check the conditional, it has to check every letter in both strings to see if they match.

As well, this approach is fairly error prone.  It’s very easy to type the strings in slightly wrong. If you’re not careful, it’s easy to type one string where the state is assigned, and another string value where you’re checking state in order to do something state-specific.  If you don’t catch the error, you’ll have a hard to diagnose bug because the conditional check won’t match what you’ve set the state variable to.  One extra space, or misspelled word, or inconsistent use of capital letters, and the string match check will fail. The compiler won’t help you catch this sort of bug.

It’s a lot faster to do conditional checking via numbers.  But naming your states with literal numbers: 0, 1, 2, etc. provides no meaning.  Is 0 the idle state?  is 1 running?  is 2 jumping?  Keeping track of this stuff in your head makes your work as a programmer harder, and makes the code hard to read, and brittle. You can do slightly better by creating a named variable and assigning the number value to it. The named variable can be expressive, e.g. state_idle = 0, state_running = 1, etc.  But variables values can change, and storing each value takes a little bit of memory.  By contrast, an enum value is constant — it cannot be changed once it is declared. And enums are global, so they are only declared and set in memory once, and thus require fewer resources. Even if you have ten thousand instances that use your enum, the memory used by them is the same as if there is only once instance.

Create an enum that provides expressive labels on these values, and you have the best of both worlds: expressive code that checks conditions fast, and uses computer resources efficiently.

Iteration over a set

Conventionally, programmers often rely on conventional looping variable names such as i or j to iterate over a collection of things, such as the members of an array, or other data structure.  While this is OK, you can make your code more expressive by using enums to denote important numbers.

For example, it’s common for programmers to use a nested for loop iterating over the variables i and j to create a grid structure.  Instead of using i and j, we can use variables named row and column instead.  As we iterate over the range of rows and columns, we can use enums as flags to do something special at row (or column) == enum.constant.

Another example:

Say we want to implement an inventory equipping system for the player. The character has inventory slots for head, body, gloves, right hand, left hand, shoes.  We decide to create an array, called inventory[], and assign the equipped item in each slot.

We could simply index the array with numbers:

inventory[0] = hat;
inventory[1] = armored_breastplate;
inventory[2] = iron_gauntlets;
inventory[3] = sword;
inventory[4] = empty;
inventory[5] = double_jump_boots;

Now, it’s not too hard to tell what each inventory slot is for.  But it’s not as easy as it could be, either.  Is the sword in the left hand or the right hand?

Let’s use an enum, and make the code easier understand.

enum inv_slots{head, body, gloves, right_hand, left_hand, shoes};
 
inventory[inv_slots.head]       = hat;
inventory[inv_slots.body]       = armored_breastplate;
inventory[inv_slots.gloves]     = iron_gauntlets;
inventory[inv_slots.right_hand] = sword;
inventory[inv_slots.left_hand]  = empty;
inventory[inv_slots.shoes]      = double_jump_boots;

Now, it’s quite easy to see that the double_jump_boots are being worn on the player’s feet, rather than being carried in the player’s hands. It’s simple to keep track of which slot is which.

Creating a set of related, named values

Here’s a useful enum for direction angles for a top-down game:

enum compass {e = 0, ne = 45, n = 90, nw = 135, w = 180, sw = 225, s = 270, se = 315};

It’s much easier to remember and understand direction = compass.sw instead of direction = 225. You can use this enum for 4-direction and 8-direction movement or aiming, and you could even expand upon it if you wanted by adding constants for ene, nne, nnw, wnw, wsw, ssw, sse, and ese, like so:

enum compass {
              e   = 0, 
              ene = 22.5, 
              ne  = 45, 
              nne = 67.5, 
              n   = 90, 
              nnw = 112.5
              nw  = 135, 
              wnw = 157.5
              w   = 180, 
              wsw = 202.5
              sw  = 225, 
              ssw = 252.5
              s   = 270, 
              sse = 292.5
              se  = 315,
              ese = 337.5
              };

(I like to format my code like this; lining up the variable names, equals signs, and values makes it easy to scan down the file, and makes it clear that these values are all related.)

If I wanted, I could go a step further and use radians to make it clearer that these values are fractions of a circle:

enum compass {
              e   = 0, 
              ene = radtodeg(pi * 0.125), 
              ne  = radtodeg(pi * 0.25),
              nne = radtodeg(pi * 0.375), 
              n   = radtodeg(pi * 0.5), 
              nnw = radtodeg(pi * 0.625),
              nw  = radtodeg(pi * 0.75),
              wnw = radtodeg(pi * 0.875),
              w   = radtodeg(pi), 
              wsw = radtodeg(pi * 1.125),
              sw  = radtodeg(pi * 1.25),
              ssw = radtodeg(pi * 1.375),
              s   = radtodeg(pi * 1.5),
              sse = radtodeg(pi * 1.625,
              se  = radtodeg(pi * 1.75),
              ese = radtodeg(pi * 1.875)
             };

You can group together, and name other values that go together in a system using enums in this way, too.

Parting thoughts

Hopefully, these examples will help you see how enums can be useful to make your code more expressive, easier to read, and easier to maintain. Look for opportunities to use them in your code. They can really help!

GameMaker Studio 2 impressions: New Project

[Rather than posting a long article that takes days to organize, I’m opting to do short-form posts that focus on a narrow aspect of the new GameMaker. This means more frequent, smaller posts, which will hopefully be more timely and more digestable for readers. For more articles in this series, just follow the GameMaker Studio 2 tag.]

If I click on New Project, I have to choose between creating a Drag & Drop project or a GameMaker Language project.

GMS2: Create new project

Weird; I can’t use both in the same project anymore? [I haven’t actually created a new project yet; I don’t know. But that seems to be the implication here.]

Really, I expect that most GMS users use GML, but I’m glad that they’re keeping DnD, because for beginners and non-programmers it is much easier to learn. And it looks like they’ve really improved the Drag-n-Drop system by leaps and bounds over what it’s been up until now. (I’ll cover this in a separate post in more detail…)

But I think it’s odd that I have to pick between one or another coding system when I create my project.

Really, what I had hoped for was that there would be a “Convert DnD to GML” button that users could use, and this could facilitate learning how to code in GML by starting out in DnD, then converting to GML and seeing what it generates for you. I don’t know whether this is a feature that YYG have planned or not, if it is I haven’t discovered yet. Or, even better than a one-way conversion, YYG could have made DnD and GML completely equivalent, such that there was full coverage of the entire GML language with DnD actions, and allowed the developer to switch between views, viewing the code as visual drag and drop actions, or as GML code, and develop however they’re more comfortable at the moment.

I think this “one or the other but not both” approach could potentially cause problems, and will result in pushing users to using GML-only. When a new programmer begins to learn GML, at first they typically start out by going through a project they’ve created using DnD, and replacing the DnD actions an instruction at a time with equivalent GML. If you can’t do that in GMS2, it will make transitioning that much harder, because you would have to start a new project, and code exclusively in GML, before you’re totally ready. Rather than make a gradual transition to becoming a GML coder, the neophyte GMS2 developer will need to develop sufficient confidence in their understanding of GML to start a new project from scratch and use it exclusively.

This pretty much destroys GMS’s gentle learning curve that makes it great for first-time programmers. Update: GML-DnD conversion is exactly how it works! Right-click in the object-editor and there’s an option to convert from DnD to GML, and vice versa.

DnD to GML

GMS2 allows you to convert DnD directly to GML, and GML can be converted to DnD (it just shoves the GML code into an Execute Code DnD action, so it’s only semi-reversible).

Oddly, the DnD2GML conversion warns you that this is a one-way change, but that is apparently not the case (although converting GML to DnD simply puts the GML code into an Execute Code DnD action).

I suspect that many users look down at DnD disparagingly, but really there’s nothing wrong with using it. It’s quick, and if it’s all you need, it’s all you need. For what would be a simple, one-liner GML script, I often opt to use DnD when I’m in a hurry, because it’s expedient.

Simple GameMaker performance throttling

Here’s a quick tip for performance throttling in GameMaker.

Say you’ve got some code that you need to execute frequently, but if the game starts slowing down too much, you can live without executing this block of code.

Like, for example, say you want to spawn a new instance of some object very frequently, such as in the Step event, but if performance starts to lag you can skip it. You could try to test out how many instances the game can handle without frame rate dropping to an unacceptable level, and cap the number to something somewhat below the maximum. The problem is that this number will vary depending on the hardware. Someone running your game on an older, slower machine will not be able to sustain the same performance that someone with a brand new, high end machine. There really isn’t a true, one-size-fits-all number that works for every situation.

What you really want to do is base the cap on the current performance of the game as it’s running right now. To do that, wrap it up in an if statement like this:

if room_speed < fps
{
 //keep doing the thing that will eventually cause performance issues
}

The way this code works, as soon as fps drops below room_speed, it will stop doing the thing that contributes to the performance problem. This technique does not guarantee that fps will never drop below room_speed, but it will cause performance to stop degrading by not contributing to the problem once performance has degraded to the point that the conditional check takes the “false” branch.

If you don’t want ANY noticeable performance degradation, you may want to make the conditional check be something more like

if (room_speed + 10) < fps

instead; this will give you a little buffer to keep the fps enough above room_speed that you should not see any noticeable performance problems. Or, substitute the calculation room_speed + n with the literal value that you don’t want fps to drop below. Use this to ensure a safer margin of acceptable GameMaker performance.

GML draw_text_rtf() script enables drawing of pseudo-rich text strings

When I was working on my recent blog post on string handling and text drawing in GML, I had the idea for a function that would draw formatted text. (You can read a paragraph in that post where I complained about how difficult this is to do using the built-in GML draw_text() functions.)

I posted a feature request to the official GameMaker bug tracking site (which recently closed its submission system and now works differently, by the way). And this spurred a discussion with some of the other users on the bugtracker. A user named Miah_84 came up with a script that very nearly did everything I wanted. I made a few modifications and cleaned up the code, and present it below.

The downside of this script is that it is very slow, as it makes numerous calls to the draw_text() function, which is itself very slow, and iterates over the raw rtf string several times in order to parse it. Running the demo project on my 2.0GHz Core 2 Duo laptop with discrete graphics, it only runs at around 200fps in debug mode. Comment out the call to the draw_text_rtf function, and the frame rate jumps to about 1100fps. The more formatting in the string, the slower the function will draw.

Still, combined with surfaces, this can be an extremely useful function for displaying text to the screen, in ways that was not possible previously using native GML functions.

Discussion thread on this script at http://www.gmlscripts.com/forums/viewtopic.php?pid=3358#p3358 — I expect that in the near future this will end up as an addition on GMLScripts.com, as well.

Download draw_text_rtf.gml.zip

Demo Project RTFDemo.gmz

GameMaker Tutorial: String handling and Drawing Text

[Editor’s note: This article was written primarily with GameMaker: Studio 1.x in mind. There have been some changes to the way GameMaker Studio 2 handles strings, mainly dealing with escaping codes, and this article has not yet been updated to reflect that. Refer to the official manual chapter on Strings for all the details.]

Drawing text to the screen is a basic part of most videogames. There are a huge number of useful applications for text. Just a few of the more common applications:

  1. Score
  2. HUD/Dashboard
  3. Menus
  4. Special effects
  5. Messages and dialogs
  6. Instructions/Story
  7. Debugging/diagnostics/benchmarking — it can be incredibly useful to draw the current value of variables to the screen when debugging, or performance metrics.

Things to know about drawing stuff in GameMaker

  1. Draw functions only work in two places: In the Draw or Draw GUI Events, or when drawing to a Surface. If you try to use them anywhere else, nothing happens. If you’re drawing in the Draw GUI Event, you’ll want to be familiar with the draw_set_gui_size() function so your Draw GUI stuff will be drawn to the proper scale if you’re using Views.
  2. Drawing directly to the screen (especially text) is slow. Draw a lot of text and performance will suffer.
  3. There are ways to improve performance when drawing text. The most important of these is to use Surfaces. Surfaces are not available in the free edition of GameMaker, and not all hardware may support them. Using surfaces properly is not that difficult once you understand them, but is generally considered to be an “advanced” concept in GameMaker, and is less straightforward than drawing directly to the screen in the “normal” way.
  4. But there are challenges. Setting up a Surface for optimizing text performance is tricky because it can be hard to know in advance how large the surface needs to be to contain the text you are drawing. Fortunately, GameMaker provides some useful functions which can enable you to get the dimensions needed for the surface: string_width() and string_height(), which give you the width and height, respectively, in pixels of a string drawn with draw_text() in the current font. If you’re using draw_text_ext() string_width_ext() and string_height_ext() are the functions to use instead. These functions allow you to create a drawing surface of proper dimensions, provided you know the string and font and can decide on a width prior to creating the surface. Keep in mind that the dimensions of a string depend on the font used to display it, so always use draw_set_font() to set the font to the correct one that you intend to draw the string with before using the measurement functions.
  5. Draw settings (for things such as color, alpha transparency, and font alignment) are global in GameMaker. That means that if you have multiple objects which draw functions, and if any of them changes the color, alpha, or font alignment, all objects will be drawn using those same settings. For this reason, if you are using draw functions in your objects, it’s best to set all the draw settings in the object in order to make sure they are what they need to be. If you never change color, or alpha, or font alignment, then you don’t need to set that property before you use draw functions — but if you do need to change them for one object, it’s best to set them to what they need in the Draw event of every object, immediately before calling the drawing routines.
  6. For serious performance optimization, you need to learn how GameMaker “batches” drawing operations, and organize your code to have the least number of drawing batches as possible.

Fonts

Everyone these days knows what fonts are, right? Fonts are like the clothes that text dresses up in when it wants to go out and be seen. In GameMaker, fonts are game resources, just like sprites, or objects, or other resources, and need to be added to the project — you don’t simply have direct access to the same fonts that are installed on the system, you have to explicitly add a font to your project. If your project has no font resources set up, text drawn to the screen will still render, but oddly and probably not consistently across platforms. So, always define a font resource and make sure that it’s used if you’re drawing text.

To save space, you can define a font resource to include only certain character ranges, such as number digits only, or alphabet characters only, or only the upper case or lower case letters in the alphabet. If you know you won’t be needing certain characters, and are concerned about the size of the game when it is built, go ahead and constrain the range. Otherwise, the default range of 37-128, covering A-z, 0-9, and special characters, is good.

For legal reasons, it’s important to note that fonts are copyrighted, and most need to be licensed for commercial use. There are free fonts out there (google for them) with liberal licensing terms that you may be able to use in your project, if the terms of the license allow.

Of course, you can create your own fonts. Creating your own font is outside the scope of this article, but there are tools you can use to produce your own fonts if you’re crazy enough. It’s probably easier to simply purchase a license for a professionally designed font.

Formatting issues

Alignment

Text alignment is set using the and draw_set_valign() functions. Use GameMaker’s built-in font align constants {fa_left, fa_center, fa_right, fa_top, fa_middle, fa_bottom} as arguments to these functions to keep the code readable.

New Lines

To signify a new line in a GML string, use the pound character (#). The GML code

draw_text(x, y, "Hello#World");

would be drawn like so:

Hello
World

You can also use a literal return in your string, but it’ll make your source code look yucky.

draw_text(x, y, "Hello
World");

Would draw to the screen exactly the same as “Hello#World”.

Escape characters

If you’re familiar with strings in programming languages, you know that it gets tricky when using certain characters that are reserved for program syntax or markup. Most languages allow you to “escape” the markup syntax so that you can still use characters normally reserved for markup purposes as literal characters in a string. GML is no exception.

#

What if you want to use a # in a string, and you don’t want it to signify a new line? Use the “\#” escape character.

The string "We're \#1!" would be drawn like so:

We’re #1!

Quotes

A matched pair of quotes, single or double, can be used in GML to begin and end a string. If you want quotes to appear as text within a string, you can use the other type of quote to encapsulate them, like so:

my_string = 'This is a single-quoted string.';
my_string = "This is a double-quoted string.";
my string = 'This is "an example" of a string including double quotes-as-text.';
my string = "This is 'an example' of a string including single quotes-as-text.";

It gets tricky when you need to have BOTH types of quotes in the same sentence:

my_string = 'Bob said " We shouldn' + "'" + "t."+ '"' ; // Bob said "We shouldn't."

It looks like a mess, but you just have to do a lot of concatenation and quote your quotes with the other type of quote marks.

String concatenation

As with many languages, you can combine two strings together by adding them with the + operator. With number values + adds them; with strings, + concatenates the two strings together, creating a longer string made of the first one and second one stitched together. You can do this with literal string values, or with variables containing strings:

concatenated_string = string1 + string2;
concatenated_string = "Hello " + "World";

But if you try to add a string and a number, you need to tell the program to convert the number into a string. The string() function will convert numeric values to strings, which allows them to be incorporated into a larger string.

health = 100;
draw_string(x, y, "Player1 Health: " + string(health));

GML String functions

We’ve already introduced a few of the more commonly useful ones, but there are many other useful GML string functions. I’m not going to go into each one in depth, but review the official documentation and keep in mind that they’re out there, and can be useful.

One important thing to be aware of with GML strings is that, unlike most other languages, GML strings are 1-indexed, not 0-indexed. This means that when counting the characters that make up the string, the first character is character 1, not character 0.

GML text drawing functions

Mostly I have used draw_text() and draw_text_ext(), but it’s good to know that there are a few more variations on these basic text drawing functions.

  • draw_text
  • draw_text_color
  • draw_text_ext
  • draw_text_ext_color
  • draw_text_ext_transformed
  • draw_text_ext_transformed_color
  • draw_text_transformed
  • draw_text_transformed_color

It might seem like a lot to keep track of, but it’s pretty easy if you remember the following:

draw_text: basic draw text function.

_ext: allows you control over the line spacing and width of the draw area. This means you don’t have to manually handle line breaks by inserting # or return characters in your text.

_transformed: allows you to scale and rotate the drawn text.

_color: allows you to set a color gradient and alpha to the text.

Again, text is always drawn using the current global drawing color, alpha, halign and valign properties. It’s best to set these before drawing to ensure that they are the expected values, using draw_set_color, draw_set_alpha, draw_set_halign, and draw_set_valign functions.

Keep code clean by storing strings in variables

This is perhaps obvious, but it’s often useful to store a string value in a variable, to keep your code neater and easier to read.

draw_string(x, y, "Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.##Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battlefield of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.##But, in a larger sense, we can not dedicate, we can not consecrate, we can not hallow this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom—and that government of the people, by the people, for the people, shall not perish from the earth.");

— is a lot harder to read than:

draw_string(x, y, gettysburg_address);

— and moreover, all that text gets in the way of comprehension of what your code is doing. So use variables to store strings, and keep your code looking clean.

draw_text_ext()

While we’re dealing with a very long string, it’s a good opportunity to talk about a function that makes drawing them much easier.

You could manually set line breaks in a long string by sprinkling #’s every N characters or so, but that is laborious and inflexible. It’s better to use the draw_text_ext() function, which allows you to specify a width for the line, and (optionally) also how many pixels should separate lines.

draw_text_ext(x, y, string, vertical_separation, width);

When drawn, the line will automatically break when it reaches the width provided to the function.

Formatting

GameMaker is rather limited in its typographical capability when drawing text to the screen. GameMaker Font resources, unlike an installed font on the system, are a specific size and style only. There’s no bold or italic or other style options available that you can use to modify the font resource. If you want bold or italic, you have to create a new font resource, and use draw_set_font(font) to that resource in order to use it.

This means that if you want to use bold text in a sentence, you need to create a second font resource for the bold font, draw your normal text, then switch fonts to the bold font, and draw the bold text, *somehow* positioning the two different drawings so that they look like they’re a single block of text. You have to leave a hole in your normal text where the bold word will appear. This is *not* easy, nor is it generally recommended. If you really want it, and are masochistic enough to put yourself through the trial and error to do it, go ahead. But before too long you’ll probably realize that it’s not worth the effort.

See this script draw_text_rtf which allows you to draw rich text format, originally written by Miah_84 and improved by me.

Special Effects

Scrolling text

Scrolling text is extremely easy to do. The draw_text function must be called by some object, and includes arguments for the x and y where the text will be drawn. Simply change the x and y over time, add you have moving text. The easiest thing to do is to set the instance that is drawing the text in motion.

Typewriter text

Another easy to implement technique is “typewriter text” — that is, displaying a string one character at a time as though it were being typed out.

First, let’s take a string stored in a variable, my_string.

string_length(my_string) will give you the length of my_string.

draw_text(x, y, my_string) would draw the entire string at once. But we want to draw it one letter at a time.

The GML function string_copy(string, index, length) comes in handy here. We can use this instead of string in our draw_text function:

1
2
//In the Create Event
typed_letters = 0;
1
2
3
//In the Draw Event
draw_text(x, y, string_copy(my_string, 0, typed_letters);
if (typed_letters < string_length(my_string)) {typed_letters++};

Note that this will type at room_speed characters per second, which at 30 fps is extremely fast. You may want to type slower, in which case you can slow down the function in one of several ways. You can use an alarm to increment typed_letters every N steps, rather than increment it in the Draw event. Or you don’t want to bother with an Alarm event, you could do something like this:

1
2
3
//In the Draw Event
if typed_letters < length {typed_letters+=0.1;}
draw_text(x, y, string_copy(my_string, 0, ceil(typed_letters)));

This would give a typing speed of room_speed/10, or 1 character roughly every 0.33 seconds for a 30 fps room, or 3 characters/second, which is a bit more reasonable. You can adjust this rate to taste.

If you want the text to reset and type over again when the message is completed, you can do this:

if typed_letters < length {typed_letters+=0.1;} else {typed_letters = 0;}

Additionally, you can optionally add code to play a sound with each letter, or start a sound when the typing starts and stop the sound with the full length string has been reached.

Marquee text

The Typewriter Text technique can be modified slightly to draw a scrolling marquee:

1
2
3
4
5
6
7
8
//In the Create Event
/*Hint: you may want to pad the end of your marquee string with extra spaces so it 
will scroll all the way off your marquee.*/
my_string = "Some text for your marquee "
start_letter = 0;
marquee_length = 10; // or however many letters in your marquee
type_rate = 3/room_speed; // 3 char per second
marquee_scrolling = true;
1
2
3
4
5
6
//In the Draw Event
if marquee_scrolling{
 draw_text(x, y, string_copy(my_string, start_letter, ceil(start_letter + marquee_length)));
 start_letter += type_rate;
 if (start_letter > string_length(my_string)) start_letter = 0;
}

Blinking text

Blinking is annoying in web pages, but can be a very useful effect in games. Blinking attracts the eye, and can get attention where it’s needed. Of course, blinking can be done with any graphical element, not just text.

Blinking is just turning on the drawing and then turning it off on a cycle, using a timer, such as an Alarm Event.

1
2
3
//In the Create Event
blink = true; //(or false, if you want the initial state to be off)
blink_steps = room_speed/2; //for a 1 second blink cycle. Set this value to suit.
1
2
3
//In the Alarm[0] Event
blink = !blink; //toggles the blink from on to off or vice versa.
alarm[0] = blink_steps; //re-sets the alarm so it keeps blinking
1
2
//In the Draw Event
if blink {/*do the draw stuff*/}

The above code gives a 50% “duty cycle” (the blink is “on” 50% of the time, “off” 50% of the time). It’s possible to vary the duty cycle in a variety of interesting ways…

1
2
3
4
//In the Create Event
blink = true; //(or false, if you want the initial state to be off)
blink_on_steps = room_speed/2;
blink_off_steps = room_speed/4;
1
2
3
//In Alarm[0]
if blink {alarm[0] = on_steps;} else {alarm[0] = off_steps;}
blink = !blink;

This blink code will result in a blink that stays on for 0.5 seconds, and blinks off for 0.25 seconds.

Even more sophisticated blink periods can be achieved using math functions rather than a static value. Setting alarm[0] = irandom(10) would result in a random flicker. Think of creative ways to use other math functions to create interesting effects. If you come up with a good one, share your code by posting a comment to this article.

Yet another way to flicker or blink text is through varying alpha. Or by switching colors. Or even size.

One last way to blink is to toggle the state of the visible boolean in the instance.

visible = !visible;

One thing to keep in mind, though, is that an instance that is not visible will not be checked for collisions. Also visible applies to the entire object’s Draw event, so it is all or nothing. Still it is a simple way to draw or not draw an object.

What next?

If you’re familiar with other programming languages, you may be disappointed at the limits of the built-in functions for manipulating strings. There are a lot of things you can do more easily in other languages than in GML, unfortunately. Since GML only has two data types, strings are extremely important, but because games tend to focus more on graphics, sound, and interface, the average GameMaker developer can get by with the string functions that do exist, for the most part.

There are a number of useful GML scripts for doing more advanced things with strings that have been collected at gmlscripts.com. Many of the functions built in to more mainstream programming languages can be found there.

GameMaker tutorial: Elegant instance_change() in your state machine

In GameMaker, a commonly used technique is to build a system made up of a several objects to represent an entity in your game, such as the player or enemy, in various states, such as idle, dead, shooting, jumping, running, climbing, and so forth. This is what is known as a Finite State Machine pattern.

When the time is right in the game, we change an instance from one state object to another by using the powerful instance_change() function. Instance_change() takes the instance and transforms it into a new type of object. Its Event behaviors will change to those defined by the new object type, but its old properties (object variables) will remain the same as before, allowing the instance to retain its variables with their current values.

The instance_change() function takes two arguments: object, the object the instance will turn into, and perform_events, a boolean which controls whether the new object’s Create event will be performed or not.

Normally, the Create event is where an object initializes its variables and initiates its default behavior. When we’re dealing with a State Machine comprised of a number of objects, this can become problematic, however. Some code in the Create Event is initialization code that we may only want to execute one time, to set up the instance when a brand new instance is created, while other code in the Create event is behavioral and we may need to execute whenever an existing instance reverts back into that state again. Thus, the perform_events argument in the instance_change() function isn’t adequate for this situation — it’s too all or nothing.

For example, let’s say I have a generic object for an enemy, oEnemy. I want some visual variety to this enemy, so I’ve created a few different sprites for it. In the Create Event, I want to randomly choose one of those sprites to be the sprite for this instance. But if the instance changes into another state object, and then reverts back, if I call the Create Event, it will randomly choose a new sprite. I don’t want this, as it ruins the illusion of continuity — I need that instance to retain its sprite. But I do need the Create Event to run, whenever it re-enters this state, because I’m using it to set the instance in motion.

So, how can I elegantly select which lines of code I want to run in the Create Event?

Conditional blocks

This is the least elegant solution, but you could use if to check whether a variable exists or has a value. For example:

if sprite_index == -1 {sprite_index = choose(sprite1, sprite2, sprite3);}

This is inelegant because it adds lots of lines of code that only need to be run one time (when a brand new instance is created) but need to be checked potentially many times (any time that instance changes back into the object state). It also only checks certain, specific things, case by case. As I continue to build the state machine, I may end up introducing more features which require initialization, which would necessitate more checks, further bloating the code. I always want to write the least amount of code needed, both for reasons of performance and maintainability.

Move one-time code to an init state object.

The more elegant solution is to recognize that initialization is its own state, and we need to separate it out from the other states in the state machine. We can create an oEnemy_init object, put our one-time initialization code into it, and then the final step in the Create Event for the init object would be to change the object into the default state.

None of the other states in the state machine should put the instance back into the init state, thereby guaranteeing that the init code only executes once. Now your code is neatly separated, your states objects in your state machine are as simple as can be.

csanyk.com © 2016
%d bloggers like this: