让我们通过制作 RPG 来学习 Godot 4 — 第 19 部分:暂停菜单和主菜单
暂停菜单和主菜单是每款游戏的默认功能。我们不希望玩家每次去拿零食时都会死亡,也不希望游戏直接生成到游戏中!在这一部分之后,我们的玩家需要通过主菜单来退出和启动游戏,并且能够暂停游戏。暂停屏幕将在下一部分升级,以允许玩家保存游戏。主屏幕也将在下一部分升级,以允许玩家加载已保存的游戏。
你将学到什么:
- 如何暂停和恢复游戏状态。
- 如何退出游戏项目。
暂停菜单 GUI 设置
在你的玩家场景中,向 UI 层添加一个新的 CanvasLayer
节点,并将其命名为 PauseScreen
。
在该节点下,添加一个 ColorRect
节点,并将其命名为 Menu
。将其锚点预设设置为“Full Rect”,并将其颜色更改为 #365655
。
在 Menu
节点下,我们将添加三个按钮,分别重命名为 resume
、save
和 quit
。
现在,将每个按钮的大小设置为 (x: 180, y: 20)
,并将它们的锚点预设设置为居中。
然后按如下方式调整它们的位置:
将每个按钮的字体更改为“Schrödinger”,大小为 20。
然后更新它们的标签文本,如下图所示:
主菜单 GUI 设置
现在我们的暂停菜单已经创建好了,我们可以继续创建主菜单。主菜单将位于其自己的场景中,因为这是我们运行游戏时将加载的场景。从这里,我们可以选择开始新游戏、加载已保存的游戏、更改一些设置或退出游戏。
创建一个新场景,并将其根节点设置为 Node2D
。我们为主场景使用了相同的节点。将其重命名为 MainScene
,并将此场景保存到你的 Scenes
文件夹中。
此场景的其余部分与我们的 PauseScreen
类似。让我们按照在暂停屏幕中相同的步骤来创建此菜单屏幕(按钮位置属性如下):
将 ColorRect
的颜色更改为 #581929
,以便我们可以将其与暂停菜单区分开来。
暂停菜单功能
对于我们的暂停菜单,我们希望当玩家按下键盘上的 ESC
(退出)键时,游戏暂停。让我们从添加此输入开始。将新输入命名为 ui_pause
。
在我们的代码中,我们希望捕获游戏的暂停状态。如果我们按下 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'节点。
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”,水平和垂直对齐也设置为居中。
在我们的 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。
如果现在运行你的场景,你应该能够暂停/取消暂停游戏——如果你死亡或退出,你应该被重定向到 MainScene
屏幕。
我们还需要在暂停屏幕可见时显示光标。
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
文件夹中。
将每个按钮(new
、load
、quit
)的 pressed()
信号连接到你的新脚本。我们不会在游戏中添加设置——因此设置按钮只是为了展示!
让我们添加代码,以便在通过 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”。
现在,如果你运行你的场景,你应该会在 MainMenu
场景中打开,从这里你可以退出游戏或开始新游戏。
现在我们的游戏有了主菜单和暂停菜单!接下来,我们将添加保存和加载游戏的功能。记得保存你的项目,我们下一部分见!