1
\$\begingroup\$

I am wondering if there is an elegant way to script sequential events in GameMaker 2.

In Godot 3.1, it is very easy to chain events together using yield() and signals. You just start doing something, yield until it emits a signal that it's done, and then continue your code. I am wondering if there is anything similar in GameMaker 2.

In Godot, for example, if i wanted to spawn an explosion cluster which consisted of 10 explosions, each starting 50 milliseconds after each other, I could do something like this.

func create_explosion_cluster(x, y):
    for x in range(10):
        create_explosion(x, y)
        timer = Timer.new()
        add_child(timer)
        timer.wait_time = 0.05
        timer.start()
        yield(timer, "timeout")
        timer.queue_free()

I used this method for complex boss behaviors as well. For example,

func boss_attack():
    animation_player.play("intro_to_attack")
    yield(animation_player, "animation_finished")
    animation_player.play("attack")
    for x in range(10):
        create_projectile(x, y, vector_to_player)
        timer = Timer.new()
        add_child(timer)
        timer.wait_time = 0.05
        timer.start()
        yield(timer, "timeout")
        timer.queue_free()
    animation_player.play("outro_to_attack")
    yield(animation_player, "animation_finished")`

In fact, I use this technique all over the place when I want certain events to follow other events (in transitions, cutscenes, enemy behaviors, etc.)

Is there a way to script sequential events in GameMaker with similar flexibility as you have in Godot?

If there is a general design pattern to solve this kind of problem without yield(), co-routines, and so on, I'd love to know it.

\$\endgroup\$
1
\$\begingroup\$

The most portable way to do this in languages without coroutines is to convert the code into a state machine.

For every yield you make a new state. Then you write code that starts from each yield point and executes the code through all paths up until the next yield. And you have to make sure that every variable that needs to persist across a yield does so. Then you can put all of those bits of code in a bit switch statement and add a state variable on which you switch:

So taking your boss_attack:

func boss_attack():
    //start in state 0
    animation_player.play("intro_to_attack")
    yield(animation_player, "animation_finished") //state 1
    animation_player.play("attack")
    for x in range(10):
        create_projectile(x, y, vector_to_player)
        timer = Timer.new()
        add_child(timer)
        timer.wait_time = 0.05
        timer.start()
        yield(timer, "timeout") //state 2
        timer.queue_free()
    animation_player.play("outro_to_attack")
    yield(animation_player, "animation_finished") //state 3

This becomes in C-ish pseudocode.

//persistent variables.
state = 0;
x = 0;

func boss_attack(){
    switch(state){
    case 0:
        animation_player.play("intro_to_attack");
        state=1;
        onAnimationFinished(boss_attack);
        break;
    case 1:
        animation_player.play("attack")
        x = 1;
        create_projectile(x, y, vector_to_player)
        state = 2
        onTimer(0.05, boss_attack);
        break;
    case 2:
        x = x + 1;
        if(x<=10){
            create_projectile(x, y, vector_to_player)
            state = 2
            onTimer(0.05, boss_attack);
        }else{
            animation_player.play("outro_to_attack")
            state=3;
            onAnimationFinished(boss_attack);
       }
       break;
    case 3:
       state = 0;
       //don't reschedule but prep for next run through.
       break;
    }
}

This can be quite a bit more work but is much more flexible

\$\endgroup\$
0
\$\begingroup\$

This is so far my idea of handling multiple projectiles/effects shortly after each other.

Perhaps it's not the same flexibility, but I think this can work similair.

Create:

defaultTimer = 0.05;
timer = defaultTimer;
defaultAmount = 10;
amount = defaultAmount;

Step:

if (amount > 0)
{
    if (timer > 0)
    {
        timer -= (1/room_speed)
    }
    else 
    {
       //create explosion here
       Instance_Create(/*explosion object*/);
       amount -= 1;
       timer = defaultTimer
    }
}
//setting amount to 10 will set the explosions off again. 

I can't find anything related to a script timeout in GMS2, I'd love to know though.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.