08.adding-the-menu-to-the-obstacle-course

Adding the menu to the obstacle course

The last missing piece in our game is the customization menu.

In this lesson, we will add the menu to the obstacle course and use Godot’s pause feature to pause the game when the menu is visible.

That will teach you one way to work with menus: overlaying them on top of the running game.

We will tackle it in this order:

  1. Add the customization menu to the obstacle course.
  2. Allow opening and closing the menu to change the characters’ look.
  3. Pause the game when using the menu.

Let’s get started.

Creating and initializing the menu

First, in the ObstacleCourse scene, we need to add an instance of our customization menu.

Start by adding a new CanvasLayer node as a child of ObstacleCourse and name it Menus. In the Inspector, set its Layer property to a high number like 100.

This change ensures that our menu always shows in front of everything else.

Press Ctrl+Shift+A to instantiate the UICustomizeCharacter as a child of the Menus node. This shortcut opens a popup where you can quickly search for scenes in your project.

Then, click the eye icon next to the UICustomizeCharacter node to make it invisible at the start of the game.

If you try to run the game now, you will get an error because the PlayerCharacter instances in the menu need a PlayerSettings resource to work.

We will provide them from the obstacle course script.

But, first, open the UICustomizeCharacter.gd script and define a function to provide settings to both players at once.

func setup(savegame: SaveGame) -> void:
    player_1.settings = savegame.player_1
    player_2.settings = savegame.player_2
    _update_display()

The setup() function allows us to let the menu initialize itself and update its display according to the saved game data. All we need is to give it access to the SaveGame resource.

The place where we load the SaveGame resource is the ObstacleCourse’s script, so we need to call UICustomizeCharacter.setup() from there.

Open the ObstacleCourse.gd script where we get the UICustomizeCharacter node and call its setup() function.

onready var ui_customize_character := $Menus/UICustomizeCharacter

func _ready() -> void:
    # ...
    ui_customize_character.setup(_savegame)

That clears the error. Now, we need a way to open and close the menu. That’s what we’ll add next.

Opening and closing the character customization menu

We need to add an input to open and close the menu.

Go to Project -> Project Settings… -> Input Map and add a new action named toggle_options_menu. Assign it a key or gamepad button. We went with Escape on the keyboard.

Then, reopen the ObstacleCourse.gd script where we toggle the menu’s visibility using the _input() function.

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("toggle_options_menu"):
        if not ui_customize_character.visible:
            ui_customize_character.show()
        else:
            ui_customize_character.hide()
            _savegame.write_savegame()

We use the show() and hide() functions to toggle physics processing on the menu’s playable characters. If you look at the UICustomizeCharacter.gd script, you’ll find that we have overrides for these two functions.

# All Control nodes have a show() and hide() function we can override to do
# extra stuff when making a menu visible or hiding it.
#
# Here, we prevent the characters from moving when making the menu invisible.
func show():
    .show()
    _toggle_players_active(true)


func hide():
    .hide()
    _toggle_players_active(false)

With this change, you can already open and close the menu.

Why do we need to use the _input() function?

We saw the _unhandled_input() function before. It is part of the same family as _input().

Godot has three input functions that receive input events one by one. They each get called with a different priority:

  1. First, Godot calls the _input() function. You’ll use it for actions that need priority over the game’s user interface.
  2. Then comes the _gui_input() function. This function only exists on Control nodes and runs when clicking or using the keyboard inside menus.
  3. Finally, you have _unhandled_input(). It’s the recommended function for gameplay.

These functions can consume input events, preventing other functions and other nodes from receiving them.

User interface nodes automatically consume events when navigating menus or clicking on buttons, preventing other nodes from receiving input events.

That’s why we use the _input() function here. If we put our code in _unhandled_input(), we would be able to open the menu but not close it.

Practice: Equipping weapons

Open the practice Equipping weapons.

In this practice, the Inventory resource is shared between the inventory screen and a character visualisation to the right.

When equipping an item in the left or right-hand slot, we want that change to be reflected on the character preview on the right.

Pausing and unpausing in Godot

We have one remaining problem. After the countdown animation, if you open the menu, moving the characters in the menu will also move the characters in the game level.

When we open the menu, all we are doing is showing it on top of the game level. We are not removing the game level or the game characters behind it, so their _physics_process() function keeps running.

We need to stop processing in the level while the menu is open. We could call set_physics_process() on each character, but there’s a better solution: Godot’s pause feature.

You can get the SceneTree object by calling get_tree() and setting its paused property to true to pause the game.

Feel free to try in the ObstacleCourse’s _ready() function.

func _ready() -> void:
    # ...
    get_tree().paused = true

If you do that, no button press works anymore in the game. It’s normal: you paused the entire game, so none of your code is running anymore.

What you want is for the pause only to affect the game level.

You can pause when opening the menu, and the level’s characters will not move while the menu is visible.

You need to change the Pause Mode property of nodes to do that.

Changing the nodes’ pause mode

By default, every node’s Pause Mode is set to Inherit, which causes all nodes to stop when pausing the game.

Select the ObstacleCourse node and find the Pause Mode property at the bottom of the Inspector.

Set the Pause Mode to Process so the ObstacleCourse’s input function still works and allows us to open and close the menu.

Then, select the Course node and set its Pause Mode to Stop.

We don’t want the Course or its children to keep updating while the menu is open. This will prevent players from inadvertently moving characters in the level.

We also want the game timer to stop, so select the Timer node and set its Pause Mode to Stop.

All the other nodes will keep processing, including the character customization menu.

We can now head back to the ObstacleCourse.gd script to toggle the SceneTree.paused property when opening and closing the menu.

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("toggle_options_menu"):
        # ...
        get_tree().paused = ui_customize_character.visible

Be careful to add this new line of code after the lines where we open and close the menu. Your _input() function should look like this:

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("toggle_options_menu"):
        if not ui_customize_character.visible:
            ui_customize_character.show()
        else:
            ui_customize_character.hide()
            _savegame.write_savegame()
        get_tree().paused = ui_customize_character.visible

And that’s it. You can now run the game, use the menu, and see character customizations show in the running game.

Even better, you can stop the game, and the next time to run it, your characters will preserve any changes made through the menu!

Key takeaways

You practiced an essential programming skill in this series: abstraction.

You took a specific scene representing the player with everything hard-coded, and turned it into a more general character you can use for two to four-player co-op.

All you had to do was move some input actions into an editable resource.

You replaced some code with data: the actions stored in each resource file.

Abstraction is often presented in complicated ways in programming, and people tend to do it before they need to.

But like you did in the series, you can often quickly turn something very specific and hard-coded into something more flexible.

Looking back

If you have a friend or a loved one close by, now’s a good time to play together!

The game’s not perfect, but it’s pretty fun to make a run and see who wins.

This completes a pretty complex series. You can be proud of yourself because this one was already not quite beginner-level anymore.

With that, you learned everything you need to complete a game: physics, movement, input, processing, resources, saving and loading, and even a bit of user interface.

That does not mean you know everything there is to know about code or Godot. You’re at the start of a lifelong learning journey.

But you have already made excellent progress, which you will put to the test in the course’s final challenge.