新手引导经常被低估。很多原型只是弹一个箭头、遮住按钮、让玩家点下一步。真正进入产品阶段后,引导会跨越战斗、背包、任务、商城、抽卡、设置和剧情;玩家可能断线、切场景、跳过、重进游戏,甚至在另一台设备继续。教程如果没有状态机和门禁规则,很容易卡住玩家。
Godot 做引导 UI 很简单,但可维护的引导系统需要把步骤条件、输入限制、UI 高亮、业务完成事件和进度持久化拆开。教程不是 UI 脚本自己判断下一步,而是 TutorialGateService 根据玩家状态决定当前是否需要拦截。
项目里的真实问题
一个常见事故是引导要求玩家点击背包按钮,但背包按钮因为活动弹窗被挡住;或者玩家在引导中途断线重进,教程停在“点击强化”步骤,但装备已经强化完成,按钮不再可点。还有一种情况是玩家跳过教程后,后续系统仍然认为他没完成某个步骤,导致红点和任务目标不一致。
这些问题说明,引导步骤不能只依赖 UI 点击。每一步都应该有进入条件、完成条件、失败恢复和跳过策略。UI 只是表现层,真正的进度来自业务事件和玩家状态快照。
设计目标
- 步骤可恢复:重进游戏后能根据玩家状态恢复到合理步骤,而不是旧 UI 状态。
- 输入可控:遮罩和输入限制只影响当前步骤需要的区域,不误伤其他系统。
- 跳过一致:跳过教程后,相关门禁、红点和任务状态同步更新。
- 配置可检查:步骤引用的 UI 节点、业务事件和条件能在提交前校验。
目标不是把一个小功能做成庞大平台,而是让它进入真实项目后仍然可维护。Godot 的 Node、信号和 Resource 很适合快速验证,但功能一旦要覆盖多个页面、多个平台和多次版本更新,就必须把状态、配置、失败路径和观测方式拆清楚。下面的方案都围绕一个原则:业务脚本提交意图,系统层做决策,表现层只消费快照。
推荐架构
flowchart TD
A["玩家行为/任务进度"] --> B["TutorialGateService"]
B --> C["步骤条件"]
B --> D["UI遮罩"]
B --> E["输入限制"]
B --> F["进度持久化"]
C --> G["状态快照"]
D --> G
E --> G
F --> G
G --> H["UI反馈/日志/回滚"]
这张图里的模块可以按项目规模合并。小团队可以用一个 Autoload 管理,大团队可以拆成配置 Resource、Service、ViewModel 和调试面板。关键是调用方向要稳定:场景和 UI 不直接修改底层状态,而是提交意图并订阅快照。这样测试、灰度和回滚才有抓手。
关键实现细节
引导步骤要用 Resource 描述,而不是写在 UI 脚本里。Step 包含 id、进入条件、完成事件、高亮目标、允许输入区域、超时策略、跳过策略和恢复检查。这样策划可以 review 流程,程序可以写校验工具。
完成条件要优先依赖业务事件。例如“装备强化完成”比“点击强化按钮”更可靠。按钮点击可能失败、网络可能超时、玩家可能已经通过别的入口完成强化。引导应等待业务状态真正变化,再进入下一步。
输入遮罩不要全局吞掉所有输入。只允许当前高亮区域接收点击,其余区域可以阻止 gameplay 输入,但系统返回、断线弹窗、无障碍按钮等应有例外。否则教程会把玩家困住。
恢复逻辑要从玩家状态推导。重进游戏后,如果背包已打开且装备已强化,就跳过相关步骤;如果条件不满足,就回到最近一个可执行步骤。不要持久化“当前 UI 第几步”作为唯一真相。
失败处理和恢复路径
步骤目标 UI 不存在时,开发包直接报警,正式包跳过该步骤或进入安全引导。不要在空节点上画箭头。
业务事件超时时,显示可理解的重试或跳过选项。弱网下教程不能永远卡在“正在等待”。
跳过教程后,要写入完整跳过状态,并通知红点、任务和功能解锁系统刷新。只隐藏 UI 不等于完成跳过。
数据契约和协作接口
TutorialStep schema 包含 step_id、enter_condition、complete_event、target_path、input_policy、skip_policy、recover_rule。
业务系统通过事件总线发送 tutorial 相关事件,但不直接推进教程步骤。
UI 高亮层只订阅当前 step snapshot,负责遮罩、箭头、文案和动画。
GDScript 接口草图
class_name TutorialGateService
extends Node
signal snapshot_changed(snapshot: Dictionary)
signal rejected(reason: String, payload: Dictionary)
var _snapshot := {}
var _op_version := 0
func apply_intent(intent: Dictionary) -> void:
_op_version += 1
var version := _op_version
_snapshot = {"phase": "checking", "intent": intent}
emit_signal("snapshot_changed", _snapshot)
_execute(intent, func(result: Dictionary):
if version != _op_version:
return
if not result.get("accepted", false):
emit_signal("rejected", result.get("reason", "unknown"), result)
return
_snapshot = result
emit_signal("snapshot_changed", _snapshot)
)
func snapshot() -> Dictionary:
return _snapshot.duplicate(true)
接口草图保留了版本号,是因为很多客户端问题来自异步乱序:玩家快速切换页面、网络请求晚返回、资源加载被取消后又完成。如果旧结果可以覆盖新状态,问题会非常隐蔽。实际项目里还要补超时、取消、错误码和日志字段。
分阶段落地
第一阶段只接入一个核心流程,例如背包强化,验证步骤 Resource 和业务完成事件。
第二阶段加入恢复、跳过和输入遮罩例外。
第三阶段做编辑器校验和跨设备进度同步。
自动化验证和人工验收
引导中途断线重进,确认能恢复到可执行步骤。
目标按钮被弹窗遮挡时,引导延后或关闭弹窗,不直接卡死。
玩家跳过教程后,红点、任务和功能解锁状态一致。
步骤配置引用不存在 UI 节点时,校验能发现。
观测指标
- 教程步骤完成率、跳过率和卡住时长。
- 步骤超时次数和目标 UI 缺失次数。
- 恢复后跳转步骤分布。
- 教程导致输入被阻止的时长。
指标不一定全部进入正式服。开发包可以显示完整调试面板,内测包采样关键计数,正式包只保留错误码和聚合趋势。指标的目的不是制造报表,而是让一次异常能被定位到具体阶段、具体配置和具体玩家路径。
上线前检查清单
- 每个步骤有进入条件和业务完成条件。
- 遮罩输入策略有例外路径。
- 跳过会同步功能解锁和红点状态。
- 重进游戏从玩家状态恢复,不只读当前 step。
- 步骤引用的 UI 和事件能自动校验。
检查清单要随着事故复盘不断更新。每次问题暴露后,都问它是否能变成自动检查、灰度指标或人工验收步骤。能沉淀下来的经验,才会在下一次版本里真正保护团队。
工程落地补充
教程系统还要处理版本迁移。某个步骤在 1.0 版本存在,1.1 版本被删除或合并后,老玩家存档里可能仍停在旧 step_id。TutorialGateService 启动时应读取教程版本,把旧 step 映射到新 step,或者根据玩家状态直接标记完成。不要让废弃步骤成为永久卡点。
另一个常见需求是 A/B 测试。不同新手流程可能启用不同步骤顺序,但业务完成事件应该共享。这样某条实验路线关闭后,玩家仍然能回到主线教程。实验 id 可以写进教程进度,问题反馈时就能知道玩家走的是哪条路线。
配置版本也很重要。系统上线后,配置会跟着内容迭代不断变化:新增步骤、新增音频规则、新增安全区 profile、新增商品或新增目标类型。每份配置都应该有 version 和 lastmod,客户端日志里记录当前版本。出现问题时,团队能知道玩家使用的是哪一版配置,而不是只看到一个模糊的功能名。
调试入口要从第一版就准备。不要等问题出现后再临时加日志。开发包至少能显示当前快照、最近一次意图、失败原因和配置来源。QA 报告如果能带上这四个信息,排查效率会比只发截图高很多。对于 UI 类系统,最好能在截图角落显示关键 id,例如 step_id、marker_id、quote_id 或 target_id。
团队协作边界
这类系统通常不是单个程序能独立定完的。策划需要确认规则和文案,美术或 UI 需要确认表现,QA 需要确认验收脚本,服务端或平台同学需要确认接口边界。建议在文章对应的系统落地时,把“谁能改配置、谁能发开关、谁负责看指标”写在 README 或内部文档里。
同时要约定变更流程。新增一个教程步骤、新增一种购买错误码、新增一个目标类型、新增一个音频 ducking 规则,都应该有最小验收样本。没有样本的配置变更,很容易在下一次内容更新时破坏既有路径。把样本保留下来,后续自动化才能逐步建立。
案例复盘
一次强化引导中,玩家在点击强化前通过活动奖励获得了更高等级装备,原步骤要求点击的装备不再存在。旧教程直接卡死。改成 recover_rule 后,服务检查到玩家已拥有强化结果,直接跳到下一步,并在历史日志中记录 skipped_by_state。这个案例说明教程状态必须服从玩家事实。
灰度验收脚本
灰度验收可以选 50 个新号,记录每个步骤进入、完成、跳过和恢复。测试中故意插入断线、弹窗、切场景和跳过。验收报告要能指出卡住最多的步骤,以及它是 UI 目标缺失、业务事件未发还是条件配置错误。
验收边界补充
验收时还要覆盖“老玩家回流”。老玩家可能已经解锁功能,但教程版本更新后又新增步骤。系统应该识别他已经具备能力,不要强行重新引导基础操作。可以只显示轻提示或入口说明。
每次验收都要同时看成功路径和失败路径。成功路径证明功能能跑,失败路径证明系统不会把玩家带进不可理解的状态。对于这类客户端系统,最容易漏测的往往不是主流程,而是取消、超时、配置缺失、目标失效、切场景和重进游戏。把这些边界做成固定脚本,后续内容扩展时才能继续复用。
另外,验收结果要能落到文件或截图里。只说“体感还行”不够,至少要有关键状态快照、调试面板截图或日志片段。系统越复杂,越需要可保存的证据。这样下一次同类问题出现时,团队能对比前后行为,而不是重新凭记忆讨论。
小团队接入版本
小团队可以先不用可视化编辑器,只用 Resource 或 JSON 配置 5 到 10 个核心步骤。关键是先把“点击按钮推进”改成“业务状态推进”。这个转变完成后,引导复杂度才会可控。
交付边界
交付标准是教程能被恢复、跳过和验证。玩家不应该因为引导遮罩失去控制,QA 也不应该靠肉眼猜某一步为什么没推进。
结语
新手引导不是弹几个箭头,而是一套门禁和状态恢复系统。Godot 客户端把步骤条件、业务事件和 UI 高亮拆开后,教程才能在真实网络和真实玩家行为下稳定运行。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。