OFFICIAL Best Practices GML (Follow up AMA#2)

rmanthorp

GameMaker Staff
Admin
GameMaker Dev.
Hello, today we've got another remasted tech blog covering Best Practices Coding in GameMaker. You can read it over on our tech blog:

https://www.yoyogames.com/blog/63/best-practices-when-coding-in-gamemaker-studio-2
In this article we are going to cover some "best practices" for when you are using GameMaker Language (GML for short) to code your game, and at the same time explain a little bit about the inner workings of GameMaker Studio 2.

To follow this up, we are running another AMA with a focus on GML best practices. However, if you have a broader GML question we might also pick that up.

If you have a question for Russell and the core tech team, please respond in this thread and preface your question with 'GML QUESTION:'. As we did the last time, we will be collecting the questions and returning with a follow-up post containing the answers.

Thank you!
 
G

Guest

Guest
GML QUESTION: An ancient dispute: to make a colored rectangle, is it more efficient to use the draw rectangle function or to stretch a one-pixel sprite? If the latter, does it negate the efficiency by using a white one-pixel sprite and coloring it with the draw sprite function?
 
Last edited by a moderator:

FrostyCat

Redemption Seeker
GML QUESTION: I have a DS Grid I need to export to json. What is the best practice for handling that?
The answer to this question is actually not as straight-forward as most GMC responders around here may think.

To start, here is the standard form that most GMC responders would attest to (assuming that grid is pre-initialized):
Code:
var json_data = ds_map_create();
json_data[? "grid_data"] = ds_grid_write(grid);
var json = json_encode(json_data);
Code:
var json_data = json_decode(json);
ds_grid_read(grid, json_data[? "grid_data"]);
Most people would say this is a universal solution, but I know better than that. I know that on the HTML5 export, ds_*_read() and ds_*_write() functions work in a different format than all other exports. So while the form is fine and dandy if you don't ever touch the HTML5 export or work in a cross-platform capacity, if you do touch it in a cross-platform manner it's far from fine. An example of this would be JSON data added as Included Files, or JSON data uploaded online and downloaded to clients on a mix of platforms.

The cross-platform alternative would be this, which works in pure JSON space (i.e. maps and lists only):
Code:
var json_data = ds_map_create();
var virtual_grid = ds_list_create(),
    virtual_grid_width = ds_grid_width(grid),
    virtual_grid_height = ds_grid_height(grid);
for (var yy = 0; yy < virtual_grid_height; yy++) {
  var virtual_grid_row = ds_list_create();
  for (var xx = 0; xx < virtual_grid_width; xx++) {
    ds_list_add(virtual_grid_row, grid[# xx, yy]);
  }
  ds_list_add(virtual_grid, virtual_grid_row);
  ds_list_mark_as_list(virtual_grid, yy);
}
ds_map_add_list(json_data, "grid_data", virtual_grid);
var json = json_encode(json_data);
Code:
var json_data = json_decode(json);
var virtual_grid = json_data[? "grid_data"],
    virtual_grid_height = ds_list_size(virtual_grid),
    virtual_grid_width = ds_list_size(virtual_grid[| 0]);
for (var yy = 0; yy < virtual_grid_height; yy++) {
  var virtual_grid_row = virtual_grid[| yy];
  for (var xx = 0; xx < virtual_grid_width; xx++) {
    grid[# xx, yy] = virtual_grid_row[| xx];
  }
}
It's more verbose to start with, but there are no HTML5 vs. non-HTML5 format differences to deal with. In addition, this format is human-readable and human-writable, unlike the binary-like format of native-export ds_*_write() and ds_*_read(). But here's the biggest question of all: Given that we're supposed to be using an engine, why does this still need to be man-handled?

YoYo should think over this issue and reconsider how to harmonize data structure serialization going forward. The current default with ds_*_write() and ds_*_read() just doesn't work in a cross-platform setup and hampers the portability of GM data. The lack of built-in nested serialization support across all data structure types is also unhelpful on many levels and need to be addressed.
 
Last edited:
M

Musehill

Guest
Thanks for the AMA. I've been using mp_grids not only for pathfinding, but also as part of a simple collision check system. I'm early in my project and haven't seen any problems with it but is there any disadvantage of getting a cell's status frequently through the mp_grid functions? As opposed to using a simple 2D array or the tile-based method mentioned in the blog?
 
H

Homunculus

Guest
@FrostyCat Although I used the "ds_list of ds_lists" workaround you proposed many times in the past to get a json friendly output, I am totally guilty of suggesting ds_grid_write as a solution. I agree with you that this should be the proper answer, but I want to stress how bad it is that the default serialization methods are handled differently depending on the platform.

It also bothers me that the output is some kind of obscure format that only GM uses (as far as I know), it may be useful for as a very basic form of obfuscation but it makes no sense to use it as such.
 
I

iRhymeWithRawr

Guest
GML QUESTION:
I have a question about a limitation of GML. If I create a constant (e.g. #macro c_black32 make_colour_rgb(24,20,37)) GM will still error if I use it as a case for a switch statement.

E.g:

Switch (color) {
case c_black32:
dothing()
break;
}

will throw an error stating that you can only use constants as cases. However, I was under the impression that macros were constants. Am I wrong or is this an oversights?
 

Cpaz

Member
GML QUESTION:
I have a question about a limitation of GML. If I create a constant (e.g. #macro c_black32 make_colour_rgb(24,20,37)) GM will still error if I use it as a case for a switch statement.

E.g:

Switch (color) {
case c_black32:
dothing()
break;
}

will throw an error stating that you can only use constants as cases. However, I was under the impression that macros were constants. Am I wrong or is this an oversights?
Macros are actually not constants. Macros are a snippet of code consolidated to a single keyword. At compile time the keyword is replaced with the snippet.
So in terms of usage it's kinda like a constant. But ultimately, they're pretty different.
 
Last edited:

GMWolf

aka fel666
@iRhymeWithRawr
To elaborate on @Cpaz s answer, macros are pretty much like a find and replace functionality.
If you have '#macro foo bar' any instance of 'foo' will get replaced with 'bar'.
Apparently this isn't exactly what really happens (something to do with it being done at the AST level) but in my experience that is how it behaves.

That means you can even have part of statements as part of your macro.
For example:
Code:
#macro testXGreaterThan if x >

testXGreaterThan 5 {
  //Do something
}
Expands to
Code:
if x > 5 {
  //Do something
}
Of course I don't recommend you do this, it's pretty horrendous code. But it demonstrates how macros are not exactly constants.

This makes macros more powerful as you can use them to define often used code.
For example
Code:
#macro RANDOM_COLOUR make_colour_rgb(random(255), random (255), random (255))

image_blend = RANDOM_COLOUR;

Macros not being constants does mean you need to take more care designing them.
Consider the following:
Code:
#macro sum1and2 1+2

X = sum1and * 3;
You may think the result would be 9. But in fact you will get 7. That is because the macro is first expanded, then evaluated. So you get 1+2 * 3. Which is equivalent to 1 +(2*3). Which is 7.
So instead, define your macros with perens:
Code:
#macro sum1and2 (1+2)
Of course all of this is not super well defined and could change at any moment. Perhaps even now what I wrote doesn't hold up any more. So try to keep your macros simple!
GML QUESTIONS:
1. Did I get this right?
What is up with the AST level substitutions, and what does it mean for out macro definitions?
 

Nux

GameMaker Staff
GameMaker Dev.
Macros are the most powerful tool in GML, in my opinion. There are some really cool things you can do with them, such as: overriding existing built-in functions, scripts, and variables/constants:
Code:
#macro show_debug_message scr_log
/// @desc Writes this message to a file.
/// @param msg {String} The message to write.
var file = file_text_open_append("log");
if (file != -1) {
    file_text_writeln(file);
    file_text_write_string(file, msg);
    file_text_close(file);
}
This will make all your show_debug_message calls be routed to this new script, which writes that message to a file instead of the console. Pretty amazing stuff.

Another thing which I've found useful is overriding mouse_x and mouse_y to add gamepad support to a mouse cursor, without having to rewrite all your code to support that.
 
GML QUESTION: How expensive is creating and destroying objects in game. For single use instances such as bullets or FX is it better to destroy the instance or deactivate it. Should you create all required instances at room start and just deactivate/activate or recycle as needed. I know every situation is different but the garbage collector is a black box so I'm not sure which is best.
 
Last edited:
Top