暂停菜单和主菜单是每款游戏的默认功能。我们不希望玩家每次去拿零食时都会死亡,也不希望游戏直接生成到游戏中!在这一部分之后,我们的玩家需要通过主菜单来退出和启动游戏,并且能够暂停游戏。暂停屏幕将在下一部分升级,以允许玩家保存游戏。主屏幕也将在下一部分升级,以允许玩家加载已保存的游戏。

你将学到什么:

  • 如何暂停和恢复游戏状态。
  • 如何退出游戏项目。

暂停菜单 GUI 设置

在你的玩家场景中,向 UI 层添加一个新的 CanvasLayer 节点,并将其命名为 PauseScreen
1.jpg
在该节点下,添加一个 ColorRect 节点,并将其命名为 Menu。将其锚点预设设置为“Full Rect”,并将其颜色更改为 #365655
2.jpg
3.jpg
Menu 节点下,我们将添加三个按钮,分别重命名为 resumesavequit
4.jpg
现在,将每个按钮的大小设置为 (x: 180, y: 20),并将它们的锚点预设设置为居中。
5.jpg
然后按如下方式调整它们的位置:
6.jpg
7.jpg
8.jpg
将每个按钮的字体更改为“Schrödinger”,大小为 20。
9.jpg
然后更新它们的标签文本,如下图所示:

10.jpg

主菜单 GUI 设置

现在我们的暂停菜单已经创建好了,我们可以继续创建主菜单。主菜单将位于其自己的场景中,因为这是我们运行游戏时将加载的场景。从这里,我们可以选择开始新游戏、加载已保存的游戏、更改一些设置或退出游戏。

创建一个新场景,并将其根节点设置为 Node2D。我们为主场景使用了相同的节点。将其重命名为 MainScene,并将此场景保存到你的 Scenes 文件夹中。
11.jpg
此场景的其余部分与我们的 PauseScreen 类似。让我们按照在暂停屏幕中相同的步骤来创建此菜单屏幕(按钮位置属性如下):
12.jpg
13.jpg
14.jpg
15.jpg
16.jpg
ColorRect 的颜色更改为 #581929,以便我们可以将其与暂停菜单区分开来。

17.jpg

暂停菜单功能

对于我们的暂停菜单,我们希望当玩家按下键盘上的 ESC(退出)键时,游戏暂停。让我们从添加此输入开始。将新输入命名为 ui_pause
18.jpg
在我们的代码中,我们希望捕获游戏的暂停状态。如果我们按下 ui_pause 输入,此暂停状态应设置为 true,并且游戏应暂停。如果游戏暂停,我们的暂停屏幕应显示——因此请确保将其可见性更改为隐藏。由于我们将玩家的处理模式更改为“始终”,我们还需要禁用玩家的移动处理。

在玩家脚本中,让我们定义一个变量来保存游戏的暂停状态。

Player.gd

# 旧代码

# 暂停状态
var paused

然后,在我们的 input() 函数中,让我们设置游戏暂停并显示屏幕,前提是 PauseScreen 节点尚未可见。

Player.gd

# UI 节点
@onready var pause_screen = $UI/PauseScreen   

func _input(event):
    # 输入事件用于攻击,即射击
    if event.is_action_pressed("ui_attack"):
        # 检查当前时间
        var now = Time.get_ticks_msec()
        # 检查玩家是否可以射击,如果装填时间已过且有弹药
        if now >= bullet_fired_time and ammo_pickup > 0:
            # 射击动画
            is_attacking = true
            var animation  = "attack_" + returned_direction(new_direction)
            animation_sprite.play(animation)
            # 将子弹发射时间设置为当前时间
            bullet_fired_time = now + bullet_reload_time
            # 减少弹药并发出信号
            ammo_pickup = ammo_pickup - 1
            ammo_pickups_updated.emit(ammo_pickup)
    # 使用生命值消耗品
    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) 
    # 使用耐力消耗品      
    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)
    # 与世界互动         
    elif event.is_action_pressed("ui_interact"):
        var target = ray_cast.get_collider()
        if target != null:
            if target.is_in_group("NPC"):
                # 与 NPC 对话
                target.dialog()
                return     
            # 睡觉
            if target.name == "Bed":
                # 播放睡眠屏幕
                animation_player.play("sleeping")
                health = max_health
                stamina = max_stamina
                health_updated.emit(health, max_health)
                stamina_updated.emit(stamina, max_stamina)
                return
    # 显示暂停菜单
    if !pause_screen.visible:
        if event.is_action_pressed("ui_pause"):
            # 暂停游戏
            get_tree().paused = true
            # 显示暂停屏幕弹出窗口
            pause_screen.visible = true
            # 停止移动处理
            set_physics_process(false)
            # 将暂停状态设置为 true
            paused = true

现在,让我们将每个按钮('resume'、'save'、'quit')的 pressed() 信号连接到我们的脚本。

如果游戏的暂停状态不为 'true',那么我们需要在 'resume' 按钮的 'on_pressed()'函数中取消暂停游戏。我们还需要允许玩家处理其移动,并隐藏 'PauseScreen'节点。
19.jpg

Player.gd

# ---------------- 暂停菜单 -------------------------------------------
# 恢复游戏
func _on_resume_pressed():
    # 隐藏暂停菜单
    pause_screen.visible = false
    # 将暂停状态设置为 false
    get_tree().paused = false
    paused = false
    # 接受移动和输入
    set_process_input(true)
    set_physics_process(true)

然后,在我们的 quit 按钮的 on_pressed() 函数中,我们将玩家重定向回 MainMenu 场景中的主屏幕。

Player.gd

# ---------------- 暂停菜单 -------------------------------------------
func _on_quit_pressed():
    Global.change_scene("res://Scenes/MainScene.tscn")
    get_tree().paused = false

我们还希望再次显示光标。

Player.gd

# ---------------- 暂停菜单 -------------------------------------------
func _on_quit_pressed():
    Global.change_scene("res://Scenes/MainScene.tscn")
    get_tree().paused = false
    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

我们将在下一部分中添加保存和加载功能时,回到 save 按钮的 on_pressed() 函数。与此同时,让我们花时间修复我们的游戏结束屏幕,以便玩家死亡并按下 ESC 时重定向到 MainMenu 场景。

让我们向 GameOver 节点添加一个新的 Label 节点。此标签将告诉玩家按 ESC 返回主菜单。将字体更改为“Schrodinger”,大小设置为 10。并将其锚点预设设置为“Center Bottom”,水平和垂直对齐也设置为居中。
21.jpg
在我们的 ui_pause 输入中,让我们更新代码,以便在玩家生命值为 0 时将玩家重定向到 MainMenu 场景。

Player.gd

func _input(event):
    # 旧代码
    # 显示暂停菜单
    if !pause_screen.visible:
        if event.is_action_pressed("ui_pause"):
            # 暂停游戏
            get_tree().paused = true
            # 显示暂停屏幕弹出窗口
            pause_screen.visible = true
            # 停止移动处理
            set_physics_process(false)
            # 将暂停状态设置为 true
            paused = true
            
            # 如果玩家死亡,返回主菜单屏幕
            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

我们还需要更新玩家的 hit() 函数,以便在他们死亡时暂停游戏并允许输入。

Player.gd

# ------------------- 伤害与死亡 ------------------------------
# 对玩家造成伤害
func hit(damage):
    health -= damage    
    health_updated.emit(health, max_health)
    if health > 0:
        # 伤害
        animation_player.play("damage")
        health_updated.emit(health, max_health)
    else:
        # 死亡
        set_process(false)
        get_tree().paused = true
        paused = true
        animation_player.play("game_over")

确保你的 GameOver 屏幕设置为可见。我们的 game_over 屏幕的动画应设置在关键帧 0。
22.jpg
23.jpg
如果现在运行你的场景,你应该能够暂停/取消暂停游戏——如果你死亡或退出,你应该被重定向到 MainScene 屏幕。
24.jpg
24.jpg
25.jpg
26.jpg
我们还需要在暂停屏幕可见时显示光标。

Player.gd

func _input(event):
    # 旧代码

    # 显示暂停菜单
    if !pause_screen.visible:
        if event.is_action_pressed("ui_pause"):
            # 暂停游戏
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            get_tree().paused = true
            # 显示暂停屏幕弹出窗口
            pause_screen.visible = true
            # 停止移动处理
            set_physics_process(false)
            # 将暂停状态设置为 true
            paused = true
            
            # 如果玩家死亡,返回主菜单屏幕
            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

主菜单功能

在你的 MainScene 场景中,将一个新脚本附加到其根节点,并将其保存到你的 Scripts 文件夹中。
27.jpg
将每个按钮(newloadquit)的 pressed() 信号连接到你的新脚本。我们不会在游戏中添加设置——因此设置按钮只是为了展示!
28.jpg
让我们添加代码,以便在通过 new 按钮开始新游戏时更改场景到我们的主场景,并在按下 quit 按钮时关闭游戏。我们可以通过 get_tree().quit() 方法退出游戏,这将关闭整个游戏窗口。我们将在下一部分中添加加载游戏的功能。我们还需要在此场景激活时显示光标。

MainScene.gd

extends Node2D

func _ready():
    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

# 新游戏
func _on_new_pressed():
    Global.change_scene("res://Scenes/Main.tscn")
    Global.scene_changed.connect(_on_scene_changed)

# 退出游戏
func _on_quit_pressed():
    get_tree().quit()
    
# 只有在场景更改后,我们才释放资源     
func _on_scene_changed():
    queue_free()

最后,我们需要将运行场景更改为 MainScene 场景。在你的项目设置 > 常规 > 应用程序 > 运行中,将主场景从“Main”更改为“MainScene”。
29.jpg
现在,如果你运行你的场景,你应该会在 MainMenu 场景中打开,从这里你可以退出游戏或开始新游戏。
30.jpg

31.jpg

现在我们的游戏有了主菜单和暂停菜单!接下来,我们将添加保存和加载游戏的功能。记得保存你的项目,我们下一部分见!

标签: Godot, 2D游戏, 游戏开发, 游戏自学

添加新评论