diff --git a/Assets/Tests/EditMode/SaveSystemTests.cs b/Assets/Tests/EditMode/SaveSystemTests.cs index 6ac1b0a..e1ea111 100644 --- a/Assets/Tests/EditMode/SaveSystemTests.cs +++ b/Assets/Tests/EditMode/SaveSystemTests.cs @@ -143,16 +143,16 @@ namespace BaseGames.Tests.EditMode public void SaveData_SerializeDeserialize_MetaVersionPreserved() { var original = new SaveData(); - original.Meta.Version = "2.1"; + original.Meta.Version = SaveMigrator.CurrentVersion; original.Meta.SlotIndex = 2; original.Meta.SaveCount = 42; string json = JsonConvert.SerializeObject(original, Formatting.None); var restored = JsonConvert.DeserializeObject(json); - Assert.AreEqual("2.1", restored.Meta.Version); - Assert.AreEqual(2, restored.Meta.SlotIndex); - Assert.AreEqual(42, restored.Meta.SaveCount); + Assert.AreEqual(SaveMigrator.CurrentVersion, restored.Meta.Version); + Assert.AreEqual(2, restored.Meta.SlotIndex); + Assert.AreEqual(42, restored.Meta.SaveCount); } [Test] @@ -191,23 +191,6 @@ namespace BaseGames.Tests.EditMode () => JsonConvert.SerializeObject(data, Formatting.None)); } - // ── PlayerSaveData · ShieldHP 默认值 ──────────────────────────────── - - [Test] - public void PlayerSaveData_ShieldHP_DefaultIsMinusOne() - { - var player = new PlayerSaveData(); - Assert.AreEqual(-1, player.ShieldHP, - "ShieldHP 默认 -1 表示满护盾"); - } - - [Test] - public void PlayerSaveData_ShieldIsBroken_DefaultIsFalse() - { - var player = new PlayerSaveData(); - Assert.IsFalse(player.ShieldIsBroken); - } - // ── SaveMeta · IsSteelSoul ──────────────────────────────────────────── [Test] diff --git a/Assets/_Game/Scripts/Core/ILingZhuProvider.cs.meta b/Assets/_Game/Scripts/Core/ILingZhuProvider.cs.meta new file mode 100644 index 0000000..a4fd4da --- /dev/null +++ b/Assets/_Game/Scripts/Core/ILingZhuProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44b3cc2acf7eadd478af66d7d4cf770a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs b/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs index 9bbe23e..551b6bf 100644 --- a/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs +++ b/Assets/_Game/Scripts/Core/Save/GameSaveManager.cs @@ -54,6 +54,13 @@ namespace BaseGames.Core.Save public void Register(ISaveable s) => _saveables.Add(s); public void Unregister(ISaveable s) => _saveables.Remove(s); + // ── 游玩时间追踪 ────────────────────────────────────────────────────── + private void Update() + { + if (_current != null) + _current.Meta.Playtime += Time.unscaledDeltaTime; + } + // ── 存档 ────────────────────────────────────────────────────────────── public async Task SaveAsync(int slot = -1) { diff --git a/Assets/_Game/Scripts/Core/Save/SaveData.cs b/Assets/_Game/Scripts/Core/Save/SaveData.cs index 16eeec0..79a6e91 100644 --- a/Assets/_Game/Scripts/Core/Save/SaveData.cs +++ b/Assets/_Game/Scripts/Core/Save/SaveData.cs @@ -34,7 +34,7 @@ namespace BaseGames.Core.Save [Serializable] public class SaveMeta { - public string Version = "2.1"; + public string Version = "2.2"; public int SlotIndex; public string LastSaved; // ISO 8601 public float Playtime; @@ -61,10 +61,6 @@ namespace BaseGames.Core.Save public List UnlockedFormIds = new(); public DeathShadeSaveData DeathShade; - - // 护盾:-1 = 满护盾(默认),>= 0 = 当前耐久值 - public int ShieldHP = -1; - public bool ShieldIsBroken = false; } [Serializable] @@ -80,7 +76,6 @@ namespace BaseGames.Core.Save public class EquipmentSaveData { public List EquippedCharmIds = new(); - public int NotchesUsed; public int MaxNotches; public List OwnedCharmIds = new(); public List UpgradedCharmIds = new(); @@ -252,7 +247,7 @@ namespace BaseGames.Core.Save [Serializable] public class SettingsSaveData { - /// 玩家选择的语言。空字符串 = 使用系统默认。 + /// 玩家选择的语言。空字符串 = 使用系统默认。由 LocalizationManager 读写。 public string Language = string.Empty; } } diff --git a/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs b/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs index 3ccb45a..30679f1 100644 --- a/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs +++ b/Assets/_Game/Scripts/Core/Save/SaveMigrator.cs @@ -4,11 +4,11 @@ namespace BaseGames.Core.Save { /// /// 处理存档版本迁移。 - /// 迁移链:旧版本 → "2.0" → "2.1"(CurrentVersion),每个分支按顺序落下执行(fall-through)。 + /// 迁移链:旧版本 → "2.0" → "2.1" → "2.2"(CurrentVersion),每个分支按顺序落下执行(fall-through)。 /// public static class SaveMigrator { - public const string CurrentVersion = "2.1"; + public const string CurrentVersion = "2.2"; public static SaveData Migrate(SaveData data) { @@ -43,6 +43,21 @@ namespace BaseGames.Core.Save v = "2.1"; } + // ── 2.1 → 2.2 ─────────────────────────────────────────────────────── + if (v == "2.1") + { + // 2.2 删除 EquipmentSaveData.NotchesUsed(冗余,由 TryEquipCharm 重新计算)。 + // 2.2 删除 PlayerSaveData.ShieldHP / ShieldIsBroken(护盾在存档点始终全满,无需持久化)。 + // 2.2 删除 SettingsSaveData.Language(全局设置由 SettingsManager 写入 settings.json)。 + // 旧存档中这些字段由 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 + + Debug.Log("[SaveMigrator] 从 '2.1' 迁移至 '2.2'。"); + v = "2.2"; + } + // ── 未识别的未来版本 ───────────────────────────────────────────────── if (v != CurrentVersion) Debug.LogWarning($"[SaveMigrator] 未知版本 '{v}',将直接使用(可能存在兼容性问题)。"); diff --git a/Assets/_Game/Scripts/Equipment/EquipmentManager.cs b/Assets/_Game/Scripts/Equipment/EquipmentManager.cs index 3aa20fd..d60c3ce 100644 --- a/Assets/_Game/Scripts/Equipment/EquipmentManager.cs +++ b/Assets/_Game/Scripts/Equipment/EquipmentManager.cs @@ -34,6 +34,9 @@ namespace BaseGames.Equipment private EquipmentContext _ctx; // ── 生命周期 ────────────────────────────────────────────────────────── + private void OnEnable() => ServiceLocator.GetOrDefault()?.Register(this); + private void OnDisable() => ServiceLocator.GetOrDefault()?.Unregister(this); + private void Awake() { _ctx = new EquipmentContext @@ -111,7 +114,7 @@ namespace BaseGames.Equipment data.Equipment.EquippedCharmIds.AddRange(_equipped.Select(c => c.charmId)); data.Equipment.OwnedCharmIds.Clear(); data.Equipment.OwnedCharmIds.AddRange(_collected.Select(c => c.charmId)); - data.Equipment.NotchesUsed = UsedNotches; + data.Equipment.MaxNotches = _currentNotchCapacity; } public void OnLoad(SaveData data) @@ -122,6 +125,11 @@ namespace BaseGames.Equipment _equipped.Clear(); _usedNotches = 0; + // 恢复 Notch 上限(若存档值为 0 则使用初始默认值) + _currentNotchCapacity = data.Equipment.MaxNotches > 0 + ? data.Equipment.MaxNotches + : _config.initialNotchCount; + // 从 CharmCatalog 按 ID 恢复收藏并重新装备 _collected.Clear(); foreach (var id in data.Equipment.OwnedCharmIds) diff --git a/Assets/_Game/Scripts/Player/FormController.cs b/Assets/_Game/Scripts/Player/FormController.cs index 5aabb79..b95dbdd 100644 --- a/Assets/_Game/Scripts/Player/FormController.cs +++ b/Assets/_Game/Scripts/Player/FormController.cs @@ -1,5 +1,7 @@ using System; using UnityEngine; +using BaseGames.Core; +using BaseGames.Core.Save; using BaseGames.Core.Events; using BaseGames.Input; @@ -13,7 +15,7 @@ namespace BaseGames.Player /// 3. _onSkillSetChanged SO 事件(SkillHUD 刷新) /// 架构 05_PlayerModule §6。 /// - public class FormController : MonoBehaviour + public class FormController : MonoBehaviour, ISaveable { [Header("配置")] [SerializeField] private FormConfigSO _config; @@ -37,6 +39,7 @@ namespace BaseGames.Player private void OnEnable() { + ServiceLocator.GetOrDefault()?.Register(this); if (_input == null) return; _input.SwitchSkyFormEvent += OnSwitchSky; _input.SwitchEarthFormEvent += OnSwitchEarth; @@ -45,6 +48,7 @@ namespace BaseGames.Player private void OnDisable() { + ServiceLocator.GetOrDefault()?.Unregister(this); if (_input == null) return; _input.SwitchSkyFormEvent -= OnSwitchSky; _input.SwitchEarthFormEvent -= OnSwitchEarth; @@ -86,6 +90,31 @@ namespace BaseGames.Player SwitchForm(form.formType); } + // ── ISaveable ──────────────────────────────────────────────────────────── + public void OnSave(SaveData data) + { + data.Player.ActiveFormId = CurrentForm?.formId; + } + + public void OnLoad(SaveData data) + { + if (string.IsNullOrEmpty(data.Player.ActiveFormId) || _config?.forms == null) + { + if (_config?.forms != null && _config.forms.Length > 0) + CurrentForm = _config.forms[0]; + return; + } + + for (int i = 0; i < _config.forms.Length; i++) + { + if (_config.forms[i]?.formId == data.Player.ActiveFormId) + { + SwitchToFormByIndex(i); + return; + } + } + } + // ── 内部输入处理 ──────────────────────────────────────────────────────── private void OnSwitchSky() => SwitchForm(FormType.TianHun); private void OnSwitchEarth() => SwitchForm(FormType.DiHun); diff --git a/Assets/_Game/Scripts/World/DeathShadeManager.cs.meta b/Assets/_Game/Scripts/World/DeathShadeManager.cs.meta new file mode 100644 index 0000000..6931847 --- /dev/null +++ b/Assets/_Game/Scripts/World/DeathShadeManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bf433e2cc0b7a9499692239ed9fcd92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/World/Map/MapManager.cs b/Assets/_Game/Scripts/World/Map/MapManager.cs index 0afe441..082c5ac 100644 --- a/Assets/_Game/Scripts/World/Map/MapManager.cs +++ b/Assets/_Game/Scripts/World/Map/MapManager.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; @@ -53,18 +52,14 @@ namespace BaseGames.World.Map public void OnSave(SaveData data) { - data.Map.ExploredRooms ??= new List(); - data.Map.ExploredRooms.Clear(); - data.Map.ExploredRooms.AddRange(_exploredRooms); - data.Map.MappedRooms ??= new List(); - data.Map.MappedRooms.Clear(); - data.Map.MappedRooms.AddRange(_mappedRooms); + data.Map.ExploredRooms = new HashSet(_exploredRooms); + data.Map.MappedRooms = new HashSet(_mappedRooms); } public void OnLoad(SaveData data) { - _exploredRooms = new HashSet(data.Map.ExploredRooms ?? new System.Collections.Generic.List()); - _mappedRooms = new HashSet(data.Map.MappedRooms ?? new System.Collections.Generic.List()); + _exploredRooms = data.Map.ExploredRooms != null ? new HashSet(data.Map.ExploredRooms) : new HashSet(); + _mappedRooms = data.Map.MappedRooms != null ? new HashSet(data.Map.MappedRooms) : new HashSet(); } // ── 事件驱动房间发现 ────────────────────────────────────────────────── diff --git a/Assets/_Game/Scripts/World/WorldStateRegistrySaver.cs.meta b/Assets/_Game/Scripts/World/WorldStateRegistrySaver.cs.meta new file mode 100644 index 0000000..c000f72 --- /dev/null +++ b/Assets/_Game/Scripts/World/WorldStateRegistrySaver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 751a997be7ac4f748abb20be90b615d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: