A pause and main menu are a default feature that comes with every game. We don’t want our player to die each time the gamer has to go and get a snack, and we also don’t want to spawn directly into the game! After this part, our player needs to be able to quit and start the game via the main menu, as well as pause the game. The pause screen will be upgraded in the next part to allow our players to save their game. The main screen will also be upgraded in the next part to allow our players to load their saved game.
WHAT YOU WILL LEARN IN THIS PART:
· How to pause and unpause the game state.
· How to quit out of the game project.
PAUSE MENU GUI SETUP
In your Player scene, add a new CanvasLayer node to your UI Layer and call it “PauseScreen”.
To this node, add a ColorRect node, and call it “Menu”. Set the anchor-preset to “Full Rect” and change its color to #365655.
To the Menu node, we will add three buttons renamed to resume, save, and quit.
Now, set each of your button’s sizes to be (x: 180, y: 20) and their anchor presets to be centered.
Then change the positions as follows:
Change each of their fonts to be “Schrödinger” size 20.
Then update their label texts to be like the image below:
MAIN MENU GUI SETUP
Now that our pause menu is created, we can go ahead and create our Main Menu. Our Main Menu will be in its scene because it is this scene that we will load up when we run the game. From here on, we can choose to start a new game, load a saved game, change some settings, or quit the game.
Create a new scene with a Node2D node as its root. We used the same node for our Main scene. Rename it to MainScene and save this scene underneath your Scenes folder.
The rest of this scene is similar to our PauseScreen. Let’s take the same steps as we did in our Pause Screen to create this menu screen (the properties for the button positions can be found below):
Change your ColorRect’s color to #581929 so that we can differentiate it from our Pause Menu.
PAUSE MENU FUNCTIONALITY
For our pause menu, we want the game to pause if the player presses the ESC (escape) key on their keyboard. Let’s start with adding the input for this. Call the new input ui_pause.
In our code, we want to capture the paused state of our game. If we press the ui_pause input, this paused state should be set to true, and the game should be paused. If the game is paused, our pause screen should be shown — so make sure you change its visibility to be hidden. Since we changed our player’s processing mode to “always”, we also need to disable the player’s movement processing.
In our Player script, let’s define a variable that will hold our game’s pause state.
### Player.gd
#older code
#paused state
var paused
Then, in our input() function, let’s set the game to pause and the screen to be shown only if the PauseScreen node is not already visible.
### Player.gd
# UI nodes
@onready var pause_screen = $UI/PauseScreen
func _input(event):
#input event for our attacking, i.e. our shooting
if event.is_action_pressed("ui_attack"):
#checks the current time as the amount of time passed
var now = Time.get_ticks_msec()
#check if player can shoot if the reload time has passed and we have ammo
if now >= bullet_fired_time and ammo_pickup > 0:
#shooting anim
is_attacking = true
var animation = "attack_" + returned_direction(new_direction)
animation_sprite.play(animation)
#bullet fired time to current time
bullet_fired_time = now + bullet_reload_time
#reduce and signal ammo change
ammo_pickup = ammo_pickup - 1
ammo_pickups_updated.emit(ammo_pickup)
#using health consumables
elif event.is_action_pressed("ui_consume_health"):
if health > 0 && health_pickup > 0:
health_pickup = health_pickup - 1
health = min(health + 50, max_health)
health_updated.emit(health, max_health)
health_pickups_updated.emit(health_pickup)
#using stamina consumables
elif event.is_action_pressed("ui_consume_stamina"):
if stamina > 0 && stamina_pickup > 0:
stamina_pickup = stamina_pickup - 1
stamina = min(stamina + 50, max_stamina)
stamina_updated.emit(stamina, max_stamina)
stamina_pickups_updated.emit(stamina_pickup)
#interact with world
elif event.is_action_pressed("ui_interact"):
var target = ray_cast.get_collider()
if target != null:
if target.is_in_group("NPC"):
# Talk to NPC
target.dialog()
return
#go to sleep
if target.name == "Bed":
# play sleep screen
animation_player.play("sleeping")
health = max_health
stamina = max_stamina
health_updated.emit(health, max_health)
stamina_updated.emit(stamina, max_stamina)
return
#show pause menu
if !pause_screen.visible:
if event.is_action_pressed("ui_pause"):
#pause game s
get_tree().paused = true
#show pause screen popup
pause_screen.visible = true
#stops movement processing
set_physics_process(false)
#set pauses state to be true
paused = true
Now, let’s connect each of our buttons — resume, save, quit — pressed() signals to our script.
If the game’s paused state is not true, then we need to unpause our game in our resume button’s on_pressed() function. We also need to allow our player to process its movement again, and we need to hide our PauseScreen node.
### Player.gd
# ---------------- Pause Menu -------------------------------------------
#resume game
func _on_resume_pressed():
#hide pause menu
pause_screen.visible = false
#set pauses state to be false
get_tree().paused = false
paused = false
#accept movement and input
set_process_input(true)
set_physics_process(true)
Then in our quit button’s on_pressed() function, we’ll redirect the player back to the main screen in the MainMenu scene.
### Player.gd
# ---------------- Pause Menu -------------------------------------------
func _on_quit_pressed():
Global.change_scene("res://Scenes/MainScene.tscn")
get_tree().paused = false
We also want to make the cursor show again.
### Player.gd
# ---------------- Pause Menu -------------------------------------------
func _on_quit_pressed():
Global.change_scene("res://Scenes/MainScene.tscn")
get_tree().paused = false
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
We will come back to our save button’s on_pressed() function in the next part when we add saving and loading functions to our game. Whilst we’re at it, let’s take the time to also fix our Game Over screen to redirect our player to the MainMenu scene when they die and press ESC.
Let’s add a new Label node to our GameOver node. This Label will tell our player to press ESC to go back to the Main Menu. Change the font to “Schrodinger” with the size set to 10. Also set its anchor-preset to be “Center Bottom”, and its horizontal and vertical alignment to be centered as well.
In our ui_pause input, let’s update our code to redirect the player to the MainMenu scene if the player’s health is 0.
### Player.gd
func _input(event):
# older code
#show pause menu
if !pause_screen.visible:
if event.is_action_pressed("ui_pause"):
#pause game s
get_tree().paused = true
#show pause screen popup
pause_screen.visible = true
#stops movement processing
set_physics_process(false)
#set pauses state to be true
paused = true
# if the player is dead, go to back to main menu screen
if health <= 0:
get_node("/root/%s" % Global.current_scene_name).queue_free()
Global.change_scene("res://Scenes/MainScene.tscn")
get_tree().paused = false
return
We also need to update our player’s hit() function to pause the game and allow for input if they do die.
### Player.gd
# ------------------- Damage & Death ------------------------------
#does damage to our player
func hit(damage):
health -= damage
health_updated.emit(health, max_health)
if health > 0:
#damage
animation_player.play("damage")
health_updated.emit(health, max_health)
else:
#death
set_process(false)
get_tree().paused = true
paused = true
animation_player.play("game_over")
Make sure your GameOver screen is set to visible. The animation for our game_over screen should be set on keyframe 0.
If you now run your scene, you should be able to pause/unpause your game — and if you die or quit, you should be redirected to your MainScene screen.
We also need to show our cursor whenever the pause screen is visible.
### Player.gd
func _input(event):
# older code
#show pause menu
if !pause_screen.visible:
if event.is_action_pressed("ui_pause"):
#pause game
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
get_tree().paused = true
#show pause screen popup
pause_screen.visible = true
#stops movement processing
set_physics_process(false)
#set pauses state to be true
paused = true
# if the player is dead, go to back to main menu screen
if health <= 0:
get_node("/root/%s" % Global.current_scene_name).queue_free()
Global.change_scene("res://Scenes/MainScene.tscn")
get_tree().paused = false
return
MAIN MENU FUNCTIONALITY
In your MainScene scene, attach a new script to its root node and save it under your Scripts folder.
Connect the pressed() signal from each of your buttons — new, load, and quit — to your new script. We’ll not be adding settings to our game — so the settings button is just there for show!
Let’s add our code to change the scene to our Main scene when we start a new game via the new button, as well as close the game when we press the quit button. We can quit our game via the get_tree().quit() method, which shuts down our entire game window. We’ll add the functionality to load the game in the next part. We also need to show our cursor whenever this scene is active.
### MainScene.gd
extends Node2D
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
# New game
func _on_new_pressed():
Global.change_scene("res://Scenes/Main.tscn")
Global.scene_changed.connect(_on_scene_changed)
# Quit Game
func _on_quit_pressed():
get_tree().quit()
#only after scene has been changed, do we free our resource
func _on_scene_changed():
queue_free()
The last thing that we have to do for now is to change our run scene to our MainScene scene. In your Project Settings > General > Application > Run, change your main scene from “Main” to “MainScene”.
Now if you run your scene, you should open up on the MainMenu scene, and from here you can quit your game or start a new game.
Now our game has a Main Menu and a Pause Menu! Next up, we’re going to be adding the functionality to save and load our game. Remember to save your project, and I’ll see you in the next part.
The final source code for this part should look like this.
FULL TUTORIAL
The tutorial series has 23 chapters. I’ll be releasing all of the chapters in sectional daily parts over the next couple of weeks.
If you like this series or want to skip the wait and access the offline, full version of the tutorial series, you can support me by buying the offline booklet for just $4 on Ko-fi!😊
You can find the updated list of the tutorial links for all 23 parts in this series here.