code of the Ninja();

// in search of swift, efficient, and invisible code

2011-08-27

Pausing

Last time, I talked about how triggers can be used to replace traditional step events in order to solve certain esoteric problems in Game Maker. This time I want to explain how to use triggers to achieve something a lot more universally useful: pausing the game.

Game Maker, sadly, has no good built-in methods for pausing that are quite satisfactory (in fact, the documentation doesn't even mention it, except of course the references to the debugging feature). Over the years, I've experimented with the following solutions, but found each of them lacking:

Solution: Using the keyboard_wait() function
Problems: It freezes everything, preventing any sort of pause menu functionality. It also breaks the flow of games designed for controllers or the mouse, because it requires interaction with the keyboard.
Solution: Going to a dedicated Pause room
Problems: This is much better, because you can put whatever you want in the pause room to create any kind of effect you want (menus, faded image, soft music, etc). But it's kind of a pain to juggle the room_persistent status of the calling room, and if you want to show a frozen image of the game, you have to create the resource yourself using something like background_create_from_screen(). Also, if you want access to any of the objects from the calling room, you're sort of out of luck.
Solution: Using instance_deactivate_all()
Problems: This solution uses an object for controlling all pause behaviour that deactivates everything else and takes over until the game is unpaused. It has most of the same problems as the previous solution, though (no access to paused objects; the need to manually create an image resource from the screen), and introduces yet another: Unpausing requires you to call something like instance_activate_all(); if there are any objects you don't want to be reactivated, you're out of luck. Basically, this solution is incompatible with any game that has to activate/deactivate instances for any other purpose.

After all this, one starts to wonder why there isn't just some kind of built-in functionality for pausing in Game Maker. Almost all games need the feature (besides notable exceptions like Phantasy Star Online) so why not? There could be a variable called pause_object which would point to the only object which would remain active while the game was paused (much like how view_object points to the object which the view should follow). And there'd be a function called game_pause() that would take one argument - true or false - to toggle the state on and off. Would this have been so hard?

Fortunately, with a little bit of work, triggers come to our rescue in this situation, too.

Because trigger events can be set to occur before each step moment (beginning, middle, and end), it's possible to create triggers to completely replace the step events:

(If you can't see the image, the condition code for these triggers should be return global.paused;).

If you move the actions in the step events on all your objects to the corresponding trigger events instead, you can prevent the execution of these actions merely by setting global.paused to true. Any object you don't wish to be paused should retain traditional step events, of course.

Using this method, the draw event will still be executed, preventing the need to generate an image. And all paused objects are still active in case you need to access them. Great!

One quickly notices the drawbacks to this solution, though. Beginning, middle, and end step events aren't the only things that happen for objects during each step in Game Maker. There are other events, like keyboard checking and so on, that are also performed.

This doesn't bother me personally, because I tend to ignore these events and write my own code for checking keys and stuff in the step event of some kind of control object. This isn't for everyone, though, so is there some way to get around this?

Well, yes. You can create more custom triggers that check the same conditions as the original, but also check global.paused. For example, return global.paused and mouse_check_button_pressed(mb_left);. This is a bunch of busy work, but do it at the start of your project and it might save you some trouble down the line.

Another drawback to using triggers to pause is that Game Maker handles certain things about objects automatically. Every step, it adds hspeed to x and vspeed to y, and so on. Again, this doesn't affect me because I use all my own variables, but it can be gotten around anyway: in your code that pauses the game, you could just do this...

...and of course the reverse when you unpause.

In conclusion, I admit that this solution is far from perfect also, and it requires a lot of work to get functioning flawlessly. But depending on what your game is like, and considering the issues with competing solutions, it might be useful to you.

If there are better solutions, I'd love to hear about them.

2 comments:

  1. "...if there are objects you don't want reactivated, and you can't find a way to deactivate them again because they're somehow anonymous, you might need to rethink the overall architecture of your engine."

    Objects can't deactivate themselves (it generates a silent error that prevents further execution of that step and can sometimes freeze the game), so if the criteria for deactivation isn't a simple region or id check, you're out of luck. For instance, recreating the way objects individually check if they're out of range in Sonic is very difficult.

    In other words, activation code is very finicky and the less that conflicts with it the better.

    ReplyDelete
  2. Imagine this situation: Every step, a simple region (roughly the size of the view) is activated. Unlike the standard example in GM's help, however, there is no accompanying deactivation command. Each object that may have been reactivated does its own check whether it should be deactivated (similar to Sonic's code). This way a falling block can decide to continue downward until it lands, instead of deactivating in midair and perhaps being come across by the player in this state later on.

    But objects can't deactivate themselves, so they might instead pass their ID to a control object. Each step, the control object deactivates whatever ID it's told to (and only one at a time, to avoid slowdown with many deactivation commands).

    When pausing, everything gets deactivated. When unpausing, you can't just reactivate the view - there might be busy objects outside the view that need to return to their previous state. But if you reactivate ALL, then a huge number of objects are then needed to be deactivated by the control object over the next few seconds.

    For most purposes, things like this will never cause a developer trouble. But there are an amazing number of hoops one has to jump through to get GM to behave precisely like classic Sonic (for instance), and having as many tricks and tools in your arsenal can't be a bad thing.

    ReplyDelete