ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Godot 3D游戏开发:从主页到引导层的完整实现解析

Godot 3D游戏开发:从主页到引导层的完整实现解析 如果你已经能搭出一个可以自由行走的3D小场景下一步通常会被两个“看起来很简单”的地方卡住一个是游戏最开始的主页另一个是玩家进入游戏后要看的引导提示。主页不只是放几个按钮引导也不只是弹一两行文字。在 Godot 里它们背后涉及场景切换、UI 分层、全局单例、输入控制以及如何把 3D 物体的位置转换成屏幕坐标。这个第 30 篇的教程就是把这两个模块放在一起彻底拆开讲清楚。我的判断很直接对于新手项目“游戏主页 游戏引导”比角色建模、地形美化更值得先做。玩家打开游戏后前三十秒看到的是主页和引导流程而不是你精心布置的某个角落。哪怕你只有一个测试用的方块场景只要主页能进入、引导能说明问题这个“游戏”就已经具备基本可玩性了。这篇教程会用 Godot 3D 的视角从零搭建一个可复用的游戏主页再做一个可以挂在任意 3D 场景上的引导层。读完你能独立完成主页跳转、场景加载、引导弹窗、3D 目标高亮、事件关闭引导这一整条流程。有一点需要先说明标题里的 Godot3D指的是“用 Godot 做 3D 游戏”并不代表必须使用 Godot 3.x。下面的代码按 Godot 4.x 的 GDScript 写法演示。如果你用的是 Godot 3.x唯一要留意的是少数 API 名称差异例如场景切换从change_scene_to_file换成change_scene。我会在文中把这类差异点标出来。1. 游戏引导与游戏主页到底解决了什么问题先想一个问题如果游戏没有主页玩家一打开引擎运行窗口就直接掉进 3D 场景会怎样新手可能连摄像机怎么转都不知道更不知道按 WASD 是移动。就算你写了完整的移动代码玩家不读任何说明第一次体验往往就是原地转圈、头晕、然后关掉游戏。主页解决的是“游戏如何开始”的问题。它给玩家一个明确的入口同时给开发者一个集中管理按钮、设置、关卡选择的场景。主页本质上也是一个场景只不过它的内容绝大多数是 Control 节点而不是 MeshInstance3D 或 CharacterBody3D。你可以把它当成一个“只有 2D UI 的舞台”而 3D 游戏场景则是另一个舞台两者通过场景切换串联起来。引导解决的是“玩家如何学会玩法”的问题。在 3D 游戏里玩家的第一个运行体验和观看视频完全不同视角、操作、碰撞都会影响理解。引导的任务不是把所有系统都讲一遍而是把当前最需要的信息在正确的时机展示出来。引导又分为硬引导和软引导硬引导强制玩家完成某个操作后才能继续例如“按住 W 走到发光点”软引导只是展示提示文字玩家可以随时跳过。新手项目建议先做软引导因为硬引导需要处理暂停、屏蔽输入、焦点锁定、多步骤状态机复杂度会快速上升。所以这一篇教程做的是这样一个组合一个主页点击“开始游戏”进入 3D 场景一个引导层在 3D 场景加载后自动弹出给出操作提示并高亮场景里的目标节点点击“继续”或“跳过”后引导消失玩家恢复正常操作。这个组合并不复杂但它覆盖了绝大多数独立小游戏的前期体验流程。2. Godot3D 项目的核心概念与适用场景在动手前需要把几个 Godot 核心概念对齐。它们是理解后面所有步骤的基础。第一个概念是场景Scene。场景是 Godot 的根组织单位可以是一个主菜单、一个关卡、一个角色甚至一个 UI 控件组合。每个场景都对应一个.tscn文件通过节点树构成自己的内部结构。场景之间可以切换也可以嵌套实例化。在这个教程里主菜单、3D 关卡、引导层都是独立场景。第二个概念是节点Node和场景树SceneTree。节点是场景树上的对象任何功能都由节点组合完成。你可以把场景想象成舞台节点是演员场景树是导演。导演负责把各个舞台按顺序管理起来。当你调用get_tree().change_scene_to_file()时本质上是告诉导演“把这个舞台撤掉换上另一个舞台”。第三个概念是 CanvasLayer。这是很多新手容易忽略的节点。CanvasLayer 是一个与 3D 世界无关的 2D 绘制层。即使你在 CanvasLayer 下面放一堆 Label、Button、ColorRect它们也不会被 3D 摄像机影响。你可以把它理解成摄影棚里的玻璃板3D 场景在玻璃板后面照常运动UI 画在玻璃板前面稳定显示。游戏主页和引导层都强烈建议放在 CanvasLayer 中而不是直接挂在 Node3D 下。第四个概念是 Control 节点和锚点。Button、Label、Panel、ColorRect 都属于 ControlControl 是 Godot UI 系统的基础。锚点决定 Control 相对父节点的位置和缩放使用锚点铺满全屏后窗口大小变化时 UI 依然能自适应。后面的主页和引导层都要用到锚点。第五个概念是信号Signal。信号是节点之间的解耦通信方式。按钮被点击后发出pressed信号你只需要把处理函数连接到这个信号上按钮不需要知道谁会处理这个事件。第六个概念是自动加载单例Autoload。在项目设置里把一个脚本注册为 Autoload它会在游戏启动时进入场景树并且全局可用。我们后面要写的引导管理器就非常适合做成 Autoload因为它在主菜单、游戏场景、引导层里都可能被调用。下面用表格快速对比这些概念概念角色在本教程中的作用场景 Scene组织单位主菜单、3D 关卡、引导层各是一个场景节点 Node功能单元按钮、摄像机、角色都由节点实现场景树 SceneTree全局调度器负责场景切换、暂停、根节点管理CanvasLayer2D 绘制层让 UI 独立于 3D 世界显示ControlUI 控件Button、Label、ColorRect 都属于 Control信号 Signal事件通信监听按钮点击、关闭引导Autoload全局单例引导管理器全局可访问这一章的核心结论是主页和引导不是孤立功能而是“场景节点如何组织、UI 如何分层、全局状态如何管理”这三个工程问题的具体体现。如果你理解了这几个概念后续写任何游戏 UI 流程都不会再感到混乱。3. 环境准备与前置条件开始写代码前请确保你的环境满足以下条件下载并安装 Godot 4.x 稳定版。可以在 Godot 官网选择对应操作系统的标准版。新建一个空白项目项目名称可以叫Godot3DGuideDemo。渲染器选择如果只是学习选择Forward即可如果你的电脑较老也可以选择Mobile或Compatibility。编辑器语言保持默认 GDScript不需要额外插件。在项目里建议先规划好文件夹结构。项目文件和场景数量少时可以随意但只要你的项目开始出现主页、引导、关卡、脚本多个模块混乱的目录会让你很快失控。我建议按下面方式规划res:// ├── scenes/ │ ├── ui/ │ │ ├── main_menu.tscn │ │ └── guide_layer.tscn │ └── game/ │ └── game.tscn ├── scripts/ │ ├── autoload/ │ │ └── guide_manager.gd │ ├── ui/ │ │ ├── main_menu.gd │ │ └── guide_layer.gd │ └── game/ │ └── player.gd └── project.godot如果你的项目没有这个目录结构可以手动创建。之后所有场景和脚本都按照这个规则保存。文件夹命名统一用小写和下滑线避免使用中文和空格。Godot 虽然支持中文名称但节点路径和脚本引用一旦出现编码问题排查起来很麻烦。另一个前置准备是输入映射。后面我们会让玩家用 WASD 控制角色移动所以需要在 Project Settings 的 Input Map 里添加四个动作move_left、move_right、move_forward、move_back。分别绑定 A、D、W、S 键。Godot 里输入动作名是自定义字符串最好用英文不要用中文。添加好后物理移动代码通过Input.get_vector()读取这四个动作就可以写出跨平台通用的移动逻辑。现在环境已经准备好。下面从游戏主页开始逐步搭建这两个核心模块。4. 游戏主页场景搭建与按钮交互主页的作用是给玩家一个干净的入口。我们先用编辑器手动搭建一个最简单的主菜单然后写脚本让它能切换到 3D 游戏场景。4.1 创建主菜单场景在 Godot 编辑器中点击“新建场景”根节点选择Control命名为MainMenu保存为res://scenes/ui/main_menu.tscn。然后在 MainMenu 下添加一个 CanvasLayer命名为UICanvas。接着在 UICanvas 下添加CenterContainer将它的锚点铺满全屏。CenterContainer 的作用是让子节点始终居中非常适合菜单布局。在 CenterContainer 下添加VBoxContainer用于垂直排列菜单项。设置 VBoxContainer 的 Separation 为 20让各个按钮之间有间隔。在 VBoxContainer 下添加四个子节点TitleLabel显示游戏标题字号可以设大一些。StartButton文本设为“开始游戏”。SettingsButton文本设为“设置”。QuitButton文本设为“退出游戏”。为了让按钮更好看可以在 Button 的 Inspector 中修改theme_override_colors/font_color和theme_override_font_sizes/font_size。这些主题覆盖属性只影响当前节点适合快速调整 UI 而不用创建完整主题资源。对于一个新手项目这样做已经够了不要一开始就进入设计系统思维。4.2 为主菜单编写跳转脚本创建脚本res://scripts/ui/main_menu.gd挂载到 MainMenu 根节点上。脚本内容如下# 文件路径res://scripts/ui/main_menu.gd extends Control onready var start_button: Button $UICanvas/CenterContainer/VBoxContainer/StartButton onready var settings_button: Button $UICanvas/CenterContainer/VBoxContainer/SettingsButton onready var quit_button: Button $UICanvas/CenterContainer/VBoxContainer/QuitButton func _ready() - void: start_button.pressed.connect(_on_start_pressed) settings_button.pressed.connect(_on_settings_pressed) quit_button.pressed.connect(_on_quit_pressed) func _on_start_pressed() - void: get_tree().change_scene_to_file(res://scenes/game/game.tscn) func _on_settings_pressed() - void: print(设置界面暂未实现先做一个占位按钮) func _on_quit_pressed() - void: get_tree().quit()这段代码的逻辑非常直白。_ready()在场景进入场景树后执行把所有按钮的pressed信号连接到对应的处理函数。_on_start_pressed()通过场景树切换到game.tscn这就是“点击主页按钮进入游戏”的核心操作。_on_quit_pressed()调用get_tree().quit()退出游戏编辑器里运行时也会退出播放。需要说明的是_ready()中使用$UICanvas/CenterContainer/VBoxContainer/StartButton这种路径写法依赖于节点名称和层级完全正确。如果某个节点名称少了一个字母运行时就会报错。遇到这种情况排查顺序是先检查场景树面板再检查脚本路径。设置按钮这里没有做真正的设置界面只打印日志。这样做不是为了敷衍而是提醒你在功能还没有实现时不要直接删掉入口可以先保留按钮并打日志等后续有能力时再补设置场景。很多新手会卡在“设置界面怎么做”上从而影响完整流程体验。先保留入口后面填充内容远比一开始就追求所有按钮都可用更重要。4.3 设置主菜单为启动场景场景制作完成后需要告诉 Godot 启动时先加载哪个场景。打开 Project Settings找到Application Run Main Scene选择res://scenes/ui/main_menu.tscn。这一步不做的话按 F5 运行时会随机打开项目里的某个场景或者弹错。现在菜单已经可以运行。按 F5 会看到 TitleLabel、开始游戏、设置、退出四个控件。点击“开始游戏”会因game.tscn不存在而报错所以下一步创建 3D 游戏场景并接上引导层。5. 游戏引导叠加层与高亮定位游戏引导在整个流程中扮演的角色是在 3D 场景之上增加一个“只读解释层”。它不修改游戏场景里的任何节点只是叠加提示文字并用高亮框标出一个 3D 对象。这样做的好处是你可以把引导层复用到任何关卡只需要传入不同的目标节点路径和文案。5.1 引导管理器自动加载单例先创建引导管理器res://scripts/autoload/guide_manager.gd再在 Project Settings 的 Autoload 中注册它名字命名为GuideManager。注册后项目里任何脚本都可以直接调用GuideManager.start_guide(first_move)。# 文件路径res://scripts/autoload/guide_manager.gd extends Node const GUIDES : { first_move: { target_path: NodePath(Player), title: 移动操作, desc: 使用 WASD 控制角色移动看场景中间的方块就能理解方向。, size: Vector2(260, 180) } } var _layer: CanvasLayer null func start_guide(guide_key: String) - void: if _layer ! null: return if not GUIDES.has(guide_key): push_error(未找到引导配置: guide_key) return var data: Dictionary GUIDES[guide_key] var packed: PackedScene load(res://scenes/ui/guide_layer.tscn) var layer: CanvasLayer packed.instantiate() layer.set(title_text, data[title]) layer.set(desc_text, data[desc]) layer.set(target_node, data[target_path]) layer.set(highlight_size, data[size]) layer.name GuideLayer get_tree().get_root().add_child(layer) _layer layer func finish_guide() - void: if _layer null: return _layer.queue_free() _layer null func is_guide_active() - bool: return _layer ! null这段代码看起来不多但它承担了三个重要职责。第一把引导配置集中在一个字典里。以后想增加新的引导步骤只需要在GUIDES里增加一条而不需要改动 UI 场景。这个设计叫“数据驱动”新手阶段养成这种习惯非常有用。第二通过load()加载引导层场景并用set()把文案和节点路径传进去。引导层和游戏场景之间不直接引用而是通过数据传递所以它具备复用性。第三用_layer字段记录当前是否已有引导层。如果玩家已经打开引导再次调用start_guide会被忽略调用finish_guide则会把层删掉。is_guide_active()供角色移动脚本判断引导开启时禁止移动。5.2 创建引导层场景新建场景res://scenes/ui/guide_layer.tscn根节点选择CanvasLayer挂脚本res://scripts/ui/guide_layer.gd。在 CanvasLayer 下按顺序添加三个子节点Mask一个 ColorRect颜色设为黑色Alpha 设为 0.4锚点铺满全屏。它用来压暗 3D 场景让引导内容更突出。HighlightBox一个 ColorRect颜色设为黄色Alpha 设为 0.25。这个节点是代码中要动态移动的高亮框。GuidePanel一个 Panel作为提示面板底座。在面板下添加 MarginContainer再在 MarginContainer 下添加 VBoxContainer。VBoxContainer 里放 TitleLabel、DescLabel、Buttons 这个 HBoxContainer。Buttons 里放 ContinueButton 和 SkipButton。注意HighlightBox 的 Mouse Filter 属性要设为 Ignore。否则它虽然只是一个提示框也会拦截鼠标点击影响按钮操作。Mask 同样要根据你的需求决定是否拦截点击。如果希望引导弹出时屏蔽后面 3D 场景的点击Mask 保持默认即可如果希望玩家能穿透遮罩操作场景就把 Mask 也设为 Ignore。5.3 编写引导层脚本挂载到 CanvasLayer 上的脚本如下# 文件路径res://scripts/ui/guide_layer.gd extends CanvasLayer var title_text: String 提示 var desc_text: String var target_node: NodePath NodePath() var highlight_color: Color Color(1, 0.9, 0.2, 0.25) var highlight_size: Vector2 Vector2(260, 180) onready var title_label: Label $GuidePanel/MarginContainer/VBoxContainer/TitleLabel onready var desc_label: Label $GuidePanel/MarginContainer/VBoxContainer/DescLabel onready var highlight_box: ColorRect $HighlightBox onready var continue_btn: Button $GuidePanel/MarginContainer/VBoxContainer/Buttons/ContinueButton onready var skip_btn: Button $GuidePanel/MarginContainer/VBoxContainer/Buttons/SkipButton func _ready() - void: title_label.text title_text desc_label.text desc_text highlight_box.color highlight_color highlight_box.size highlight_size continue_btn.pressed.connect(_on_continue_pressed) skip_btn.pressed.connect(GuideManager.finish_guide) update_highlight() func _process(_delta: float) - void: update_highlight() func _find_target() - Node3D: var current_scene : get_tree().current_scene if current_scene null: return null if target_node NodePath(): return null return current_scene.get_node_or_null(target_node) func update_highlight() - void: var target : _find_target() var camera : get_viewport().get_camera_3d() if target null or camera null: highlight_box.hide() return var screen_pos : camera.unproject_position(target.global_transform.origin) highlight_box.show() highlight_box.position screen_pos - highlight_box.size * 0.5 func _on_continue_pressed() - void: GuideManager.finish_guide()这段脚本的关键点是update_highlight()。它先把 3D 节点的世界坐标传给camera.unproject_position()后者返回这个点在屏幕上的 2D 坐标然后把高亮框移动到该点的中心。这样的高亮不是精确的包围盒而是一个固定大小的提示框胜在简单且够用。_process()里每帧调用update_highlight()是必须的。因为 3D 摄像机和目标节点都可能移动如果只计算一次玩家转头后高亮框就会离开原来的节点。对于新手项目这是最稳妥的实时高亮方式。_find_target()使用get_tree().current_scene取得当前游戏的根场景再以target_node为 NodePath 找到目标。因为引导层是直接挂在 Viewport 下和当前场景不是父子关系所以不能用相对路径而要从当前场景根节点出发。这里的target_node在 GuideManager 里被配置成了NodePath(Player)也就是说游戏场景根节点下要有一个名为 Player 的节点。5.4 创建 3D 游戏场景接下来做一个最小的 3D 场景来验证引导。新建场景res://scenes/game/game.tscn根节点选择 Node3D命名为Game。再创建以下节点层次Game (Node3D) ├── WorldEnvironment (添加背景环境可选) ├── DirectionalLight3D (平行光) ├── Ground (MeshInstance3D BoxMesh铺一个地面) └── Player (CharacterBody3D) ├── MeshInstance3D (胶囊体) └── CollisionShape3D (胶囊碰撞体)给 Player 挂上脚本player.gd内容如下# 文件路径res://scripts/game/player.gd extends CharacterBody3D export var move_speed: float 5.0 func _physics_process(_delta: float) - void: if GuideManager.is_guide_active(): return var input_dir : Input.get_vector(move_left, move_right, move_forward, move_back) var direction : (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() velocity.x direction.x * move_speed velocity.z direction.z * move_speed move_and_slide()在引导层存在期间GuideManager.is_guide_active()会返回 true角色不会移动。只有玩家点击“继续”或“跳过”后引导层销毁移动逻辑才恢复。最后在 Game 根节点上挂一个最简单的game.gd# 文件路径res://scripts/game/game.gd extends Node3D func _ready() - void: print(进入 3D 游戏场景) GuideManager.start_guide(first_move)这样当玩家从主菜单点击“开始游戏”进入game.tscn场景加载完成后_ready()自动执行引导层弹出。整个流程到此已经连接起来。6. 整合从主页到 3D 场景再到引导关闭让我们把完整流程串起来验证每一个环节的触发顺序运行项目Godot 读取启动场景main_menu.tscn显示主页。玩家点击“开始游戏”main_menu.gd调用change_scene_to_file(res://scenes/game/game.tscn)。Godot 卸载主菜单场景加载game.tscn。game.gd的_ready()执行调用GuideManager.start_guide(first_move)。GuideManager 加载guide_layer.tscn把它作为子节点添加到根 Viewport。CanvasLayer 上的 Mask 压暗背景TitleLabel 显示“移动操作”DescLabel 显示操作说明HighlightBox 定位到 Player 中心。玩家点击“继续”或“跳过”GuideManager.finish_guide()删除引导层is_guide_active()返回 false角色移动恢复正常。在这个流程里主页和引导层之间并没有直接的代码调用而是通过场景切换和 Autoload 单例间接衔接。这个解耦关系很重要以后你想在进门之前先播放一小段动画或者插入一个加载界面都不需要改动主页按钮代码只需要调整game.gd里的调用时机。如果你使用的 Godot 是 3.x第 4 步会略有不同。Godot 3 中场景切换 API 是change_scene(res://scenes/game/game.tscn)而不是change_scene_to_file。其它 CanvasLayer、Signal、Autoload 的概念都是一致的。如果你不小心混用了 API运行时会直接报“不存在这个方法”这种情况先检查版本。6.1 引导层的样式调整文章里没有给引导层做大量视觉设计但这不意味着引导层可以很粗糙。建议至少调整三处样式第一Mask 的透明度。太低的 Alpha 压不住背景太高又会让玩家看不清 3D 高亮目标0.3 到 0.5 是一个比较稳的范围。第二GuidePanel 的背景。默认 Panel 是灰白色可以给它添加一个 StyleBoxFlat设置圆角和半透明底色让它从背景里明显浮出来。第三字体大小。TitleLabel 建议使用 28 以上DescLabel 建议 18 以上按钮字号也要与正文拉开层次。UI 最怕“所有文字一样大”玩家一眼分不清主次。不要一上来就引入复杂动画和缓动效果。先把功能跑通再慢慢添加Tween渐入渐出会比一开始堆节点更容易控制。7. 运行结果与效果验证按 F5 运行项目你应该看到主菜单界面。验证步骤如下确认 TitleLabel 显示游戏标题。点击“开始游戏”观察是否会跳转到 3D 场景。进入 3D 场景后控制台应输出进入 3D 游戏场景。屏幕上应出现压暗遮罩、提示面板、继续按钮、跳过按钮。HighlightBox 应覆盖在 Player 节点所在位置。在引导层未关闭时按 WASD 角色不应移动。点击“继续”后引导层消失按 WASD 角色开始移动。如果第 2 步报错最常见原因是game.tscn路径不存在或者场景尚未保存。检查res://scenes/game/game.tscn是否真实存在于文件系统。如果第 5 步高亮框没有出现打开脚本的运行时错误信息确认_find_target()是否返回了节点以及摄像机和 Player 是否在同一场景树内。也可以用下面的命令行方式检查项目是否能一键加载到主菜单。在项目目录下打开终端执行godot --path . --editor如果要验证项目是否能直接运行到的某个场景可以运行godot --path . res://scenes/game/game.tscn这个命令会直接加载 game.tscn方便在开发引导功能时跳过主页快速进入测试场景。没有把握时不要在生产项目里频繁用命令行覆盖启动场景这会让你误以为项目配置变了。运行验证不一定要等到 UI 完美设计完成后才开始。先用最简单的方块、最简单的按钮确认整个流程的逻辑闭环。只要“主页到游戏再到引导关闭”这条主线是通的后续替换美术资源只是时间问题。8. 常见问题与排查思路我整理了新手在实现主页和引导时最容易遇到的六个问题每个问题都按“现象、原因、排查、方案”的顺序说明。问题现象可能原因排查方式解决方案点击开始按钮没有反应按钮信号未连接或场景文件不存在查看控制台错误信息确认change_scene_to_file路径检查_ready()中 connect 代码确认game.tscn路径主菜单显示但按钮位置不对锚点没有设置选中按钮查看 Inspector 中的 Layout 菜单选择预设的“全屏矩形”锚点重新调整缩进引导层弹出不了GuideManager 未注册到 Autoload在脚本里调用GuideManager时看是否提示未声明在 Project Settings Autoload 中添加guide_manager.gd高亮框在错误位置没有每帧更新或目标节点路径错误在update_highlight()里加print(global_transform.origin)在_process()中调用update_highlight()核对 target_node引导开启时角色仍能移动is_guide_active()未生效在 Player_physics_process()首行加 print 调试移动逻辑前判断GuideManager.is_guide_active()Godot 3.x 运行报 API 错误版本差异查看报错的方法名change_scene_to_file替换为change_scene还有一个新手容易忽略的问题引导层关闭后再调用start_guide(first_move)时如果上一次queue_free()还没有执行完_layer可能仍然指向一个无效节点。Godot 的queue_free()是延迟释放当前帧结束后才会删除节点。所以 GuideManager 在调用finish_guide()时立刻把_layer null能避免多数二次进入场景的问题。如果你发现第二次进入场景后引导不出现先检查_layer状态这是最常见的坑之一。此外如果你使用了自定义字体或主题并发现按钮文字不显示请检查字体资源路径和 Theme 属性。优先使用内置默认字体跑通逻辑再替换自定义字体不要一开始就引入 OpenType 字体配置。9. 最佳实践与工程建议主页和引导虽然功能简单但写法和工程组织方式会直接影响后续项目扩展。这里给出几条我建议新手从一开始就养成的习惯。第一场景职责分离。主菜单、3D 关卡、引导层分别是独立场景。千万不要把主菜单按钮直接塞进游戏场景否则后续做关卡切换时会非常痛苦。Git 提交时也尽量保证一个场景一个职责合并冲突会少很多。第二UI 节点统一放在 CanvasLayer 下。尤其对于 3D 游戏UI 一旦直接挂在 Node3D 下随着摄像机移动菜单和按钮会在屏幕上漂移。使用 CanvasLayer 后UI 不再受摄像机影响布局稳定后续做抬头显示也会更轻松。第三Autoload 只放全局协调逻辑不要放场景特有数据。GuideManager 适合做全局引导管理但不要在它里面写某个关卡的具体摆法。全局单例被项目所有脚本依赖里面内容越少越稳定。第四引导文案要短。玩家在 3D 场景里第一目标是“看懂”而不是“读教程”。一段引导最多两行明确告诉玩家按哪个键、产生什么效果。如果需要详细说明放到后续的暂停菜单或帮助界面里。第五高亮框的 MouseFilter 必须设为 Ignore。高亮框只是视觉提示不是交互区块。如果不设置高亮框可能挡住玩家对目标区域的点击尤其是引导关闭前后鼠标状态不一致时很容易出现“看不见原因但按钮就是点不了”的怪问题。第六暂停与引导结合需要额外注意。本篇没有启用get_tree().paused因为暂停会牵扯到每个节点的 ProcessMode对新手不够友好。如果你确实希望引导弹出时游戏暂停可以在start_guide()中设置get_tree().paused true并把引导层 CanvasLayer 的process_mode设为PROCESS_MODE_ALWAYS同时在finish_guide()中恢复暂停。这需要你对 Godot 的暂停机制有完整理解否则会出现 UI 全部失灵的情况建议后续再单独实验。第七路径和命名保持一致。主页脚本、引导管理器、游戏场景脚本分别放在 scripts/ui、scripts/autoload、scripts/game 下节点名使用英文 PascalCase。Godot 的onready var依赖节点名节点一旦重命名脚本里的路径可能失效。编辑器重命名节点时记得检查脚本中所有$路径。第八多尝试事件总线模式。当前 ContinueButton 直接调用GuideManager.finish_guide()属于最简单的耦合方式。如果后续引导变得复杂可以定义一个guide_finished信号让多个系统监听这个事件例如关闭移动限制、解锁摄像机、播放音效。但这个模式需要额外封装建议在出现“多个系统同时靠引导状态”时再引入。10. 总结与后续学习方向这一篇教程从游戏主页讲到了引导层核心是把“主页、场景切换、3D 场景、UI 叠加层、全局单例、坐标转换”串成一条完整链路。你已经可以做到玩家在主页点击开始按钮进入 3D 场景场景加载后自动弹出引导遮罩引导高亮目标节点点击按钮关闭引导并恢复操作。这套代码虽然短但它包含的思维模型是通用的场景是游戏流程的基本单位CanvasLayer 是 UI 与 3D 世界的分界线Autoload 是多个场景之间的协调器unproject_position()是 3D 世界与屏幕坐标之间的桥梁。以后你在别的项目里做任务系统、对话系统、新手教学都会用到同样的思路。下一步建议做三件事第一把引导配置改成外部 JSON 文件这样策划或你自己调整文案时不需要打开 GDScript 代码。第二实现真实的高亮包围盒。当前高亮框固定在目标节点中心之后可以用MeshInstance3D.get_aabb()计算节点的本地包围盒再把包围盒的八个角点都通过unproject_position()转换到屏幕坐标算出包围盒在屏幕上的精确矩形这样高亮效果会专业很多。第三给引导层加入Tween过渡动画。淡入淡出只需要几十行代码但体验提升非常明显也是从“能用”走向“好用”的必经之路。建议把这篇收藏备用等下一次你要给某个 3D 场景做教学引导或主菜单时直接照着这个结构落地比临时查碎片化资料省时间得多。实践时如果遇到问题优先检查 Console 面板的报错第一行错误信息通常已经告诉了你真正的解法。
返回列表