GMS 2.3+ [TEXT TUTORIAL] Keyword "function" & Methods Explained

GM Version: Studio 2.3+
Target Platform: Windows / All
Download: n/a
Links: n/a

[THEME]
Today we will be talking about functions, so this tutorial will be about the function keyword and the method() function.
In the in-betweens, I will also talk about binding. What is it and why is it important when using functions?

Let me warn you right away, this will be a big tutorial. I'll do my best to keep it clean and simple to follow ;)

[EXPLANATION]
Lets start with the structure of the tutorial, first we will talk about functions then the concept of binding and finally the built-in GML method() function.

NOTE: The code examples throughout this tutorial will be using structs instead of objects. The main reason being they are easier to write because you don't have to deal with a lot of events.​
Though the code works the same for both structs/objects and if needed can be easily adapted. If you need any help with it, please let me know in the comments below.​

[FUNCTIONS]

(1) FUNCTION TYPES
Functions in game maker come in two flavors they can be named functions and anonymous functions:
There is nothing better than some examples for you to understand what I'm trying to explain.
GML:
// This function is an example of a named function:
function printHello() {
    show_debug_message("hello");
}
// As you can see you are using the template:
// function name(arguments) { code }


// This function is an example of an anonymous function:
printWorld = function() {
    show_debug_message("World");
}
// As you can see you are using the template:
// variable = function(arguments) { code }
As you probably might have already guessed named functions have a name associated with them and during compilation they are assigned an index whereas the anonymous one doesn't and must be stored in a variable in order to be called. Here are the rules that apply to functions types:
  • named functions are assigned an index at compile time (using typeof() on them prints number).
  • named functions cannot be re-declared within a given scope (you cannot have more than one function with the same name in a given scope).
  • named functions declared inside a script file become global functions (aka script functions).
  • anonymous functions must be stored in a variable (using typeof() on them prints method).
Now that we made a small introduction to function types lets briefly talk about global functions.

(2) GLOBAL FUNCTIONS
As stated above global functions (aka script function) are all the named functions that are declared within a script file. They are the equivalent of scripts from older versions of GameMaker Studio. A global function is a function that is available project wide meaning that once declared that can be called from wherever you want within your code.

Let's take as an example this function declared in a script file:
GML:
function printHello() {
    show_debug_message("Hello");
}
You will be able to call this function inside any object/struct/room code.

So for the ones of you that are already thinking:
So all builtin GML functions are global functions!!
You couldn't be more right :) all functions that GML provides natively are global functions.


[BINDING]

(1) WHAT IS IT?
Now it's time to complicate things a little bit more with the concept of binding. Binding is "the process that results in the creation of a link between a function and the object/struct it acts upon (method variable)".
In other words, when a function is executed, the variables it uses within its execution code belong to a given scope, we can say that the function is bound to that scope.


(2) UN-BOUND FUNCTION
The first kind of functions we will be talking about are functions that are not bound to any scope in specific.
When they execute they will just use the current scope from where they are being called.

For example, let's look at this code below (again you can place it inside an empty [SCRIPT FILE] if you want to follow along)
GML:
// This function is a global function
function printHp() {
    // Here we will print the hp variable
    show_debug_message(hp);
}

// This constructor defines a "Player" struct
function Player() constructor {
    hp = 500;

    printHp(); // We call the printHp (global function)
}

// This constructor defines an "Enemy" struct
function Enemy() constructor {
    hp = 200;

    printHp(); // We call the printHp (global function)
}

// We tell the constructor to build a new Player struct
// This will run the code inside the constructor function
var _player = new Player();

// We tell the constructor to build a new Enemy struct
// This will run the code inside the constructor function
var _enemy = new Enemy();
Now you probably can guess the output to the terminal of the code above, right?
It should not be very difficult but we will take a look at it, either way:
Code:
500 // This is the HP of the player
200 // This is the HP of the enemy
So what we could learn from this is that global functions are not bound (linked) to any scope.

Let us take the experiment one step further by imagining we placed the following line of code below our current example:
GML:
// ...
var _player = new Player(); // this is the older code, prints: 500
var _enemy = new Enemy(); // this is the older code, prints: 200

// Here we are outside player struct and enemy struct and therefore there is no "hp" variable
printHp(); // This will throw an ERROR
In this case, executing the printHp() global/script function in a scope where the variable hp is not present throws an error.
The same function gave us 3 different outputs, in other words, the output of an unbound function depends on where you are calling the function from.


(3) BOUND FUNCTIONS (METHOD VARIABLES)
The example above probably sounds natural and intuitive and that is most likely because it's the way scripts used to work on older GameMaker Studio versions.
However, as I stated above apart from global functions (those that are named and declared inside a script file) we also have method variables (all non-global-function).
This category embraces all anonymous functions and named functions that are not declared inside script files.

The declaration of a method variable results in the creation of an automatic bound between the function and the scope it was declared in.

For a better understanding let's look at this code below (again you can place it inside an empty [SCRIPT FILE] if you want to follow along)
This setup is similar to the ones before but now we will define functions inside a struct (these will be method variables):
GML:
function Player() constructor {
    name = "player";
    hp = 100;

    // This is a 'named function' declared inside a struct (METHOD VARIABLE)
    function printName() {
        show_debug_message(name);
    }

    // This is an 'anonymous function' declared inside a struct (METHOD VARIABLE)
    printHp = function() {
        show_debug_message(hp);
    }
}

function Enemy() constructor {
    name = "enemy";
    hp = 2000;
}

var _player = new Player();

_player.printName(); // will print -> player
_player.printHp(); // will print -> 100
Well I think the code above is simple to understand but now lets go a little bit further by adding some lines to it.
Let's store the printName and printHp inside the enemy and run them:
GML:
var _enemy = new Enemy();

_enemy.printName = _player.printName;
_enemy.printHp = _player.printHp;

_enemy.printName(); // ??
_enemy.printHp(); // ??
Can you figure out the output of the code above?
Go ahead, a second look at the code and the rules we talked about, and give it a shot, I'll wait. ;)

player
100

Were you able to guess it right? Was it something unexpected?

Let's go through the code together then, I will add numbers to the lines with the order in which code is executed:
GML:
function Player() constructor {
    name = "player"; // (2)
    hp = 100; // (3)

    // This is a 'named function' declared inside a struct (METHOD VARIABLE)
    function printName() { // (4)
        show_debug_message(name);
    }

    // This is an 'anonymous function' declared inside a struct (METHOD VARIABLE)
    printHp = function() { // (5)
        show_debug_message(hp);
    }
}

function Enemy() constructor {
    name = "enemy"; // (9)
    hp = 2000; // (10)
}

var _player = new Player(); // (1) <------------------- START EXECUTION

_player.printName(); // (6) will print the "name" -> player
_player.printHp(); // (7) will print the "hp" -> 100

// Everything is okay up until now!!

var _enemy = new Enemy(); // (8)

_enemy.printName = _player.printName; // (11)
_enemy.printHp = _player.printHp; // (12)

_enemy.printName(); // (13) ----> this will call (4)
_enemy.printHp(); // (14) ----> this will call (5)
Now we will analyse it line-by-line in order:
  • (1) we call the creator of the Player struct (this is where the actual execution starts)
  • (2) & (3) the player variables name and hp
  • (4) we create a named function and it is inside a struct so it will be this will become a method variable (this variable becomes bound to the player scope)
  • (5) we create an anonymous function so it is also a method variable (this variable becomes bound to the player scope)
  • -- The constructor ends and returns the created struct
  • (6) we call the printName it will print the name "player"
  • (7) we call the printHp and the value 100 is printed
  • (8) we create an enemy instance
  • (9) & (10) the enemy variables get initialized (name & hp)
  • -- The constructor ends and returns the created struct
  • (11) & (12) we store the player method variables (printName & printHp) inside the enemy struct
  • (13) we call the method variable printName from the enemy struct, but because it is a bound to the player it is executed using the player scope.
  • (14) we call the method variable printHp from the enemy struct, but because it is a bound to the player it is executed using the player scope.
As we can see a method variable (as opposed to what happens with global functions) is bound to a specific scope, the scope where it was declared.
Passing the function around between object/rooms/struct instances won't change the result of its execution.
So summing up what we know so far, this is a partial table that resumes the resulting function given the declaration scope and the function type:

methods.png

NOTE: You probably noticed I grouped instance and room together in the table above. This is due to the fact that the room creation code​
is actually running inside an instance of a dummy object that GMS creates just for this specific purpose.​


(4) BUILT-IN 'METHOD' FUNCTION
Okay, so up until now I talked about unbound function (ex.: global functions) and bound functions (ex.: method variables) the concepts are all
falling into place, and it all starts to make sense but GML has another neat built-in functionality. What if we could:
  • bind an unbound function
  • unbind a bound function
  • rebind an already bound function (doesn't not overwrite but creates a new method variable instance)
Well this is possible thanks to a built-in function named method() this function takes in a scope (instance/struct/undefined) and a callable (function/method variable) and returns a new method variable bound to the specified scope.

Let's use the code example below to better understand this:
GML:
// As this is a script file, the function below is a global/script function
// So as we discussed already it is not bound to a specific scope.
function printName() {
    show_debug_message(name);
}

// We create a player/enemy struct
// This time using a struct literal instead of a constructor (to show you that both work the same)
player = { name: "Hero" };
enemy = { name: "Demon" };

// Uncommenting the line below would result in an error because there is no 'name' variable in this scope.
// printName() -----> [ERROR]

// However using the method function we can bind the 'printMethod' to the player struct
printPlayerName = method(player, printName);

printPlayerName(); // This will print "Hero"!

// We can also create another bound using and already bound function
// In the example below the 'method' funtion will return a new rebound method variable.
printEnemyName = method(enemy, printPlayerName);

// Now this method variable uses the 'enemy' scope.
printEnemyName(); // This will print "Demon"!

// I also said you could provide 'undefined' as the scope
// Doing so will result in the creation of an unbound method variable
unboundPrintName = method(undefined, printPlayerName);

// [ATTENTION] 'unboundPrintName' is not the same as 'printName'
// Even though they are both unbound will use the variables from whatever place they have been called from
// 'printName' is global and exists project wide, while 'unboundPrintName' is not global.

// Uncommenting the line below would result in an error as it happens with 'printName'
// unboundPrintName() -----> [ERROR]
With all the comments and details in the code above I guess we are closer to fully understand what functions/methods/scope/binding is all about.
But before going any further lets make some considerations regarding the method() function:
  • method function allows to bind a callable (function/another method) to a scope (instance/struct).
  • method function doesn't change the scope of the provided function, it returns a new method variable bound the the provided scope. (*)
  • providing undefined as a scope will result in a method variable that is unbound (executes in the scope it is called).

*NOTE: Creating a new method doesn't create a new function. We need to pay close attention to the naming here a method is not really a function per-se.​
Let's put it this way a method is a "wrapper that has a reference to a function and a reference to a scope" so you are not duplication the function but creating a new wrapper.​


[STATIC FUNCTIONS]

Before we finished we will talk about static variables. The reason we will briefly cover static functions in this tutorial is because they work a little bit different from what we've seen up until now.
On the sections above we learned that anonymous functions were automatically bound to the scope they are defined in, and this is true UNLESS we are talking about static functions.

For a real example, we will take a look at the code example below:
GML:
function Player() constructor {

    name = "Hero";
    hp = 1000;

    // Even though we are defining an anonymous function
    // this function will not bound to this scope.
    static printName = function() {
        show_debug_message(name);
    }

}

var _player = new Player();
// The function is being called from the player, so it prints the player name
_player.printName(); // Hero


var _enemy = { name: "Demon" };
_enemy.printName = _player.printName;

// The function is being called from the enemy, so it prints the enemy name
_enemy.printName(); // Demon
In the example above we can see that static functions have an execution behavior similar to the global functions as they are not attached to any scope, but at the same time they are not global. So if I could make a simple comparison I would say that a static function is "almost like" an anonymous function that is being automatically bound to undefined. And with that in mind we can actually update the table above to a more complete version containing all the information:

methods (1).png


[Q&A]

(1) SELF & OTHER
When we use method variables (bound functions) how does self and other behave? Looking at everything you've learned above about functions and methods you might be tempted to say that method variables kind of "work very similar" to a with statement calling an unbound function inside it, right?

GML:
with(myScope) {
    myUnboundFunction();
}
Sounds fair but, event though this might look true you need to consider this:
  • self inside a method variable with work as expected and return the current scope of the method.
  • on the other hand other will not work as expected, this is because calling a method variable is not considered a change in scope.
NOTE: When comparing the performance of a method to a with statement the former is ~2 times faster than the second.​


(2) UNBOUND FUNCTION INSIDE BOUND FUNCTION
What happens if I have an unbound function being called inside a bound function?
The quick answer for that is "the unbound function will be called within the scope of the bound function".

Let's look at this in code (again this code is inside a script file)
GML:
// This is a global function so it is NOT bound
function printName() {
    show_debug_message(name);
}

// This is a global function so it is NOT bound
function printStats() {
    show_debug_message("Name: " + name + ", Hp: " + string(hp) + ", Mp: " + string(mp));
}

function Player() constructor {

    name = "hero";
    hp = 100;
    mp = 10;

    // This function is a non-static, anonymous function, inside a struct
    // looking at the table above we can see it is BOUNDED to where it is declared.
    contextExecutor = function(_func) {
        // Everything executed here will be executed inside player scope.
        // Since we are executing the function passed as a parameter their can be 2 outcomes:
        // - if the function is bound it is executed within its bound scope
        // - if the function is not bound it is executed within this instance scope.
        _func();
    }
}

// Both statements below will throw an error
// printName(); // [ERROR] because variable 'name' doesn't exist
// printStats(); // [ERROR] because variables 'name'/'hp'/'mp' doesn't exist

var _player = new Player();

// However the two lines below will work:
// We are passing the unbound functions to our ""wrapper" method
// they will execute them inside the player instance scope.
_player.contextExecutor(printName);
_player.contextExecutor(printStats);

// Even if we store the method variable locally it will still be
// bound to the player scope so the code will work as expected.
var executor = _player.contextExecutor;
executor(printName);
executor(printStats);


(3) FUNCTIONS DEFINED INSIDE STRUCT LITERALS
What happens when I create a function inside a struct literal? This is another topic that might raise some confusion.

Let's consider we are inside a scope (SCOPE A) and we are about to create a struct literal:
GML:
// Lets call this SCOPE A

value = "OUTSIDE";

structLiteral = {
    // Here we are inside SCOPE B

    value: "INSIDE",

    // This function is an anonymous function and is being defined
    // inside SCOPE B (gets automatically bound to scope B) so the
    // variable 'value' it refers to is the one within this scope ("INSIDE")
    printValue: function() {
        show_debug_message(value);
    }
}

structLiteral.printValue(); // "INSIDE"
As you can see we are defining printValue inside the struct literal so it will become automatically bound to it meaning that the variables it refers to are the ones inside the struct. However, you can change the program behavior by rearranging the code to look like this:
GML:
// Lets call this SCOPE A

value = "OUTSIDE";

// This function is an anonymous function and is being defined
// inside SCOPE A (gets automatically bound to scope A) so the
// variable 'value' it refers to is the one above ("OUTSIDE")
var _printValue = function() {
    show_debug_message(value);
}

structLiteral = {
    // Here we are inside SCOPE B

    value: "INSIDE",

    // Here we are just setting the struct variable to the function
    // that we declared outside so its execution scope will not change.
    printValue: _printValue
}

structLiteral.printValue() // "OUTSIDE"
Even if it looks pretty similar here we are defining printValue outside the struct literal, inside scope A so it will become automatically bound to this scope instead.
This example above is extremely important and can be deceiving, so make sure to check where the function is being declared.

NOTE: You can also pass static functions into the struct literal and they will behave as unbound functions.​


(4) METHOD VARIABLES AND TARGET SCOPE
I don't know if you have question yourself already about this but this can also lead to some confusion.
The setup would be: you create "functionA" and after that bound it to "objectB", straight and simple!
But now the question is does functionA exist within objectB?

Let us see the code written down so we can analyse it better:
GML:
// We declare function
var _functionA = function() {
    // some code
}

// We declare an object
var _objectB = { /* some variables */ };

// After that we bound the function to the object
var _boundFunctionA = method(_objectB, _functionA);

// Does the function exist inside the object?
// In other words:
_objectB._boundFunctionA(); // is this possible???
So what do you think? :) The answer is NO even though our newly bound method variable (_boundFunctionA) acts upon the object _objectB variables it doesn't exist within its scope.
As the phrasing implies we are "binding a function to a struct/instance" (and not the other way around) so what gets bound is the function, leaving the struct exactly the same.
If the struct had no methods before it will still have no methods after.

I'll be adding more examples and stuff here as new questions might arise! Stay tuned ;)




[CONCLUSION]
And that's it, functions/methods/binding in a nutshell!
  • functions can be either named or anonymous.
  • global functions are named function defined in script files
  • method variables (non-global) are all other functions defined in instances/structs/constructors
  • method variables are automatically bound to the scope they are declared in (except static those don't get bind to anything)
  • method variables results is independent from where the execution call is being made.
  • unbound functions results depend on the location in your code they are being called them from.
That was quite a lot of information so remember to go through it a couple of times to understand it well.


Here xD from xDGameStudios,
Good coding to you all.
 
Last edited:

gnysek

Member
I don't see that it's mentioned explicity, but method() creates a copy of function from what I saw in debugger. This might be crucial in some cases, as that means if we need to update things using returned value, as previous reference still points to old thing, and that might also have some small impact on memory usage (as using method means +1 copy of something).
 
I don't see that it's mentioned explicity, but method() creates a copy of function from what I saw in debugger. This might be crucial in some cases, as that means if we need to update things using returned value, as previous reference still points to old thing, and that might also have some small impact on memory usage (as using method means +1 copy of something).
In the considerations about `method()` function I wrote that:
  • method function doesn't change the scope of the provided function, it returns a new method variable bound the the provided scope.

I do think it is pretty explicit. We also need to consider the words being use... a method variable is not a function...
creating a new method doesn't create a new function. Let's put it this way a method is a "wrapper struct that has a reference to a function and a reference to a scope"
so you are not duplicating the function, you are creating a new wrapper struct instead ;)

I didn't want to go very technical on this but I'll try to explain things very easily with some shortcuts and simplifications (just for explanation purposes)

  1. GMS will go through all your code and extract all the functions (function() { ... }) from it (named/anonymous all of them)
  2. The functions themselves are placed in a table and they are replaced in the code by their respective address in that table.
  3. So function() {} becomes an address.
  4. When we use the method function we pass in two addresses: the scope address and the function address
  5. The method returns a wrapper struct (yes it is a struct and you can actually place variables inside it but that will be a thing for another tutorial)
  6. So the memory impact is actually the one for creating a struct "that has 2 variables" (a scope and a function address)

Never the less it has a memory impact and it should be considered that is why using static functions whenever possible in constructors is more efficient and very important.
But that I explained already in my other tutorials on Static Variables: [TEXT TUTORIAL] Keyword "static" Explained (Part 1 - The Basics)

I went on and added a NOTE to that section explaining just that :)

This might be crucial in some cases, as that means if we need to update things using returned value, as previous reference still points to old thing
I do not understand what you mean by that..
 
Last edited:
Top