Hour 11. Game Flow
What You’ll Learn in This Hour:
Pausing the scene and stopping node processing
Changing to another scene
Loading resources while providing visual feedback
Controlling game behavior when the quit button is pressed
One can think of a video game as a show on stage, but interactive. It is “live,” i.e., things you see on the screen are being dynamically rendered
at the time you are playing. There are several scenes, and you are going through them one at a time. There is also a “backstage,” where tasks
are managed behind the scenes, such as setting up the scene, retrieving objects and resources, etc. However, most of the time, the player is
in control and may choose which scene to play or to take a break.
In this hour, our focus is on the “stage,” or the Scene Tree. You’ll learn how to use it to control game flow, selectively pause the game, and
switch scenes. You’ll also learn about Resource Loader, a “backstage,” and use it to load large resources without the game freezing while it
waits for them. Finally, you’ll learn how to handle “quit” requests from players and respond properly.
After Launching the Game
Have you ever wondered what actually happens when you launch the game executable?
A game cannot start if there is no operating system. Thus, it begins with the module that speaks directly with the system—the OS singleton.
OS
OS is a singleton that abstracts interactions with the system and tries to make it consistent, regardless of the platform. It is also one of the first
modules that are loaded, before other singletons and the scene system. A Main Loop must be given to keep the engine running. Without a
Main Loop, the engine will quit, because there is nothing else to process, kind of like renting a theater with no plays to show. This is useful for
stand-alone scripts, for example, which will have to inherit the Main Loop class.
Main Loop
Main Loop is a class capable of keeping the engine running after initialization. It will receive callbacks on each tick (“iteration”) throughout the
duration since the last tick (“delta”). When launching a game, a subclass of Main Loop called Scene Tree is automatically created by the
engine, and provides the interface to manage the current scene. Main Loop or Scene Tree can be accessed via
Engine.get_main_loop().
Scene Tree
Scene Tree is a class derived from Main Loop. As mentioned previously, besides preventing the engine from quitting, Scene Tree manages
the current scene. It has a viewport called “root”’ that acts as the root of the tree. Here are some of the functions regarding scene management:
change_scene(String path): Loads the scene file pointed to by the provided path and change the current scene to it. An example of
this path parameter is res://main.tscn.
change_scene_to(PackedScene packed_scene): Changes the current scene to a loaded scene. PackedScene of a scene file can
be acquired by load() or preload(). For example: var scene = preload(“res://main.tscn”) will load “main.tscn” as
PackedScene “scene.” Then you can call change_scene_to(scene).
get_current_scene(): Retrieves the root node of the current scene.
is_paused(): Notifies whether the current Scene Tree state is paused.
set_pause(bool paused): Sets the Scene Tree pause state.
quit(): Closes the game.
reload_current_scene(): Loads the current scene file from the disk.
set_auto_accept_quit(bool enabled): If set to “true,” the game will exit immediately when the player presses the exit button or the quit
key combination (Alt + F4).
In addition to being the nodes manager, Scene Tree is also capable of calling multiple nodes of the same group, high-level networking, and
more, but those are out of the scope of this hour.
Scene Tree is always accessible via the mentioned Engine.get_main_loop() method. It is optional to check whether the returned Main
Loop is a Scene Tree, as most of the time, that is what it will be. Another way is to call get_tree() from any nodes in the tree. If successful,
get_tree() will return a Scene Tree. It is important to know that get_tree() will not work if called from nodes that are not in the tree.
Once Scene Tree is created and loads your main scene, calling tree_entered signal and _ready method, your game will finally be
running.
Pausing the Game
111111111
22222222You can call get_tree().set_paused(true) from any nodes inside the tree to pause the game. However, your game will become
unresponsive from that point on, because all nodes inside the tree stop processing. You’ll need to whitelist some nodes that you want
processed regardless of the pause state. Nodes can be set to stop, continue to process, or inherit a parent’s setting via the pause_mode
property. If set to inherit, scene root will stop processing when paused.
Let’s see pausing in action. Create a new node, add a child accept dialog named PauseMenu, and attach the following GDScript to the node,
as shown in Listing 11.1.
LISTING11.1 Simple Counter with Pause Functionality
Click here to view code image
extends Node
var label = Label.new()
var counter = 0
func _ready():
label.text = str(0)
$PauseMenu.dialog_text = “Paused”
$PauseMenu.connect(“popup_hide”, self, “unpause”)
$PauseMenu.popup_exclusive = true
add_child(label)
func _process(delta):
counter += delta
label.text = “%.1f” % counter
func _input(event):
if event is InputEventKey:
if event.scancode == KEY_ESCAPE:
$PauseMenu.popup()
get_tree().set_pause(true)
func unpause():
get_tree().set_pause(false)
The script above creates a label that tells time once the game is launched. When the escape key is pressed, it pops up an accept dialog that
says “paused,” and pauses the game. When the accept dialog is closed, the game resumes. Try running the scene and press “escape.” You’ll
notice that the timer stops when the Scene Tree is paused. This is the expected response (see Figure 11.1).
FIGURE 11.1
Simple game pause dialog.
However, when you try to unpause the game by clicking the “OK” button, it doesn’t let you, because the dialog is also paused.
Now try setting the pause mode of the pause menu to “process” and restart. Alternatively, you can add the following line in _ready, as
shown in Listing 11.2.
LISTING11.2 Setting the Pause Mode to Process
Click here to view code image
$PauseMenu.pause_mode = PAUSE_MODE_PROCESS
The “OK” button should respond to input now, and the timer continues when you press it.
Switching Scenes
It is possible to never switch a scene by using one scene that loads and unloads its children. For simple games, this is overkill. It is advisable
to use Scene Tree to switch scenes.
Scene Tree is responsible for scene switching. You can call get_tree().change_scene(String path) to load a scene and set it as
the current scene. If you want to load the scene beforehand, you can use preload() or load() and save the scene in a variable. Then, call
get_tree().change_scene_to(PackedScene) with that variable as a function parameter.
Let’s try switching scenes. Create two scenes. One has a Sprite with a default robot icon as texture for visualization. Save the Sprite scene as
res://sprite.tscn. Another has a node as the root node with the following GDScript attached, as shown in Listing 11.3.
LISTING11.3 Scene Switching
Click here to view code image
extends Node
var label = Label.new()
func _ready():
label.text = “Press Start”
add_child(label)
111111111
22222222func _input(event):
if event is InputEventKey:
get_tree().change_scene(“res://sprite.tscn”)
Try running the node scene. The text saying “Press Start” should appear (see Figure 11.2).
FIGURE 11.2
Scene switching
Pressing any key should bring you to the Sprite scene (see Figure 11.3).
FIGURE 11.3
Sprite scene
To go back to the node scene, you can add another GDScript to the Sprite node that changes the scene back.
Background Loading
Sometimes, the resource is too large to be loaded in a couple of frames. The game will be unresponsive until the loading is complete. It is
generally undesirable to not provide visual feedback of long operations.
Resource Interactive Loader
Resource Interactive Loader is a class capable of loading Resource in steps. It can be created by the Resource Loader singleton by calling
ResourceLoader.load_interactive(String path).
preload() loads resources at compile time. You may not use variables as the function’s argument, and the file must exist.
load() loads resources at run time. The file does not have to exist at the time of loading, making it useful when loading user data.
Example
The following example will require a scene with many references. You can find it in the official demo projects. Download a project, and copy
both scenes and resources into your project root. Rename the scene file name to “scene.tscn.” Create a new scene and node, then attach the
GDScript shown in Listing 11.4.
LISTING11.4 Background Loading
Click here to view code image
extends Node
var label
var loader
var clock = 0
func _ready():
label = Label.new()
add_child(label)
loader = ResourceLoader.load_interactive(“res://scene.tscn”)
func _process(delta):
clock += delta
var err = loader.poll()
if err == ERR_FILE_EOF:
print(loader.get_resource())
get_tree().quit()
label.text = “%d / %d loaded (%.1f s)” % [loader.get_stage(),
loader.get_stage_count(), clock]
The script above will load the scene interactively in stages and display the progress. Note that it’s greatly slowed down so you can easily see
the progress. Each call to ResourceInteractiveLoader.poll() increases the progress. You might notice that the timer is
continuously counting up. This means the game doesn’t freeze while the resource is being loaded (Figure 11.4).
FIGURE 11.4
Interactively loading a scene.
If the loading is finished, it will return ERR_FILE_EOF. You can get the resource using
ResourceInteractiveLoader.get_resource(). If you want the resource instantly, calling
ResourceInteractiveLoader.wait() will halt the game until the resource is loaded.
Handling a Quit Request
111111111
22222222When a game receives an exit signal, there is a chance the player did not mean to quit, and it can be infuriating if he has not saved for a while.
Loss of game data can adversely affect the reputation of your game. It is a good idea to have a dialog that pops up asking if the player wants
to save first or auto-save when quitting. Any quit requests should be properly handled.
Let’s see how this is made possible with the notification system. Create a new node and attach the GDScript shown in Listing 11.5.
LISTING11.5 Manually Handling a Quit Request
Click here to view code image
extends Node
var dialog = ConfirmationDialog.new()
func _ready():
dialog.dialog_text = “Are you sure?”
dialog.get_ok().text = “Yes”
dialog.get_cancel().text = “No”
dialog.connect(“confirmed”, self, “end”)
add_child(dialog)
get_tree().set_auto_accept_quit(false)
func _notification(what):
if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
dialog.popup()
func end():
get_tree().quit()
The script above will intercept NOTIFICATION_WM_QUIT_REQUEST of Main Loop and respond by popping up the dialog that says, “Are you
sure?” The game will quit when you press the button that says, “Yes” (see Figure 11.5). Otherwise, the game will continue to run.
FIGURE 11.5
Manually handling a “quit” request.
This gives you a chance to save the game before quitting.
Summary
In this hour, you’ve learned about Main Loop and its importance of keeping the engine running. You’ve also learned about a type of Main Loop
called Scene Tree that also manages the scene that is currently playing. You should be able to query Scene Tree to pause the game and
switch the scene. When there’s a large resource that takes time to load, you’ve learned that the Resource Interactive Loader can help load it in
stages. Finally, you know how to intercept a quit request and do things that are necessary before closing.
Q&A
Q. How do you switch between scenes with a transition effect such as fading?
A. The simplest way is to make both scenes children of another scene that applies effect. If that is not applicable, there are several ways to
achieve this. One way is to apply a fade effect to both scenes. Fade out the old scene before switching, and fade in the new scene when
it’s ready. Another way is to add a fade effect on top of both scenes by adding it as a root viewport’s child and making it render on top. If
the effect requires images from the old scene, such as a crossfading effect, you can capture the viewport before switching.
Q. Can you skip the interactive loading and get resources immediately?
A. Yes, by calling ResourceInteractiveLoader.wait().
Q. Can you handle quit requests fromkilling the process?
A. No. The game will exit immediately.
Workshop
Answer the following questions to make sure you understand the content of this hour.
Quiz
1. What is the class that manages the current scene and its nodes?
2. What property of a node should be modified if you want to pause the Scene Tree without the game becoming unresponsive?
3. What is the difference between SceneTree.change_scene() and SceneTree.change_scene_to()?
4. What is returned by ResourceInteractiveLoader.poll() when the resource is done loading?
5. To what class does NOTIFICATION_WM_QUIT_REQUEST belong?
Answers
111111111
222222221. Scene Tree.
2. pause_mode.
3. change_scene() must be given a scene file path; change_scene_to() must be given a PackedScene.
4. ERR_FILE_EOF.
5. Main Loop.
Exercises
Try to execute the following exercises to get more acquainted with Scene Tree:
1. Add a pause dialog to your game or a demo project.
2. Display the pause dialog when a player makes a quit request.
3. Choose two demo projects, merge them into one, and make a main menu with options to choose which to run.