# TutorialBlock 教程流程与 Chapter 0 配置 ## 规则 TutorialBlock 点击后通过通用 `SelectionBox` 动态生成“游玩教程 / 跳过教程”两个选项。玩家选择任一选项的瞬间,系统都会: 1. 将 TutorialBlock 的 `tutorialProgressVariable` 写为 `true`。 2. 保存全局剧情变量。 3. 将该 TutorialBlock 标记为 Completed,用于故事树的节点外观和防止重复点击。 4. 重新计算所有 StoryBlock 的 `UnlockCondition`。 后续 Block 必须通过 `VariableCondition` 读取教程变量解锁,不要依赖 TutorialBlock 的完成记录。选择“游玩教程”后会进入 GameScene;选择“跳过教程”则留在故事树。教程游玩中退出、失败或关闭游戏不会回退已经确认的解锁状态。 ## 运行时职责 | 模块 | 职责 | |---|---| | `StoryProgress` | 统一执行“写变量、保存、刷新故事树”;`ResolveTutorial` 同时更新教程节点完成状态。 | | `TutorialFlowController` | 验证 TutorialBlock 配置,并临时组装教程所需的两个 `SelectionBoxOption`。 | | `SelectionBoxUIPage` | 通用多选弹窗页面:动态生成一个 `SelectionBox`,阻断底层输入并在选择后销毁该实例。 | | `SelectionBox` / `SelectionBoxButton` | 动态生成任意数量的选项按钮并确保一次显示只执行一个回调。 | | `TutorialCollection` | 用 `tutorialKey` 查询教程曲目的 `SongItemData`。 | | `InformationTransistor` | 用 `MenuReturnDestination` 保存唯一的返回目标,替代旧的两个返回 bool。 | `MenuReturnDestination.SongSelection` 供普通选曲使用;`MenuReturnDestination.Story` 供教程使用。教程从 GameScene 返回时,MenuScene 会显式重建来源章节的 StoryTree。 ## Key 命名规则 所有新 Tutorial Key 和剧情变量 Key 只使用小写英文、数字和下划线: | 用途 | Chapter 0 示例 | |---|---| | Tutorial Key | `chapter0_intro` | | 教程进度变量 | `story_tutorial_chapter0_intro_resolved` | | TutorialBlock ID(建议) | `tutorial_0_intro` | 不要用显示名称、空格、点号或本地化文本作逻辑 Key。`blockId` 是 StoryTree 内部节点标识,旧内容可继续保留既有的 `A-0` 格式;新内容建议使用清晰且稳定的名称。 ## 配置 Chapter 0 当前 `Assets/Story/Chapter0/StoryData_Chapter0.asset` 还没有 TutorialBlock;需要在 Unity Inspector 中按以下步骤添加。 ### 1. 配置教程曲目表 1. 打开 `Assets/Resources/TutorialCollection.asset`。 2. 在 `songs` 字典中将现有 Chapter 0 教程条目的 Key 调整为 `chapter0_intro`。保留其对应的 `SongItemData`,不要重新手工复制曲目数据。 3. 检查该教程条目的 `difficultyDataList`:目标难度应为 `isAvailable = true`,并确认它的 `saveDifficultyId`。当前 Chapter 0 Tutorial 的 Easy 难度应使用 ID `0`;以 Inspector 实际显示为准。 ### 2. 在 StoryData 新增起始教程节点 1. 打开 `Assets/Story/Chapter0/StoryData_Chapter0.asset`。 2. 在 `blocks` 新增一个条目,或按你的章节布局将其放在第一个剧情节点之前: | 字段 | 建议值 | |---|---| | `blockId` | `tutorial_0_intro` | | `blockType` | `Tutorial` | | `tutorialName` | 当前阶段可先填临时显示名,例如 `Chapter 0 Tutorial` | | `tutorialKey` | `chapter0_intro` | | `tutorialProgressVariable` | `story_tutorial_chapter0_intro_resolved` | | `tutorialDifficultySaveId` | `0`(以教程曲目的实际 `saveDifficultyId` 为准) | | `nextBlockIds` | 第一个后续剧情节点,例如 `A-0` | | `unlockCondition` | 留空,使教程节点在章节开始时可点击 | 3. 为原本的第一个剧情节点 `A-0` 设置 `UnlockCondition`:选择 `VariableCondition`,并填写: | 字段 | 值 | |---|---| | `variableName` | `story_tutorial_chapter0_intro_resolved` | | `comparison` | `Equal` | | `value` | `1` | 如果该节点还有其它前置要求,请使用 `AllOfCondition` 将原条件与上述 `VariableCondition` 组合。不要把它改成 `BlockCompletedCondition(tutorial_0_intro)`,否则会失去与 Yarn 和其它剧情变量共用条件系统的优势。 ### 3. 配置通用 SelectionBox TutorialBlock 不再需要专属弹窗。只需配置一套可供其它系统复用的 SelectionBox: 1. 创建一个 `SelectionBoxButton` Prefab:根节点具有 `Button` 与 `SelectionBoxButton`,子节点具有 `TMP_Text`。将 Button 和文本分别赋给 `button`、`labelText`。 2. 创建一个 `SelectionBox` Prefab:根节点具有 `CanvasGroup` 与 `SelectionBox`;配置标题 `TMP_Text`、可选内容 `TMP_Text`、选项容器 `RectTransform`,并把上一步 Prefab 赋给 `optionButtonPrefab`。 3. 在 `Assets/Scenes/MenuScene.unity` 创建一个全屏覆盖页:根节点具有 `CanvasGroup` 和 `SelectionBoxUIPage`,初始建议 `alpha = 0`、`interactable = false`、`blocksRaycasts = false`。 4. 在该覆盖页下创建 `selectionBoxContainer`,并将它赋给 `SelectionBoxUIPage.selectionBoxContainer`;将上一步的 `SelectionBox` Prefab 赋给 `selectionBoxPrefab`。 5. 选中 `MenuManager`,把该 `SelectionBoxUIPage` 组件拖入新的 `selectionBoxUIPage` 字段。 教程点击时会运行时生成一个 SelectionBox,并生成两个按钮:`游玩教程` 与 `跳过教程`。未来其它非 Yarn 的交互(例如奖励二选一、离开确认、章节分支)可以传入自己的标题、内容和 `SelectionBoxOption` 列表复用这套 UI。按钮正式本地化留待剧情文本与本地化阶段处理。 ## 验证清单 1. 使用新存档或 StoryTree 的 `DebugResetChapterProgress` 重置 Chapter 0;该调试重置现在也会清除本章节的教程变量。 2. 进入 Chapter 0,确认只有 TutorialBlock 可点击,`A-0` 保持 Locked。 3. 点击 TutorialBlock,再点击“跳过教程”:节点应变为 Completed,`A-0` 应立即成为 Current;重进游戏后状态应保留。 4. 重置后选择“游玩教程”:确认进入 Chapter 0 Tutorial 的指定难度,且 `A-0` 在确认按钮点击后已解锁。 5. 在教程暂停页或结算页返回菜单:应回到 Chapter 0 的 StoryPage,而不是 SongSelection 页面。 6. 通过普通选曲进入歌曲后返回菜单:应仍回到 SongSelection 页面,且歌曲/难度缓存不受影响。 ## 配置错误的表现 以下任一配置缺失时,TutorialBlock 会输出警告且不会解锁后续剧情:`tutorialKey`、`tutorialProgressVariable`、`tutorialDifficultySaveId`、TutorialCollection 中的曲目、可用的目标难度、`SelectionBoxUIPage` 引用、SelectionBox Prefab 或选项按钮 Prefab。 这是一项运行时保护;本阶段不增加导入器或 Editor 阶段的额外防呆校验。