using Ichni.Menu; using Ichni.Menu.UI; using System.Collections.Generic; using UnityEngine; namespace Ichni.Story { /// /// TutorialBlock 的菜单层流程协调器。 /// 负责验证配置、生成通用 SelectionBox 选项、写入剧情进度,并在“游玩”时发起 GameScene 运行。 /// 不保存“教程成绩”或“教程是否通关”;本阶段规则是玩家确认游玩或跳过即视为已处理。 /// public static class TutorialFlowController { /// /// 由 点击时调用。配置无效或 SelectionBox 页面未配置时只报错,不修改剧情进度。 /// public static void RequestTutorial(StoryBlockDefinition tutorialBlock) { if (!TryResolveTutorialRunData(tutorialBlock, out ChapterSelectionUnit chapter, out SongItemData song, out DifficultyData difficulty)) { return; } MenuManager menuManager = MenuManager.instance; SelectionBoxUIPage selectionBoxPage = menuManager?.selectionBoxUIPage; if (selectionBoxPage == null) { Debug.LogWarning("[TutorialFlowController] MenuManager 未配置 SelectionBoxUIPage,教程不会被解锁。"); return; } // TutorialFlowController 只描述“有哪些选择、各自触发什么效果”; // 具体按钮由通用 SelectionBox 在运行时生成,后续其它系统可复用同一 UI。 selectionBoxPage.Show( tutorialBlock.tutorialName, string.Empty, new List { new("游玩教程", () => PlayTutorial(tutorialBlock, chapter, song, difficulty)), new("跳过教程", () => SkipTutorial(tutorialBlock)) }); } private static void PlayTutorial(StoryBlockDefinition tutorialBlock, ChapterSelectionUnit chapter, SongItemData song, DifficultyData difficulty) { MenuManager menuManager = MenuManager.instance; if (menuManager == null || menuManager.isEnteringGame) { Debug.LogWarning("[TutorialFlowController] 菜单正在切换,忽略教程游玩请求。"); return; } // 先验证能否启动 GameScene,再写入“已处理”进度;避免无效配置导致错误解锁。 if (!CanBeginTutorialGame(chapter, song, difficulty)) return; if (!StoryProgress.ResolveTutorial(tutorialBlock)) return; // 规则:玩家确认“游玩教程”的瞬间即解锁后续剧情,不等待教程结算。 menuManager.TryBeginGame(chapter, song, difficulty, MenuReturnDestination.Story, menuManager.storyUIPage); } private static void SkipTutorial(StoryBlockDefinition tutorialBlock) { // 规则:玩家确认“跳过教程”的瞬间即解锁后续剧情,并保留在故事树。 StoryProgress.ResolveTutorial(tutorialBlock); } private static bool CanBeginTutorialGame(ChapterSelectionUnit chapter, SongItemData song, DifficultyData difficulty) { if (MenuManager.instance == null || InformationTransistor.instance == null || !MenuManager.instance.CanBeginGame) { Debug.LogWarning("[TutorialFlowController] MenuManager、InformationTransistor 或 GameScene 预加载未就绪,无法进入教程。"); return false; } if (chapter == null || song == null || difficulty == null) { Debug.LogWarning("[TutorialFlowController] 教程缺少章节、歌曲或难度配置,无法进入教程。"); return false; } return true; } /// /// 解析 TutorialBlock 的 Key、教程曲目与稳定难度 ID。 /// 任何一项缺失都会失败且不写入剧情变量,防止错误配置意外解锁后续内容。 /// private static bool TryResolveTutorialRunData(StoryBlockDefinition tutorialBlock, out ChapterSelectionUnit chapter, out SongItemData song, out DifficultyData difficulty) { chapter = ChapterSelectionManager.instance?.currentChapter; song = null; difficulty = null; if (tutorialBlock == null || tutorialBlock.blockType != StoryBlockType.Tutorial || string.IsNullOrWhiteSpace(tutorialBlock.tutorialKey) || string.IsNullOrWhiteSpace(tutorialBlock.tutorialProgressVariable) || tutorialBlock.tutorialDifficultySaveId < 0) { Debug.LogWarning($"[TutorialFlowController] TutorialBlock '{tutorialBlock?.blockId}' 的 Tutorial Key、Progress Variable 或 Difficulty Save ID 未配置完整。"); return false; } TutorialCollection collection = ChapterSelectionManager.instance?.tutorialCollection; if (chapter == null || collection == null || !collection.TryGetTutorialSong(tutorialBlock.tutorialKey, out song)) { Debug.LogWarning($"[TutorialFlowController] TutorialBlock '{tutorialBlock.blockId}' 无法解析章节或教程曲目。"); return false; } if (song.difficultyDataList == null) { Debug.LogWarning($"[TutorialFlowController] 教程 '{tutorialBlock.tutorialKey}' 没有难度列表。"); return false; } foreach (DifficultyData candidate in song.difficultyDataList) { if (candidate != null && candidate.isAvailable && candidate.saveDifficultyId == tutorialBlock.tutorialDifficultySaveId) { difficulty = candidate; return true; } } Debug.LogWarning($"[TutorialFlowController] 教程 '{tutorialBlock.tutorialKey}' 中不存在可用的 Difficulty Save ID '{tutorialBlock.tutorialDifficultySaveId}'。"); return false; } } }