DEV Community

Cover image for Let’s Learn Godot 4 by Making an RPG — Part 19: Pause Menu & Main Menu🤠
christine
christine

Posted on • Updated on

Let’s Learn Godot 4 by Making an RPG — Part 19: Pause Menu & Main Menu🤠

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”.

Godot RPG

To this node, add a ColorRect node, and call it “Menu”. Set the anchor-preset to “Full Rect” and change its color to #365655.

Godot RPG

Godot RPG

To the Menu node, we will add three buttons renamed to resume, save, and quit.

Godot RPG

Now, set each of your button’s sizes to be (x: 180, y: 20) and their anchor presets to be centered.

Godot RPG

Then change the positions as follows:

Godot RPG

Godot RPG

Godot RPG

Change each of their fonts to be “Schrödinger” size 20.

Godot RPG

Then update their label texts to be like the image below:

Godot RPG

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.

Godot RPG

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

Godot RPG

Godot RPG

Godot RPG

Godot RPG

Godot RPG

Change your ColorRect’s color to #581929 so that we can differentiate it from our Pause Menu.

Godot RPG

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.

Godot RPG

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Now, let’s connect each of our buttons — resume, save, quit — pressed() signals to our script.

Godot RPG

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.

Godot RPG




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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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.

Godot RPG

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Make sure your GameOver screen is set to visible. The animation for our game_over screen should be set on keyframe 0.

Godot RPG

Godot RPG

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.

Godot RPG

Godot RPG

Godot RPG

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


Enter fullscreen mode Exit fullscreen mode

MAIN MENU FUNCTIONALITY

In your MainScene scene, attach a new script to its root node and save it under your Scripts folder.

Godot RPG

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!

Godot RPG

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


Enter fullscreen mode Exit fullscreen mode

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”.

Godot RPG

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.

Godot RPG

Godot RPG

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.

Buy Me a Coffee at ko-fi.com


FULL TUTORIAL

Godot RPG

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.

Top comments (2)

Collapse
 
facepalm profile image
Chris

Some of the texts in the label appears blurry. It is using same font and size. If I change one of the blurry text (NEW GAME) to different one that seems to appear clear (LOAD GAME) it is clear again.

Image description

I did some search and it seems that the only solution is to either use different font or increase size... Is there any other way to get it to work?

P.S. I am using Godot 4.1

Collapse
 
wen1223 profile image
Huang

Hello! Thanks for the great tutorial! I encountered the problem after I paused the game. I cannot get mouse input after setting get_tree().paused = true. How do I fix it?