GMS 2.3+ [TEXT TUTORIAL] Keyword "static" Explained (Part 1 : The Basics)

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

[PROBLEM]
What is static? what is its purpose? and how can I take advantage of it?


[EXPLANATION]
There is no quick answer for this, as static will have different use-cases depending on the context.
However two rules always apply to static variables:
  • static declaration is a declaration that is only executed once (in the entire game) .
  • static variables can only be modified from within the context they are defined.
The two contexts where you can have static members is: inside a function or inside a constructor function, below I will explain both use cases.


[PART 1: FUNCTIONS]
The first place where you can see static members is inside functions and for explanation purposes let's look at the example below.
GML:
function printStatic(_value) {
    static _myStaticVar = _value; // This will only be executed on first run!
    show_debug_message(_myStaticVar);
}
We will now read the function line by line:
  1. static _myStaticVar = _value; on this line we are declaring a static variable called _myStaticVar and assigning it the argument _value. As we already know this line will only be executed once in the entire game. "But when?", you may ask! It is declared the first time we execute the function.
  2. show_debug_message(_myStaticVar); on this line we print the static variable _myStaticVar to the console.
It looks simple but now let's see this in action:
GML:
printStatic(10); // This will print "10"
Not that unexpected because we are passing the value 10 assigning it to the static variable _myStaticVar and printing the _myStaticVar.
Let's however keep calling the function again a couple of times with different values.
GML:
printStatic(10); // This will print "10"
printStatic(20); // This will print "10"
printStatic(30); // This will print "10"
Notice something strange? As I explained earlier the first line is only executed the first time we run the function so the function looks like this:

(1) FIRST EXECUTION
GML:
function printStatic(_value) {
    static _myStaticVar = _value;
    show_debug_message(_myStaticVar);
}
(2) CONSECUTIVE EXECUTIONS
GML:
function printStatic(_value) {
    // The first line "disappears", however the variable '_myStaticVar' still exists with the previously assigned value.
    // So we are not actually using the input variable '_value' anymore.
    show_debug_message(_myStaticVar);
}
Note: the first line does not ACTUALLY disappear this is just an analogy to try to explain what GMS2.3 is doing behind the scenes.


[QUESTIONS]
Let's look at some questions that you might have:
Q1) Can I ever change a static value?
A1) Yes you can from within the function itself.
GML:
function printStatic(_value) {
    static _myStaticVar = _value; // this line will "disappear" after the first execution.
    _myStaticVar += 1; // This line however will run always and change the static variable (incrementing it by one)
    show_debug_message(_myStaticVar);
}
Q2) What cool things can I do with it?
A2) We can for example keep a track of how many times a function was executed.
GML:
function limitedPrint(_value) {
    static _myStaticVar = 0;    // this line will "disappear" after the first execution.
    _myStaticVar += 1; // This line will run always and change the static variable (incrementing it by one)
    if (_myStaticVar >= 15) { // If we reach the limit.
        show_debug_message("Too many executions");
        return;
    }
    show_debug_message(_value); // Now we are printing the "_value".
}

[USE-CASES]
Not that useful enough 🤦‍♂️ I know the previous example was just to explain things.
So let's give a more useable examples:

(1) ARRAY SHUFFLE SNIPPET
GML:
function array_shuffle(_array) {
    static _shuffleFunc = function(obj1, obj2) { return choose(-1, 1); }; // This function doesn't need to be declared every time it is used.
    array_sort(_array, _shuffleFunc);
}
This example shuffles an array (I'll not get into detail of how the shuffle function works because is not related to the scope of this tutorial)
(2) SINGLETON SNIPPET
GML:
function getDefinitions() {
    static _definitions = { screenWidth: 800, screenHeight: 600 };
    return _definitions;
}
This example replaces the use of a global variable. Although it has something special about it.. what?
If you remember correctly the static member is created the first time the function runs so if you never use the function the struct is never created.
The variable doesn't exist until the moment you need it... Another example:
(3) SINGLETON SNIPPET (LIST)
GML:
// USING GLOBAL VARIABLE
global.masterList = ds_list_create();

// USING STATIC VARIABLE
function getMasterList() {
    static _masterList = ds_list_create();
    return _masterList;
}
In the example above we see a comparison between a global list that exists whether you need to use it or not. And a static member list that acts as a global list but only starts to exist if you ever need it.
This is optimal for extensions/assets/modules that for example only create some of their needed structures depending on what you actually are using.

[NOTES]
Even though you cannot change the value of the static variable from outside the function (rule number 2) in the singleton snippet example if we do:
GML:
var definitions = getDefinitions();
definitions.screenWidth = 12;
This will change the value inside the struct and therefor "update" the static variable.
However the static variable was pointing at a struct and it is pointing at the same struct you did not change the variable you changed the struct.



[PART 2: CONSTRUCTOR FUNCTIONS]


The second place where you can see static members is inside functions constructors.
But how different do they behave? Let's look at the example below:
GML:
function myConstructor() constructor {
    static word1 = "hello";
    static word2 = "world";

    static print = function() {
        show_debug_message(word1 + " " + word2);
    }
}
With function constructors all the same rules apply static variables are only defined once and can only be changed from within the function itself.
the second rule is a little tricky though, why?!
Let's see it in action:
GML:
var myStruct = new myConstructor();

// we can access all the variables
show_debug_message(myStruct.word1); // this prints "hello" as expected
show_debug_message(myStruct.word2); // this prints "world" as expected
myStruct.print(); // this prints "hello world" as expected

// what about changing them?
myStruct.word2 = "xD";
myStruct.print(); // this prints "hello xD" ?? confused ??
what happens when we do the assignment myStruct.word2 = "xD"; is that we are actually creating a variable named word2 that is specific to this instance.
So we didn't change the static variable we just broken the link to underlying static variable.
Still confused? Let's apply one case we saw above to the constructor functions.

GML:
function myCounterConstructor() constructor {
    static number = 0;
    number++;  // here we are changing the static variable but we are inside the constructor so we can do so.
    show_debug_message("This is instance number: " + string(number));
}

var struct1 = new myCounterConstructor(); // this prints "This is instance number: 1"
show_debug_message(struct1.number); // this prints "1";

var struct2 = new myCounterConstructor(); // this prints "This is instance number: 2"
show_debug_message(struct1.number); // this prints "2";
show_debug_message(struct2.number); // this prints "2";
// Note that now both structs have the same number value.

var struct3 = new myCounterConstructor(); // this prints "This is instance number: 3"
show_debug_message(struct1.number); // this prints "3";
show_debug_message(struct2.number); // this prints "3";
show_debug_message(struct3.number); // this prints "3";
// Note that all structs have the same value and it changed for all (that is the link I was talking about)
Well everything is as expected up until now, but let's experiment something:
GML:
struct3.number = 10000;
show_debug_message(struct1.number); // this prints "3";
show_debug_message(struct2.number); // this prints "3";
show_debug_message(struct3.number); // this prints "10000"; // here the link was broken

// what happens if I create a new instance?
var struct4 = new myCounterConstructor(); // this prints "This is instance number: 4"
show_debug_message(struct1.number); // this prints "4";
show_debug_message(struct2.number); // this prints "4";
show_debug_message(struct3.number); // this prints "10000"; // here the link remains broken
show_debug_message(struct4.number); // this prints "4";
All the structs stayed linked to the static variable although as soon as we edited the value in struct3 the link was broken but only for that instance.

[QUESTIONS]
Let's look at some more questions that you might have:
Q1) So what are the static variables advantages when using constructor functions?
A1) Well as you can see the variable is shared across all instances so you are actually saving memory. You are actually using just 1 variable that is referenced multiple times.

Q2) What are the use-cases?
A2) I'll not talk about all use cases as they are a lot of them and some allow for pretty neat tricks. But as a rule of thumb, you want functions inside constructors to be static (except in special cases) as their logic is shared through all instances of the same struct. So this way there will only be one function that is shared with all the instances instead of one new function per instance.


[NOTES]
One of the things you might ask is can I reestablish the "static link" after breaking it? How?
Yes you can, the static variable actually remains unchanged, hidden by the newly created instance variable.
GML:
function myConstructor() constructor {
    static number = 100;
}

var myInstance = new myConstructor();
show_debug_message(myInstance.number) // we are looking at the 'static' variable (100)

myInstance.number = 1; // This will break the link!
show_debug_message(myInstance.number) // we are looking at the 'instance' variable (1)

variable_struct_remove(myInstance, "number"); // This will remove the 'instance' variable
show_debug_message(myInstance.number) // we are looking at the 'static' variable (100)
What is really happening is that we create a variable with the same name (number) that hides the static variable.
So when we remove the added instance variable the static one is still there and behaves as it should.
The same applies to static functions as they are in-fact variables.
GML:
function myConstructor() constructor {

    static func = function() {
        show_debug_message("Hello");
    }
}

var myInstance = new myConstructor();

myInstance.func = function() { show_debug_message("World") };
myInstance.func(); // "World"
variable_struct_remove(myInstance, "func");
myInstance.func(); // "Hello"
One last thing you might be thinking is "so can I actually remove the static variable?!".
The answer to that is no you cannot remove a static variable from a struct it will always be there.


[CONCLUSION]
And that's it, static keyword in a nutshell!
  • static declaration is a declaration that is only executed once (in the entire game).
  • static variables can only be modified from within the context they are defined.
  • static variables don't belong to the structs, they belong to their constructor function.
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:
Top