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
  • Quit

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.

Menu script

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()

Conclusions

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.


10 Comments

Avatar

Jamess · February 13, 2020 at 12:45 pm

Hey Davide Pesce!

I am having some problems. I tried writing the codes and followed everything but the thing is not just popping up( Resume,restart, and quit game). Then I tried to downloading the file from github and import it in, it is still not popping up. I actually don’t know what the problem is.

It would be wonderful if you actually look into this. The tutorials are good though, enjoying the new progress we making. I’ll be glad to hear from you as soon as possible.

Thank you!

    Avatar

    Davide Pesce · February 13, 2020 at 1:01 pm

    I double-checked the version on GitHub and it works for me… what version of Godot are you using? On what operating system?

Avatar

James · February 17, 2020 at 11:42 am

Hey Davide Pesce!

I tried to run it again and it showed the same problem.
The Godot Engine I am using is v3.1.10 , November 27 2019.
The PC I am using is Windows 10 Pro.

Or Perhaps something is just wrong, but it has been working perfectly so far.

Thank you!

    Avatar

    Davide Pesce · February 17, 2020 at 12:07 pm

    Try upgrading to Godot 3.2 to see if anything changes. If it still doesn’t work, place the print function at various points of the script to understand which parts of the script are executed and which are not.

Avatar

James · February 18, 2020 at 11:08 am

Thanks again Davide Pesce

I’ll upgrade the Godot and will let you know what happens next.

    Avatar

    Mateo · June 28, 2020 at 9:41 pm

    Hey,
    I have found what is the problem with this MenuPopup.
    You have to activate “Exclusive” in the Popup section of your MenuPopup. Try it, it works for me.
    Good luck !

Avatar

Chris Truebe · April 4, 2020 at 10:46 pm

I’m really liking these tutorials! You’re UI is much more in depth than any I’ve seen on Youtube. However, this Menu gave me quite the annoyance. I do most of my testing with a DS4 connected to a MacBook Pro 2019 Catelina.

Throughout this tutorial I’ve been changing the KEY handles to ui_** so it could accept the DS4 natively with the Project Settings. While I had issues with the Dialogue boxes I was eventually able to fix those with some yield(get_tree().create_timer(0.4) “timeout”) and some re-maps. This Menu one had me stumped for quite a time.

After much frustration and an hour of re-mapping things as well as recoding the MenuPopup.gd I finally found the issue. You call the (if event is InputEventKey:) that I had been changing to InputEvent. This did not jive well with the InputJoyMap and the just_pressed method. I eventually erased the var just_pressed = event.pressed and not event.echo as well as all method calls for just_pressed and it worked great.

Thanks for making these Tuts!

    Avatar

    Davide Pesce · April 5, 2020 at 10:14 am

    Hi Chris, thanks for your comment!
    For some reason I had totally forgotten the joypad input! I updated the code of this tutorial to support it. It’s actually simpler than before, because you no longer have to check for continuous key presses.

Avatar

Chris · April 29, 2020 at 2:30 am

I came back to this and attempted to create my own GameOver menu like you suggested. I essentially copied the menupopup exactly but changed the matchs to what I wanted them to say. The only problem I’m having is that any input I try to use for the GameOver gets shunted to the dialoguePopup. It works perfectly fine for the MenuPopup, but the exact same process screws over the GameOver.

Any help is appreciated! Thanks!

    Avatar

    Davide Pesce · April 29, 2020 at 9:48 am

    Some generic suggestions:

    1) try to use the set_process_input function to enable input only in active dialogs and to disable it elsewhere

    2) you can learn more about how inputs work in Godot here. I advise you to look above all at how _unhandled_input works and the set_input_as_handled() function, which allows you to stop the propagation of inputs to other nodes.

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close