One thing you’ll find in almost all games is the pause menu, which usually has a number of controls such as “resume game”, “restart level”, “return to main menu”, “game settings”, etc. In this tutorial, we’ll learn how to create and script a pause menu for your game.
The pause menu we’ll make for our game will contain these 3 menu items:
- Resume game
- Restart game
The player will open the menu by pressing the escape key or start/options button on the joypad, then he can navigate it with the arrow keys or D-Pad and select the highlighted item by pressing the space bar or joypad’s attack button.
Drawing the menu GUI
The final result we want to achieve is this:
Let’s see how to do it!
Add a Popup node to CanvasLayer an call it MenuPopup. Make it visibile by clicking on the eye icon next to the node name.
Since the menu must work when the game is paused, set MenuPopup’s Pause → Mode property to Process. Also, set Exclusive to On to prevent the popup to close in response to mouse events.
Add a ColorRect node to MenuPopup. Set Rect → Position to (60, 40) and Rect → Size to (200, 100), then set the Color property to (80,80,80,255).
Add another ColorRect to MenuPopup and rename it Resume. Set Rect → Position to (70, 50) and Rect → Size to (180, 20). Now add a Label to Resume. In the Custom Font property load the font Font.tres (you’ll find it in the Fonts folder) and in Custom Colors set Font Color to black. Write RESUME GAME in the Text property of the label, then set both Align and Valign to Center. Finally, set Rect → Size to (180, 20).
Now, duplicate Resume two times. Rename one copy to Restart and the other to Quit. You’ll end up with this node structure for MenuPopup:
Set Restart‘s Rect → Position to (70, 80), then change its label’s text to RESTART GAME. Select the Quit node and set its Rect → Position to (70, 110). Finally, change its label’s text to QUIT.
Attach a new script to MenuPopup and save it as MenuPopup.gd in the GUI folder.
First, let’s add some variables:
onready var player = get_node("/root/Root/Player") var already_paused var selected_menu
The player variable will contain a reference to the Player node. To initialize it, we used a new keyword: onready. The onready keyword defers initialization of a variable until _ready() is called. That single line is a compact way to write the following code:
var player func _ready(): player = get_node("/root/Root/Player")
The already_paused variable is used to store if the game was already paused when the menu was called. If so, we don’t have to un-pause the game when leaving the menu (for example, the game is already paused if we open the menu during a conversation with an NPC).
The last variable, selected_menu, stores the currently highlighted menu item.
Before writing the code that handles the menu input, let’s write a function that set the color of the menu items, in order to highlight the current one:
func change_menu_color(): $Resume.color = Color.gray $Restart.color = Color.gray $Quit.color = Color.gray match selected_menu: 0: $Resume.color = Color.greenyellow 1: $Restart.color = Color.greenyellow 2: $Quit.color = Color.greenyellow
The first three lines set the menu items to the default color. Then, the match statement is used to check which item is highlighted to change its color.
To handle the input, the first thing we need to do is create a new input action for opening the menu. Click on the Project → Project Settings menu and go to the Input Map tab. Here, you have to create a new action called menu, to which you will assign two events: the Escape key and Device 0, Button 11 event of the joypad (Start on Xbox, Options on PlayStation).
Now we can start writing the basic structure of the _input () function:
func _input(event): if not visible: if Input.is_action_just_pressed("menu"): pass #TODO else: if Input.is_action_just_pressed("ui_down"): pass #TODO elif Input.is_action_just_pressed("ui_up"): pass #TODO elif Input.is_action_just_pressed("attack"): match selected_menu: 0: pass #TODO 1: pass #TODO 2: pass #TODO
The function handles two possible states:
- the menu is hidden: if the player presses the menu button, we want to pause the game and show the menu.
- the menu is visible: we want to navigate the menu with the up and down arrow keys and select the highlighted item with the space bar.
Let’s start from the first state. Add this code for when the menu is hidden and the player open the menu:
# Pause game get_tree().paused = true # Reset the popup selected_menu = 0 change_menu_color() # Show popup player.set_process_input(false) popup()
This code does the following:
- pause the game setting the paused property of the game tree to true
- reset the menu by choosing the first item and calling the change_menu_color() function
- disables the input processing for Player
- finally, shows the menu calling the popup() function.
When the menu is visible, we have to handle the ui_up and ui_down input events. For ui_down, enter this code:
selected_menu = (selected_menu + 1) % 3; change_menu_color()
To select the next menu item, you need to increase the value of the selected_menu variable. This works until the selected item is the last one: in that case, selected_menu is 2 and increasing it would go to item 3, that does not exist. If you want a menu where going forward from the last menu item bring you back to the first one, a compact way to do it is to use the modulo (%) operation.
Once the selected menu has changed, the function calls change_menu_color() to update the color of the menu items.
The code for ui_up is similar, but in this case we cannot use the modulo operation. Instead, we will use the if statement to check what is item the current menu item:
if selected_menu > 0: selected_menu = selected_menu - 1 else: selected_menu = 2 change_menu_color()
When the space bar or attack joypad button is pressed, we will execute the code for the highlighted menu item. For case 0 of the match statement (Resume Game), enter this code:
# Resume game if not already_paused: get_tree().paused = false player.set_process_input(true) hide()
To resume the game, this code set the pause property of the game tree to false (but only if the game was not already paused), reactivates the input processing for the Player node and hides the menu.
Restarting the game is simple: we must simply reload the main scene to bring everything back to the initial state of the game and then remove the pause. So, for case 1 of the match statement enter this code:
# Restart game get_tree().change_scene("res://Scenes/Main.tscn") get_tree().paused = false
Quitting the game is even simpler: just call the quit() function of the game tree. Add this code for the case 2 of the match statement:
# Quit game get_tree().quit()
For convenience, I report below the final code of the _input () function:
func _input(event): if not visible: if Input.is_action_just_pressed("menu"): # Pause game already_paused = get_tree().paused get_tree().paused = true # Reset the popup selected_menu = 0 change_menu_color() # Show popup player.set_process_input(false) popup() else: if Input.is_action_just_pressed("ui_down"): selected_menu = (selected_menu + 1) % 3; change_menu_color() elif Input.is_action_just_pressed("ui_up"): if selected_menu > 0: selected_menu = selected_menu - 1 else: selected_menu = 2 change_menu_color() elif Input.is_action_just_pressed("attack"): match selected_menu: 0: # Resume game if not already_paused: get_tree().paused = false player.set_process_input(true) hide() 1: # Restart game get_tree().change_scene("res://Scenes/Main.tscn") get_tree().paused = false 2: # Quit game get_tree().quit()
In this tutorial we have learned how to make a simple pause menu. As an exercise, you can try making a similar menu on the Game Over screen.
You can try the game with the new menu by clicking here (obviously Quit doesn’t work in the browser):
In the next tutorial we will learn how to export the game to create the executable and the HTML5 versions.