GameMaker: DIY closures

GameMaker Language currently doesn't have closures.

It might in GMRT (proposal), but for now you cannot do something like this

function get_enemies_below_hp(_enemies, _threshold) {
    return array_filter(_enemies, function(_enemy) {
        return _enemy.hp < _threshold;
    });
}

because _threshold is not visible inside the inner function.

But there are some ways around that.


First, the code. See if you can spot what's going on here right away:

function get_enemies_below_hp(_enemies, _threshold) {
    with { _threshold } return array_filter(_enemies, function(_enemy) {
        return _enemy.hp < _threshold;
    });
}

And here's what happening:

  1. {name} is a shorthand for {name:name}
  2. Function literals bind to the current self, which inside the with-block would be that struct we just made.
  3. Non-local/non-global variables inside the functions read from self, which is that bound struct.

So on the lower level the code works somewhat like if you did this:

function get_enemies_below_hp(_enemies, _threshold) {
    var _struct = { _threshold: _threshold };
    var _function = method(_struct, get_enemies_below_hp_anon_1);
    return array_filter(_enemies, _function);
}
function get_enemies_below_hp_anon_1(_enemy) {
    return _enemy.hp < self._threshold;
}

And if you need to retrieve some values for manipulation afterwards, you can do that too:

function count_enemies_below_hp(_enemies, _threshold) {
    var _context = { _threshold, _found: 0 }
    with (_context) array_foreach(_enemies, function(_enemy) {
        if (_enemy.hp < _threshold) _found += 1;
    });
    return _context._found;
}

Bonus: using self

Want to pass in some variables but still have access to the instance that called the function? Pack it up and pass it in:

function count_enemies_below_hp(_units, _threshold) {
    var _self = self;
    var _context = { _threshold, _self, _found: 0 }
    with (_context) array_foreach(_units, function(_unit) {
        if (_unit.team != _self.team
            && _unit.hp < _threshold
        ) _found += 1;
        // or:
        with (_self) {
            // closure-struct is now in `other`
            if (_unit.team != team
                && _unit.hp < other._threshold
            ) other._found += 1;
        }
    });
    return _context._found;
}

(note: can't do _self: self because that would refer to the struct being made)


And that's about it!

Related posts:

5 thoughts on “GameMaker: DIY closures

  1. Interesting! I worked out how one could do closures with the struct/method strategy but I never imagined “with” could be used in this way.

  2. Speaking of using `with`. I found out that `with` skips on `undefined` which can be used in the following shorthand.

    // From this...
    var _result = maybe_undefined(....);
    if (!is_undefined(_result)) {
      _result.field = ...;
    }
    
    // ... to this
    with (maybe_undefined(....)) {
      field = ...;
    }
    

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.