diff --git a/Assets/_Game/Scripts/Core/Save/EmergencySaveService.cs b/Assets/_Game/Scripts/Core/Save/EmergencySaveService.cs index ca4cf9b..acd37aa 100644 --- a/Assets/_Game/Scripts/Core/Save/EmergencySaveService.cs +++ b/Assets/_Game/Scripts/Core/Save/EmergencySaveService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using UnityEngine; using BaseGames.Core.Events; @@ -32,7 +33,16 @@ namespace BaseGames.Core.Save if (_timer >= _intervalSeconds) { _timer = 0f; - _ = _saveManager.SaveAsync(EmergencySlot); + RunFireAndForget(_saveManager.SaveAsync(EmergencySlot), "EmergencySave"); + } + } + + private static async void RunFireAndForget(System.Threading.Tasks.Task task, string context) + { + try { await task; } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[EmergencySave] {context} 失败: {e.Message}\n{e.StackTrace}"); } } diff --git a/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs b/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs index 74fdf17..80dc7f1 100644 --- a/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs +++ b/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs @@ -88,12 +88,13 @@ namespace BaseGames.Core.Save _current.Meta.SlotIndex = targetSlot; _current.Meta.SaveCount++; - // 先清空 checksum,序列化并计算,再序列化含 checksum 的最终版本 - // 使用 Formatting.None 减少序列化字符串体积和 GC 分配 + // 清空 checksum 后序列化,计算 HMAC,然后直接注入序列化字符串——避免第二次完整序列化。 + // Base64 字符集(A-Z a-z 0-9 + / =)不含需要 JSON 转义的字符,string.Replace 是安全替换。 _current.Meta.Checksum = null; string jsonForChecksum = JsonConvert.SerializeObject(_current, Formatting.None); - _current.Meta.Checksum = ComputeChecksum(jsonForChecksum); - string finalJson = JsonConvert.SerializeObject(_current, Formatting.None); + string checksum = ComputeChecksum(jsonForChecksum); + string finalJson = jsonForChecksum.Replace("\"Checksum\":null", $"\"Checksum\":\"{checksum}\""); + _current.Meta.Checksum = checksum; // 同步回内存,保持对象一致 await _storage.WriteAsync(targetSlot, finalJson); diff --git a/Assets/_Game/Scripts/Core/Save/SaveData.cs b/Assets/_Game/Scripts/Core/Save/SaveData.cs index 79a6e91..2e2cf69 100644 --- a/Assets/_Game/Scripts/Core/Save/SaveData.cs +++ b/Assets/_Game/Scripts/Core/Save/SaveData.cs @@ -128,8 +128,7 @@ namespace BaseGames.Core.Save [Serializable] public class QuestSaveData { - public Dictionary QuestStates = new(); - public List AvailableQuestIds = new(); + public Dictionary QuestStates = new(); } [Serializable] diff --git a/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs b/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs index 240f672..dc11472 100644 --- a/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs +++ b/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs @@ -50,10 +50,7 @@ namespace BaseGames.Core.Save // 2.2 删除 PlayerSaveData.ShieldHP / ShieldIsBroken(护盾在存档点始终全满,无需持久化)。 // SettingsSaveData.Language 字段保留(由 LocalizationManager 负责按存档槽读写)。 // 旧存档中已删除的字段由 Newtonsoft.Json 的 [JsonExtensionData] 忽略,无需额外处理。 - // Equipment.MaxNotches:旧存档若为 0,EquipmentManager.OnLoad 回退到初始 Notch 数量。 - if (data.Equipment != null && data.Equipment.MaxNotches == 0) - data.Equipment.MaxNotches = 0; // 保持 0,OnLoad 回退到 config.initialNotchCount - + // Equipment.MaxNotches:旧存档若为 0,EquipmentManager.OnLoad 回退到 config.initialNotchCount,无需额外处理。 Debug.Log("[SaveMigrator] 从 '2.1' 迁移至 '2.2'。"); v = "2.2"; } diff --git a/Assets/_Game/Scripts/Quest/QuestManager.cs b/Assets/_Game/Scripts/Quest/QuestManager.cs index fc85aed..c465e3d 100644 --- a/Assets/_Game/Scripts/Quest/QuestManager.cs +++ b/Assets/_Game/Scripts/Quest/QuestManager.cs @@ -129,7 +129,6 @@ namespace BaseGames.Quest public void OnSave(SaveData data) { data.Quests.QuestStates.Clear(); - data.Quests.AvailableQuestIds.Clear(); foreach (var (id, state) in _questStates) { data.Quests.QuestStates[id] = new BaseGames.Core.Save.QuestState @@ -138,7 +137,6 @@ namespace BaseGames.Quest ObjectiveIndex = 0, ProgressCounts = BuildProgressList(id), }; - if (state == QuestStateEnum.Available) data.Quests.AvailableQuestIds.Add(id); } } diff --git a/Assets/_Game/Scripts/World/SavePoint.cs b/Assets/_Game/Scripts/World/SavePoint.cs index d4223d0..875defc 100644 --- a/Assets/_Game/Scripts/World/SavePoint.cs +++ b/Assets/_Game/Scripts/World/SavePoint.cs @@ -1,3 +1,5 @@ +using System; +using System.Threading.Tasks; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; @@ -68,7 +70,7 @@ namespace BaseGames.World // 触发存档:OnSave() 由 SaveAsync 回调所有 ISaveable,包含本组件 var svc = ServiceLocator.GetOrDefault(); if (svc != null) - _ = svc.SaveAsync(svc.ActiveSlot); + RunFireAndForget(svc.SaveAsync(svc.ActiveSlot)); else Debug.LogWarning("[SavePoint] ISaveService 未注册,跳过存档。", this); } @@ -101,6 +103,14 @@ namespace BaseGames.World _isActivated = !string.IsNullOrEmpty(_savePointId) && data.World.ActivatedSavePoints.Contains(_savePointId); } + + private static async void RunFireAndForget(Task task) + { + try { await task; } + catch (Exception e) + { + Debug.LogError($"[SavePoint] 存档失败: {e.Message}\n{e.StackTrace}"); + } + } } } -