写游戏项目时很多 Godot 新手会先急着做角色控制、战斗系统结果做到一半才发现玩家打开游戏的第一眼看到的却是空荡荡的 3D 世界没有开始按钮也没有任何操作提示。游戏主页和游戏引导其实是决定玩家第一印象的关键模块。本文基于 Godot 3D 从零实现一套“主菜单 新手引导”的完整流程包含场景搭建、UI 布局、信号连接、3D 场景切换、引导数据管理等内容适合刚接触 Godot 的开发者参考。1. 为什么需要游戏主页与游戏引导1.1 游戏主页不是“一个按钮”那么简单游戏主页Main Menu是玩家运行游戏后看到的第一个界面。它通常包含游戏 Logo 或背景画面“开始游戏”按钮“继续游戏”按钮如果有存档“设置”按钮“退出游戏”按钮版本号、版权信息在 3D 游戏中主页可以是一张漂亮的 2D UI 面板也可以是一个可旋转的 3D 场景。Godot 中更常见的做法是主体用 Control 节点搭建 UI背景用 TextureRect 展示图片或者用 SubViewport 渲染 3D 场景作为动态背景。主页的核心职责是“引导玩家进入游戏状态”。它需要处理好场景切换、按钮信号、输入事件以及窗口关闭逻辑。如果主页做得很粗糙即使 3D 内容再精彩玩家也可能在第一眼就流失。1.2 游戏引导让玩家知道“该干嘛”游戏引导Tutorial是新手教学系统。它解决以下问题玩家不知道如何移动、跳跃、交互。玩家不知道当前任务目标。玩家面对复杂的 UI 界面不知道从哪下手。玩家容易在第一章就迷路或卡关。好的引导不是把所有文字一次性抛给玩家而是分步骤、按上下文、适时弹出。比如进入游戏后先显示“使用 WASD 移动”当玩家走到某个区域后再提示“按 E 打开宝箱”。在 Godot 3D 中引导可以表现为屏幕中央的文本提示。指向 3D 物体的箭头或高亮框。任务目标 UI。对话气泡。本文会实现一个最简单的通用分步引导层然后把它挂到 3D 游戏场景上。这样你可以在任何场景中复用同一套引导逻辑。2. 环境准备与项目结构2.1 Godot 版本说明本文以Godot 4.x的 GDScript 语法为例编写代码。Godot 3.x 虽然也能实现同样的功能但部分 API 名称不同例如change_scene_to_file()在 Godot 3 中是change_scene_to()。onready在 Godot 3 中写作onready var。信号连接方式在 Godot 3 中可以通过connect()或编辑器绑定。如果你使用的是 Godot 3.x请对照调整。下面提供的代码重点演示设计思路而不是死磕版本差异。2.2 创建项目目录打开 Godot 项目管理器新建一个空项目。建议先规划好目录结构godot3d-tutorial/ ├── scenes/ │ ├── main_menu.tscn │ ├── game.tscn │ ├── player.tscn │ └── tutorial_layer.tscn ├── scripts/ │ ├── main_menu.gd │ ├── game.gd │ └── tutorial_layer.gd ├── assets/ │ ├── images/ │ └── audio/ ├── autoload/ │ └── game_state.gd └── project.godotautoload目录存放全局单例脚本用于在场景切换时保留数据例如“是否已经看过引导”。3. 游戏主页主菜单的实现3.1 创建主菜单场景新建场景scenes/main_menu.tscn根节点类型选择Control命名为MainMenu。场景结构如下MainMenu (Control) ├── Background (TextureRect) ├── CenterContainer │ └── VBoxContainer │ ├── TitleLabel (Label) │ ├── StartButton (Button) │ ├── OptionsButton (Button) │ └── QuitButton (Button)这里使用Control作为根节点可以方便地进行 UI 布局。TextureRect可以放背景图如果没有素材也可以用ColorRect填充纯色背景。在VBoxContainer中设置每个按钮的自定义最小高度例如 60 像素并设置字号为 24 左右。标题文字使用你游戏的实际名称字体推荐使用支持中文的字体否则中文字符可能显示为方块。3.2 编写主菜单脚本为MainMenu根节点挂载脚本main_menu.gd。extends Control onready var start_button: Button $CenterContainer/VBoxContainer/StartButton onready var options_button: Button $CenterContainer/VBoxContainer/OptionsButton onready var quit_button: Button $CenterContainer/VBoxContainer/QuitButton func _ready() - void: start_button.pressed.connect(_on_start_pressed) options_button.pressed.connect(_on_options_pressed) quit_button.pressed.connect(_on_quit_pressed) # 将鼠标设置为可见并采用默认可视状态 Input.mouse_mode Input.MOUSE_MODE_VISIBLE func _on_start_pressed() - void: # 切换到主游戏场景 get_tree().change_scene_to_file(res://scenes/game.tscn) func _on_options_pressed() - void: # 这里可以打开一个设置面板本文省略 print(打开设置) func _on_quit_pressed() - void: # 退出游戏在编辑器中运行无效发布后才能生效 get_tree().quit()代码说明onready会在节点进入场景树后自动获取子节点引用。.pressed.connect()是 Godot 4 推荐的信号绑定方式。Input.mouse_mode设置为可见避免玩家从游戏场景返回主页后鼠标被隐藏。change_scene_to_file()会卸载当前场景并加载目标场景。如果想要保留主菜单的背景音乐可以把它放到全局单例中。3.3 为 3D 游戏打造动态背景如果不想用静态图片可以创建第二个场景作为背景然后用SubViewport渲染 3D 画面。具体做法新建一个 3D 场景里面放一个旋转的模型。在主菜单场景中添加SubViewportContainer再往里添加SubViewport。将 3D 场景实例化为SubViewport的子节点并通过脚本让模型旋转。这样做的好处是主页看起来更酷代价是会增加性能开销。对于新手项目建议先使用静态背景后续再升级。4. 游戏引导Tutorial层的设计4.1 引导层的结构新建场景scenes/tutorial_layer.tscn根节点选择CanvasLayer。CanvasLayer的特点是它不受摄像机影响永远显示在屏幕上层非常适合做 UI 提示。场景结构TutorialLayer (CanvasLayer) ├── Control (全屏遮挡可选) │ └── Panel │ └── MarginContainer │ └── VBoxContainer │ ├── Label │ ├── NextButton (Button) │ └── SkipButton (Button)Control节点用于接收鼠标事件防止玩家在引导期间点击到背后的按钮或 3D 物体。Panel是对话框面板显示引导文本和操作按钮。4.2 编写引导层脚本引导层脚本的主要功能保存一个字符串数组作为步骤列表。显示当前步骤文本。点击“下一步”进入下一步。点击“跳过”结束引导。全部步骤完成后发出tutorial_finished信号。代码如下extends CanvasLayer class_name TutorialLayer signal tutorial_finished export var steps: Array[String] [ 欢迎来到 3D 世界, 使用 W A S D 键控制角色移动。, 按住 Shift 可以跑步。, 靠近宝箱后按 E 打开宝箱。, 现在开始你的冒险吧 ] var current_step: int 0 onready var panel: Panel $Control/Panel onready var label: Label $Control/Panel/MarginContainer/VBoxContainer/Label onready var next_button: Button $Control/Panel/MarginContainer/VBoxContainer/NextButton onready var skip_button: Button $Control/Panel/MarginContainer/VBoxContainer/SkipButton func _ready() - void: next_button.pressed.connect(_on_next_pressed) skip_button.pressed.connect(_on_skip_pressed) show_step(current_step) func show_step(index: int) - void: if index steps.size(): finish_tutorial() return current_step index label.text steps[index] # 如果是最后一步按钮显示“完成” if index steps.size() - 1: next_button.text 开始游戏 else: next_button.text 下一步 func _on_next_pressed() - void: show_step(current_step 1) func _on_skip_pressed() - void: finish_tutorial() func finish_tutorial() - void: # 隐藏整个层释放鼠标事件 $Control.hide() tutorial_finished.emit()说明export让steps数组可以直接在编辑器里修改从而复用同一个引导层预设不同关卡的教学内容。class_name TutorialLayer让其他脚本可以用TutorialLayer类型声明变量。finish_tutorial()发出信号后游戏主体可以知道引导已经结束从而解锁玩家操作或开始敌人刷新。4.3 在游戏场景中接入引导层假设游戏场景game.tscn中有一个角色Player我们希望玩家未完成引导前不能移动。在游戏场景的根节点上挂一个脚本game.gdextends Node3D onready var player: CharacterBody3D $Player onready var tutorial: TutorialLayer $TutorialLayer var player_control_enabled: bool false func _ready() - void: # 启动时暂停玩家输入 player_control_enabled false tutorial.tutorial_finished.connect(_on_tutorial_finished) func _unhandled_input(event: InputEvent) - void: # 玩家是否允许操作 if not player_control_enabled: return # 这里把输入事件转发给玩家 if event.is_action_pressed(move_forward): # 实际项目中你会在玩家的 _physics_process 中读取输入 pass func _on_tutorial_finished() - void: player_control_enabled true print(引导结束玩家可以自由行动了)最简单的控制方式是在Player脚本中使用一个布尔变量can_move由game.gd控制。你也可以把玩家的输入处理直接放在玩家脚本里通过公共方法启用或禁用。5. 游戏引导与 3D 世界交互的高级设计5.1 指向 3D 物体的引导箭头有时我们需要引导玩家看向某个 3D 物体比如“到前方石头那里去”。这时引导层可以计算目标物体在屏幕上的位置然后移动箭头图标。原理使用摄像机将 3D 世界坐标转换为屏幕坐标再将屏幕坐标赋值给 UI 节点。脚本逻辑如下extends CanvasLayer # 在子场景中准备一个箭头箭头图标 onready var arrow: Control $Arrow onready var camera: Camera3D get_tree().get_first_node_in_group(player_camera) func _process(_delta: float) - void: # 目标物体的全局位置 var target_pos: Vector3 target_node.global_position # 转屏幕坐标 var screen_pos: Vector2 camera.unproject_position(target_pos) # 如果物体在相机前方显示箭头否则隐藏 var camera_transform: Transform3D camera.global_transform var to_target: Vector3 target_pos - camera.global_position var forward: Vector3 -camera_transform.basis.z if forward.dot(to_target) 0: arrow.show() arrow.position screen_pos else: arrow.hide()这个示例要求你提前在引导层里放置一个Arrow控件并且目标物体节点存在。实际项目中更常见的做法是让箭头出现在屏幕边缘指向屏幕外的目标。这里只展示了基本思路。5.2 用高亮框标注可交互物体如果想强调“按 E 打开宝箱”一种方法是给宝箱添加一个外发光材质或高亮轮廓。在 Godot 中可以用GeometryInstance3D的set_instance_shader_parameter配合自定义 shader 实现轮廓高亮。不过对于新手更简单的方法是在引导层中创建一个半透明圆角矩形手动调整位置和大小让它覆盖住目标物体屏幕坐标对应的区域。缺点是不够精确但开发效率高。如果你希望高亮跟着物体移动可以在_process中不断更新高亮框的position和size并且根据目标物体与摄像机的距离估算屏幕区域的缩放比例。这部分涉及数学计算可以放到后续进阶教程中详述。6. 全局数据与引导跳过逻辑6.1 用自动加载保存引导状态一般情况下玩家只需要看一遍新手引导。下次进入游戏时应该跳过引导直接进入可自由操作状态。Godot 提供了“自动加载”Autoload机制可以把一个脚本作为全局单例在整个游戏生命周期内保持存在。这里创建一个game_state.gdextends Node var tutorial_seen: bool false在项目设置中将该脚本添加到 Autoload项目设置 - 全局 - Autoload - 添加 路径: res://autoload/game_state.gd 名称: GameState这样在所有脚本中都可以通过GameState.tutorial_seen访问这个单例。修改game.gdfunc _ready() - void: if GameState.tutorial_seen: # 已经看过引导直接允许玩家控制 player_control_enabled true $TutorialLayer.hide() else: player_control_enabled false $TutorialLayer.tutorial_finished.connect(_on_tutorial_finished) func _on_tutorial_finished() - void: player_control_enabled true GameState.tutorial_seen true6.2 保存引导状态到磁盘如果你希望玩家下次打开游戏时仍然记得已经看过了引导需要把数据写入配置文件。Godot 可以用ConfigFile类func save_tutorial_seen() - void: var config : ConfigFile.new() config.set_value(Player, tutorial_seen, GameState.tutorial_seen) config.save(user://settings.cfg) func load_tutorial_seen() - void: var config : ConfigFile.new() var err : config.load(user://settings.cfg) if err OK: GameState.tutorial_seen config.get_value(Player, tutorial_seen, false)user://是 Godot 为每个项目分配的独立用户目录适合存放存档和设置。7. 运行验证与效果7.1 运行主菜单设置主场景为scenes/main_menu.tscn点击运行。预期效果窗口中心显示标题和三个按钮。鼠标悬停在按钮上时按钮有高亮反馈。点击“开始游戏”后切换到game.tscn。点击“退出游戏”后窗口关闭。7.2 验证引导流程在game.tscn中放置一个TutorialLayer实例并确保TutorialLayer在场景树中处于最后位置。运行主菜单并点击开始游戏进入 3D 场景后看到引导对话框。点击“下一步”依次切换文本。点击“跳过”后对话框隐藏玩家可以移动。退出游戏并再次运行如果已经实现了保存功能引导不会再次弹出。7.3 在编辑器中快速测试引导层如果你不想每次从主菜单进入游戏测试可以把game.tscn暂时设置为主场景并直接在编辑器里运行。此时如果出现相机或者角色相关报错先检查是否缺少必要的节点。建议在开发初期的测试场景中只放一个地面和一个TutorialLayer验证引导逻辑独立于游戏逻辑。8. 常见问题与排查清单问题现象常见原因解决思路点击按钮没有任何反应按钮上方有其他透明 Control 节点遮挡检查场景树顺序调整 Z 轴或确保全屏遮挡层隐藏鼠标点击穿透到 3D 场景引导层没有设置全屏遮挡层在引导层中添加 Control 并设置mouse_filter STOP中文文字显示为方块项目默认字体不支持中文导入中文字体文件并设置主题默认字体切换场景后黑屏目标场景没有摄像机在game.tscn中添加 Camera3D 节点玩家角色还能移动引导没有生效没有禁用角色输入在游戏脚本中设置player_control_enabled false引导层无法连接信号节点路径写错使用%唯一名称或从场景树中拖拽节点到脚本变量Godot 3 中change_scene_to_file报错版本 API 不同Godot 3 使用change_scene_to()排查信号问题时可以在_ready()中打印相关信息print(start_button.get_signal_connection_list(pressed))这样可以确认信号是否成功连接。9. 最佳实践与工程建议9.1 UI 节点命名与组织统一使用Button、Label、Panel等有意义的名称不要使用Node2这类默认命名。使用%UniqueName标记关键节点在脚本中用$%StartButton引用可以防止父级结构调整后路径失效。把 UI 布局与逻辑分离样式尽量通过主题Theme设置不用每个按钮单独调样式。9.2 引导模块化设计把引导层做成独立场景通过export配置步骤文本。引导层不直接依赖玩家或怪物节点的类型而是通过信号通知外部。对于复杂引导可以把步骤数据做成 JSON 或资源文件方便策划调整。9.3 场景切换注意事项不要在按钮信号回调中执行耗时操作避免界面卡死。如果场景加载较慢使用ResourceLoader.load_threaded_request()配合加载进度条。背景音乐放在 Autoload 中场景切换后音乐不会中断。9.4 输入与鼠标状态管理进入 3D 游戏时通常要捕获鼠标Input.mouse_mode Input.MOUSE_MODE_CAPTURED。打开暂停菜单或引导菜单时要释放鼠标。切回主菜单后要重新设置鼠标模式为可见。9.5 安全的场景入口如果游戏包含存档系统在主菜单要区分“开始游戏”和“继续游戏”。点击“继续游戏”时先检查存档文件是否存在避免加载空数据。对存档文件进行备份和校验防止损坏导致游戏无法启动。10. 总结与下一步进阶方向本文完成了 Godot 3D 项目中两个不可或缺的模块游戏主页和游戏引导。通过场景搭建、信号连接、Autoload 全局数据以及简单的 3D 坐标转屏幕坐标原理你可以为自己的游戏加上一个像样的开场流程。接下来可以继续扩展的方向用 Tween 动画做标题和按钮的淡入效果。通过 JSON 文件配置多关卡引导步骤。实现引导高亮框自动跟随 3D 物体。接入手柄输入与 UI 焦点导航方便主机平台。把引导系统与任务系统打通让任务目标动态显示在 UI 上。实际做项目时建议先把主菜单和引导做成模板这样后续每个新场景都能快速复用。欢迎收藏本文对照代码动手实现一遍遇到报错可以按上面的排查清单检查。实践永远是最好的学习方式。
阅读完成 · 觉得有帮助?