diff --git a/Assets/Scripts/Game Loop/ChapterController.cs b/Assets/Scripts/Game Loop/ChapterController.cs index c0df6417e..164d78db7 100644 --- a/Assets/Scripts/Game Loop/ChapterController.cs +++ b/Assets/Scripts/Game Loop/ChapterController.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using AibisDream.Kit; +using AibisDream.SaveSystem; using AibisDream.Utility; using UnityEngine; @@ -19,9 +20,9 @@ namespace AibisDream { // 获取存档并提取章节信息 var cacheChapter = string.Empty; - if (ChapterData.TryGetSaveFilePath(out var saveFilePath)) + if (ChapterData.TryGetLatestSaveSceneSoName(out var sceneSoName)) { - cacheChapter = ParseChapterTitle(saveFilePath); + cacheChapter = sceneSoName; } var sourceList = GameManager.Instance.GetAllSceneSos(); @@ -43,23 +44,6 @@ namespace AibisDream return chapterList; } - private static string ParseChapterTitle(string filePath) - { - var fileName = Path.GetFileNameWithoutExtension(filePath); - var parts = fileName.Split('_'); - // 文件名格式: yyyyMMdd-HHmmss_{sceneSoName}_{gameStage} - // sceneSoName 可能包含下划线,但时间戳(第0段)和游戏阶段(最后1段)不会 - if (parts.Length < 3) - { - Debug.LogWarning($"存档文件名格式异常: {fileName}"); - return string.Empty; - } - - var sceneSoName = string.Join("_", parts, 1, parts.Length - 2); - Debug.Log($"ParseChapterTitle: 从 {fileName} 解析出章节名 = {sceneSoName}"); - return sceneSoName; - } - public void StartWithChapter(ChapterVo chapterVo) { GameManager.Instance.StartWithLevel(chapterVo.chapterSo); @@ -67,15 +51,14 @@ namespace AibisDream public void StartWithSaveFile() { - // 获取当前存档 - if (!ChapterData.TryGetSaveFilePath(out var saveFilePath)) + var slotIndex = SlotManager.GetLatestSlotIndex() ?? SlotIndex.Auto; + if (!SlotDirectory.Exists(slotIndex)) { Debug.LogError("未找到存档"); + return; } - else - { - GameManager.Instance.StartWithSaveFile(saveFilePath); - } + + GameManager.Instance.StartWithSlot(slotIndex); } public void UnlockChapter(TalkSceneSO chapterSo) @@ -116,19 +99,22 @@ namespace AibisDream JsonUtil.SaveArray(unlockedChapterList.ToArray(), ConstRef.ChapterProgressPath); } - public bool TryGetSaveFilePath(out string saveFilePath) + public bool TryGetLatestSaveSceneSoName(out string sceneSoName) { - var files = Directory.GetFiles(ConstRef.SaveFilePath, "*.json") - .OrderByDescending(fileName => fileName) - .ToArray(); - - if (files.Length <= 0) + sceneSoName = string.Empty; + var slotIndex = SlotManager.GetLatestSlotIndex() ?? SlotIndex.Auto; + if (!SlotDirectory.Exists(slotIndex)) { - saveFilePath = string.Empty; return false; } - saveFilePath = files[0]; + var meta = SlotManager.LoadMeta(slotIndex); + if (meta == null || string.IsNullOrEmpty(meta.sceneSoName)) + { + return false; + } + + sceneSoName = meta.sceneSoName; return true; } } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs index 1aeeca75f..129c4bbf2 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleKit.cs @@ -35,7 +35,7 @@ namespace AibisDream // 读取现有字典(如果文件存在) Dictionary dataDict; - if (File.Exists(path)) + if (JsonUtil.Exists(path)) { try { @@ -76,7 +76,7 @@ namespace AibisDream try { - if (!File.Exists(path)) + if (!JsonUtil.Exists(path)) { data = default; return false; @@ -124,7 +124,7 @@ namespace AibisDream try { - if (!File.Exists(path)) + if (!JsonUtil.Exists(path)) { Debug.LogWarning($"BlockShapeData 文件不存在: {path}"); return result; @@ -182,7 +182,7 @@ namespace AibisDream // 加载配置 try { - if (!File.Exists(path)) + if (!JsonUtil.Exists(path)) { data = default; return false; @@ -228,7 +228,7 @@ namespace AibisDream // 读取现有字典(如果文件存在) Dictionary dataDict; - if (File.Exists(path)) + if (JsonUtil.Exists(path)) { try { diff --git a/Assets/Scripts/UI/Panel/SavesPanel.cs b/Assets/Scripts/UI/Panel/SavesPanel.cs index e20dffa1a..668ff5ca6 100644 --- a/Assets/Scripts/UI/Panel/SavesPanel.cs +++ b/Assets/Scripts/UI/Panel/SavesPanel.cs @@ -1,5 +1,5 @@ -using System.IO; -using System.Linq; +using System.Linq; +using AibisDream.SaveSystem; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -10,8 +10,6 @@ namespace AibisDream.UI { public bool IsOpen => gameObject.activeSelf; public bool IsCloseable => true; - - private static readonly string SaveFilePath = Application.streamingAssetsPath + "/SaveFiles"; #region 索引 public Button buttonPrefab; @@ -55,23 +53,27 @@ namespace AibisDream.UI Destroy(child.gameObject); } - // 加载存档文件 - string[] saveFiles = Directory.GetFiles(SaveFilePath, "*.json"); - var orderedSaveFiles = saveFiles.OrderByDescending(item => item).ToArray(); - foreach (var saveFile in orderedSaveFiles) + var slots = SlotManager.GetSlotViewModels() + .Where(slot => !slot.IsEmpty) + .OrderByDescending(slot => slot.SavedAt) + .ToArray(); + + foreach (var slot in slots) { var button = Instantiate(buttonPrefab, buttonParent); - button.GetComponentInChildren().text = Path.GetFileNameWithoutExtension(saveFile); - button.onClick.AddListener(() => OnSaveFileSelected(saveFile)); + var label = string.IsNullOrEmpty(slot.SceneSoName) + ? $"Slot {slot.SlotIndex}" + : slot.SceneSoName; + button.GetComponentInChildren().text = label; + var slotIndex = slot.SlotIndex; + button.onClick.AddListener(() => OnSlotSelected(slotIndex)); } } - - private void OnSaveFileSelected(string saveFileName) + + private void OnSlotSelected(int slotIndex) { - // 隐藏UI gameObject.SetActive(false); - // 开始游戏 - GameManager.Instance.StartWithSaveFile(saveFileName); + GameManager.Instance.StartWithSlot(slotIndex); } } } \ No newline at end of file diff --git a/Assets/Scripts/UI/SaveFileUIController.cs b/Assets/Scripts/UI/SaveFileUIController.cs index b212362bc..763cea2e5 100644 --- a/Assets/Scripts/UI/SaveFileUIController.cs +++ b/Assets/Scripts/UI/SaveFileUIController.cs @@ -1,5 +1,5 @@ -using System.IO; using System.Linq; +using AibisDream.SaveSystem; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -8,7 +8,6 @@ namespace AibisDream { public class SaveFileUIController : MonoBehaviour { - private static readonly string SaveFilePath = Application.streamingAssetsPath + "/SaveFiles"; #region 索引 public Button buttonPrefab; @@ -29,25 +28,29 @@ namespace AibisDream Destroy(child.gameObject); } - // 加载存档文件 - string[] saveFiles = Directory.GetFiles(SaveFilePath, "*.json"); - var orderedSaveFiles = saveFiles.OrderByDescending(item => item).ToArray(); - foreach (var saveFile in orderedSaveFiles) + var slots = SlotManager.GetSlotViewModels() + .Where(slot => !slot.IsEmpty) + .OrderByDescending(slot => slot.SavedAt) + .ToArray(); + + foreach (var slot in slots) { var button = Instantiate(buttonPrefab, buttonParent); - button.GetComponentInChildren().text = Path.GetFileNameWithoutExtension(saveFile); - button.onClick.AddListener(() => OnSaveFileSelected(saveFile)); + var label = string.IsNullOrEmpty(slot.SceneSoName) + ? $"Slot {slot.SlotIndex}" + : slot.SceneSoName; + button.GetComponentInChildren().text = label; + var slotIndex = slot.SlotIndex; + button.onClick.AddListener(() => OnSlotSelected(slotIndex)); } gameObject.SetActive(true); } - - private void OnSaveFileSelected(string saveFileName) + + private void OnSlotSelected(int slotIndex) { - // 隐藏UI gameObject.SetActive(false); - // 开始游戏 - // GameLoopManager.Instance.StartWithSaveFile(saveFileName); + GameManager.Instance.StartWithSlot(slotIndex); } private void HideSaveFileUI() diff --git a/Assets/Scripts/Utility/JsonUtil.cs b/Assets/Scripts/Utility/JsonUtil.cs index 5d036976b..2aef5f6c5 100644 --- a/Assets/Scripts/Utility/JsonUtil.cs +++ b/Assets/Scripts/Utility/JsonUtil.cs @@ -4,6 +4,8 @@ using System.IO; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using UnityEngine; +using UnityEngine.Networking; namespace AibisDream.Utility { @@ -26,6 +28,33 @@ namespace AibisDream.Utility // 缓存的 JsonSerializer 实例,用于字典反序列化优化 private static readonly JsonSerializer _cachedSerializer = JsonSerializer.Create(_defaultSettings); + /// + /// 判断文件是否存在。Android 上 StreamingAssets 在 APK 内, 不可靠。 + /// + public static bool Exists(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return false; + } + +#if UNITY_ANDROID && !UNITY_EDITOR + if (filePath.Contains(Application.streamingAssetsPath)) + { + try + { + ReadFileText(filePath); + return true; + } + catch (IOException) + { + return false; + } + } +#endif + return File.Exists(filePath); + } + /// /// 根据平台读取文件内容 ///