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:
Let’s get started.
First, in the ObstacleCourse scene, we need to add an instance of our customization menu.
Start by adding a new 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:
= savegame.player_1
player_1.settings = savegame.player_2
player_2.settings _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:
# ...
setup(_savegame) ui_customize_character.
That clears the error. Now, we need a way to open and close the menu. That’s what we’ll add next.
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:
show()
ui_customize_character.else:
hide()
ui_customize_character._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.
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:
_input()
function. You’ll use it
for actions that need priority over the game’s user interface._gui_input()
function. This function
only exists on nodes and runs when clicking or using the
keyboard inside menus._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.
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.
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.
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
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:
show()
ui_customize_character.else:
hide()
ui_customize_character._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!
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.
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.