Compare commits
2 Commits
e50cf57321
...
28c1059fe2
| Author | SHA1 | Date | |
|---|---|---|---|
| 28c1059fe2 | |||
| bc7063fb95 |
@@ -221,6 +221,9 @@ namespace BaseGames.Core.Save
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 存档槽摘要 ────────────────────────────────────────────────────────
|
// ── 存档槽摘要 ────────────────────────────────────────────────────────
|
||||||
|
/// <summary>
|
||||||
|
/// 仅解析 Meta 和 Player 两个顶层字段,避免反序列化整个 SaveData 造成不必要的 GC 压力。
|
||||||
|
/// </summary>
|
||||||
public async Task<SlotSummary> GetSlotSummaryAsync(int slotIndex)
|
public async Task<SlotSummary> GetSlotSummaryAsync(int slotIndex)
|
||||||
{
|
{
|
||||||
if (!_storage.Exists(slotIndex)) return null;
|
if (!_storage.Exists(slotIndex)) return null;
|
||||||
@@ -228,14 +231,14 @@ namespace BaseGames.Core.Save
|
|||||||
if (json == null) return null;
|
if (json == null) return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = JsonConvert.DeserializeObject<SaveData>(json);
|
var root = Newtonsoft.Json.Linq.JObject.Parse(json);
|
||||||
return new SlotSummary
|
return new SlotSummary
|
||||||
{
|
{
|
||||||
SlotIndex = slotIndex,
|
SlotIndex = slotIndex,
|
||||||
Playtime = data.Meta.Playtime,
|
Playtime = (float?)root["Meta"]?["Playtime"] ?? 0f,
|
||||||
LastSaved = data.Meta.LastSaved,
|
LastSaved = (string)root["Meta"]?["LastSaved"],
|
||||||
SceneName = data.Player?.Scene,
|
SceneName = (string)root["Player"]?["Scene"],
|
||||||
ActiveFormId = data.Player?.ActiveFormId,
|
ActiveFormId = (string)root["Player"]?["ActiveFormId"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch { return null; }
|
catch { return null; }
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ namespace BaseGames.Core.Save
|
|||||||
public async Task WriteAsync(int slotIndex, string json)
|
public async Task WriteAsync(int slotIndex, string json)
|
||||||
{
|
{
|
||||||
var path = GetPath(slotIndex);
|
var path = GetPath(slotIndex);
|
||||||
// 先写临时文件再原子性替换,防止写入中途断电损坏存档
|
// 先写临时文件再原子性替换,防止写入中途断电损坏存档。
|
||||||
|
// File.Replace 要求 destination 必须已存在;首次存档时直接 Move。
|
||||||
var tmp = path + ".tmp";
|
var tmp = path + ".tmp";
|
||||||
await File.WriteAllTextAsync(tmp, json);
|
await File.WriteAllTextAsync(tmp, json);
|
||||||
File.Replace(tmp, path, path + ".bak", ignoreMetadataErrors: true);
|
if (File.Exists(path))
|
||||||
|
File.Replace(tmp, path, path + ".bak", ignoreMetadataErrors: true);
|
||||||
|
else
|
||||||
|
File.Move(tmp, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ReadAsync(int slotIndex)
|
public async Task<string> ReadAsync(int slotIndex)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace BaseGames.Core.Save
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class SaveMeta
|
public class SaveMeta
|
||||||
{
|
{
|
||||||
public string Version = "2.2";
|
public string Version = SaveMigrator.CurrentVersion;
|
||||||
public int SlotIndex;
|
public int SlotIndex;
|
||||||
public string LastSaved; // ISO 8601
|
public string LastSaved; // ISO 8601
|
||||||
public float Playtime;
|
public float Playtime;
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ namespace BaseGames.Equipment
|
|||||||
{
|
{
|
||||||
data.Tools.ToolSlot0 = _slots[0]?.toolId;
|
data.Tools.ToolSlot0 = _slots[0]?.toolId;
|
||||||
data.Tools.ToolSlot1 = _slots[1]?.toolId;
|
data.Tools.ToolSlot1 = _slots[1]?.toolId;
|
||||||
|
// 持久化剩余使用次数(-1 = 无限,保持原值)
|
||||||
|
data.Tools.ToolStates["Slot0_Uses"] = Newtonsoft.Json.Linq.JObject.FromObject(new { uses = _remainingUses[0] });
|
||||||
|
data.Tools.ToolStates["Slot1_Uses"] = Newtonsoft.Json.Linq.JObject.FromObject(new { uses = _remainingUses[1] });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnLoad(SaveData data)
|
public void OnLoad(SaveData data)
|
||||||
@@ -90,6 +93,12 @@ namespace BaseGames.Equipment
|
|||||||
|
|
||||||
EquipTool(0, _toolCatalog.Find(data.Tools.ToolSlot0));
|
EquipTool(0, _toolCatalog.Find(data.Tools.ToolSlot0));
|
||||||
EquipTool(1, _toolCatalog.Find(data.Tools.ToolSlot1));
|
EquipTool(1, _toolCatalog.Find(data.Tools.ToolSlot1));
|
||||||
|
|
||||||
|
// 恢复剩余使用次数(EquipTool 会重置为 maxUses,此处覆盖还原)
|
||||||
|
if (data.Tools.ToolStates.TryGetValue("Slot0_Uses", out var uses0))
|
||||||
|
_remainingUses[0] = (int?)uses0["uses"] ?? _remainingUses[0];
|
||||||
|
if (data.Tools.ToolStates.TryGetValue("Slot1_Uses", out var uses1))
|
||||||
|
_remainingUses[1] = (int?)uses1["uses"] ?? _remainingUses[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ namespace BaseGames.Player
|
|||||||
{
|
{
|
||||||
if (_config.forms[i]?.formId == data.Player.ActiveFormId)
|
if (_config.forms[i]?.formId == data.Player.ActiveFormId)
|
||||||
{
|
{
|
||||||
SwitchToFormByIndex(i);
|
// 直接赋值,不触发事件——加载时场景尚未完全就绪,订阅者(WeaponManager 等)
|
||||||
|
// 会在各自 OnEnable + Register → OnLoad 回调中自行恢复状态。
|
||||||
|
CurrentForm = _config.forms[i];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using BaseGames.Core;
|
using BaseGames.Core;
|
||||||
|
using BaseGames.Core.Events;
|
||||||
using BaseGames.Core.Save;
|
using BaseGames.Core.Save;
|
||||||
using BaseGames.Platform;
|
using BaseGames.Platform;
|
||||||
|
|
||||||
@@ -97,6 +98,9 @@ namespace BaseGames.Progression
|
|||||||
if (_states.TryGetValue(kv.Key, out var state))
|
if (_states.TryGetValue(kv.Key, out var state))
|
||||||
state.Progress = kv.Value.Percent;
|
state.Progress = kv.Value.Percent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载后立即重新评估所有未解锁成就,避免离线满足条件的成就须等到下次游戏事件才触发。
|
||||||
|
EvaluateAll(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 轮询检查 ──────────────────────────────────────────────────────────
|
// ── 轮询检查 ──────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -33,11 +33,18 @@ namespace BaseGames.UI.Menus
|
|||||||
{
|
{
|
||||||
var svc = ServiceLocator.GetOrDefault<ISaveService>();
|
var svc = ServiceLocator.GetOrDefault<ISaveService>();
|
||||||
if (svc == null) return;
|
if (svc == null) return;
|
||||||
|
|
||||||
|
// 并行加载所有槽位摘要,减少主菜单等待时间
|
||||||
|
var tasks = new Task<SlotSummary>[_slotUIs.Length];
|
||||||
|
for (int i = 0; i < _slotUIs.Length; i++)
|
||||||
|
tasks[i] = svc.GetSlotSummaryAsync(i);
|
||||||
|
|
||||||
|
var summaries = await Task.WhenAll(tasks);
|
||||||
|
|
||||||
for (int i = 0; i < _slotUIs.Length; i++)
|
for (int i = 0; i < _slotUIs.Length; i++)
|
||||||
{
|
{
|
||||||
if (_slotUIs[i] == null) continue;
|
if (_slotUIs[i] == null) continue;
|
||||||
var summary = await svc.GetSlotSummaryAsync(i);
|
_slotUIs[i].Refresh(summaries[i]);
|
||||||
_slotUIs[i].Refresh(summary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user