From 5a1232274eb3001a97008db28977e31c3d2f8a10 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:41:42 +0800 Subject: [PATCH] =?UTF-8?q?chore(config):=20=E6=B7=BB=E5=8A=A0=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E9=85=8D=E7=BD=AE=E5=92=8C=20Git=20=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置 .editorconfig 文件统一代码风格 - 设置 UTF-8 编码和 LF 行尾 - 为解决方案文件和批处理脚本配置 CRLF 行尾 - 配置 .gitattributes 统一文本文件行尾规范化 - 设置二进制文件不进行行尾转换 - 指定各类源码文件使用 LF 行尾 --- .editorconfig | 18 + .gitattributes | 35 ++ docs/zh-CN/game/data.md | 1148 +++++++++++++++++++-------------------- 3 files changed, 627 insertions(+), 574 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d73be54d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + +[*.sln] +end_of_line = crlf + +[*.bat] +end_of_line = crlf + +[*.cmd] +end_of_line = crlf + +[*.ps1] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f6e20665 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,35 @@ +# Keep repository text normalized to LF unless a file format is known to require CRLF. +* text=auto eol=lf + +# Solution and Windows-native scripts are more interoperable when they keep CRLF in the working tree. +*.sln text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Source, config, scripts, and documentation stay LF across WSL and Windows editors. +*.sh text eol=lf +*.cs text eol=lf +*.csproj text eol=lf +*.props text eol=lf +*.targets text eol=lf +*.json text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.md text eol=lf +*.ts text eol=lf +*.js text eol=lf +*.mts text eol=lf +*.vue text eol=lf +*.css text eol=lf + +# Common binary assets should never be line-normalized. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.zip binary +*.dll binary +*.so binary +*.pdb binary diff --git a/docs/zh-CN/game/data.md b/docs/zh-CN/game/data.md index c6c92dad..607f7fb0 100644 --- a/docs/zh-CN/game/data.md +++ b/docs/zh-CN/game/data.md @@ -1,268 +1,268 @@ ---- -title: 数据与存档系统 +--- +title: 数据与存档系统 description: 数据与存档系统提供统一的数据持久化基础能力,支持多槽位存档、版本化数据和仓库抽象。 ---- - -# 数据与存档系统 - -## 概述 - +--- + +# 数据与存档系统 + +## 概述 + 数据与存档系统是 GFramework.Game 中用于管理游戏数据持久化的核心组件。它提供了统一的数据加载和保存接口,支持多槽位存档管理、版本化数据模式,以及与存储系统、序列化系统的组合使用。 通过数据系统,你可以将游戏数据保存到本地存储,支持多个存档槽位,并在需要时于应用层实现版本迁移。 - -**主要特性**: - -- 统一的数据持久化接口 -- 多槽位存档管理 + +**主要特性**: + +- 统一的数据持久化接口 +- 多槽位存档管理 - 数据版本控制模式 -- 异步加载和保存 -- 批量数据操作 -- 与存储系统集成 - -## 核心概念 - -### 数据接口 - -`IData` 标记数据类型: - -```csharp -public interface IData -{ - // 标记接口,用于标识可持久化的数据 -} -``` - -### 数据仓库 - -`IDataRepository` 提供通用的数据操作: - -```csharp -public interface IDataRepository : IUtility -{ - Task LoadAsync(IDataLocation location) where T : class, IData, new(); - Task SaveAsync(IDataLocation location, T data) where T : class, IData; - Task ExistsAsync(IDataLocation location); - Task DeleteAsync(IDataLocation location); - Task SaveAllAsync(IEnumerable<(IDataLocation, IData)> dataList); -} -``` - -### 存档仓库 - -`ISaveRepository` 专门用于管理游戏存档: - -```csharp -public interface ISaveRepository : IUtility - where TSaveData : class, IData, new() -{ - Task ExistsAsync(int slot); - Task LoadAsync(int slot); - Task SaveAsync(int slot, TSaveData data); - Task DeleteAsync(int slot); - Task> ListSlotsAsync(); -} -``` - -### 版本化数据 - -`IVersionedData` 支持数据版本管理: - -```csharp +- 异步加载和保存 +- 批量数据操作 +- 与存储系统集成 + +## 核心概念 + +### 数据接口 + +`IData` 标记数据类型: + +```csharp +public interface IData +{ + // 标记接口,用于标识可持久化的数据 +} +``` + +### 数据仓库 + +`IDataRepository` 提供通用的数据操作: + +```csharp +public interface IDataRepository : IUtility +{ + Task LoadAsync(IDataLocation location) where T : class, IData, new(); + Task SaveAsync(IDataLocation location, T data) where T : class, IData; + Task ExistsAsync(IDataLocation location); + Task DeleteAsync(IDataLocation location); + Task SaveAllAsync(IEnumerable<(IDataLocation, IData)> dataList); +} +``` + +### 存档仓库 + +`ISaveRepository` 专门用于管理游戏存档: + +```csharp +public interface ISaveRepository : IUtility + where TSaveData : class, IData, new() +{ + Task ExistsAsync(int slot); + Task LoadAsync(int slot); + Task SaveAsync(int slot, TSaveData data); + Task DeleteAsync(int slot); + Task> ListSlotsAsync(); +} +``` + +### 版本化数据 + +`IVersionedData` 支持数据版本管理: + +```csharp public interface IVersionedData : IData { int Version { get; } DateTime LastModified { get; } } -``` - -## 基本用法 - -### 定义数据类型 - -```csharp -using GFramework.Game.Abstractions.Data; - -// 简单数据 -public class PlayerData : IData -{ - public string Name { get; set; } - public int Level { get; set; } - public int Experience { get; set; } -} - -// 版本化数据 -public class SaveData : IVersionedData -{ - public int Version { get; set; } = 1; - public PlayerData Player { get; set; } - public DateTime SaveTime { get; set; } -} -``` - -### 使用存档仓库 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class SaveController : IController -{ - public async Task SaveGame(int slot) - { - var saveRepo = this.GetUtility>(); - - // 创建存档数据 - var saveData = new SaveData - { - Player = new PlayerData - { - Name = "Player1", - Level = 10, - Experience = 1000 - }, - SaveTime = DateTime.Now - }; - - // 保存到指定槽位 - await saveRepo.SaveAsync(slot, saveData); - Console.WriteLine($"游戏已保存到槽位 {slot}"); - } - - public async Task LoadGame(int slot) - { - var saveRepo = this.GetUtility>(); - - // 检查存档是否存在 - if (!await saveRepo.ExistsAsync(slot)) - { - Console.WriteLine($"槽位 {slot} 不存在存档"); - return; - } - - // 加载存档 - var saveData = await saveRepo.LoadAsync(slot); - Console.WriteLine($"加载存档: {saveData.Player.Name}, 等级 {saveData.Player.Level}"); - } - - public async Task DeleteSave(int slot) - { - var saveRepo = this.GetUtility>(); - - // 删除存档 - await saveRepo.DeleteAsync(slot); - Console.WriteLine($"已删除槽位 {slot} 的存档"); - } -} -``` - -### 注册存档仓库 - -```csharp -using GFramework.Game.Data; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 获取存储系统 - var storage = this.GetUtility(); - - // 创建存档配置 - var saveConfig = new SaveConfiguration - { - SaveRoot = "saves", - SaveSlotPrefix = "slot_", - SaveFileName = "save.json" - }; - - // 注册存档仓库 - var saveRepo = new SaveRepository(storage, saveConfig); - RegisterUtility>(saveRepo); - } -} -``` - -## 高级用法 - -### 列出所有存档 - -```csharp -public async Task ShowSaveList() -{ - var saveRepo = this.GetUtility>(); - - // 获取所有存档槽位 - var slots = await saveRepo.ListSlotsAsync(); - - Console.WriteLine($"找到 {slots.Count} 个存档:"); - foreach (var slot in slots) - { - var saveData = await saveRepo.LoadAsync(slot); - Console.WriteLine($"槽位 {slot}: {saveData.Player.Name}, " + - $"等级 {saveData.Player.Level}, " + - $"保存时间 {saveData.SaveTime}"); - } -} -``` - -### 自动保存 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class AutoSaveController : IController -{ - private CancellationTokenSource? _autoSaveCts; - - public void StartAutoSave(int slot, TimeSpan interval) - { - _autoSaveCts = new CancellationTokenSource(); - - Task.Run(async () => - { - while (!_autoSaveCts.Token.IsCancellationRequested) - { - await Task.Delay(interval, _autoSaveCts.Token); - - try - { - await SaveGame(slot); - Console.WriteLine("自动保存完成"); - } - catch (Exception ex) - { - Console.WriteLine($"自动保存失败: {ex.Message}"); - } - } - }, _autoSaveCts.Token); - } - - public void StopAutoSave() - { - _autoSaveCts?.Cancel(); - _autoSaveCts?.Dispose(); - _autoSaveCts = null; - } - - private async Task SaveGame(int slot) - { - var saveRepo = this.GetUtility>(); - var saveData = CreateSaveData(); - await saveRepo.SaveAsync(slot, saveData); - } - - private SaveData CreateSaveData() - { - // 从游戏状态创建存档数据 - return new SaveData(); - } -} -``` - +``` + +## 基本用法 + +### 定义数据类型 + +```csharp +using GFramework.Game.Abstractions.Data; + +// 简单数据 +public class PlayerData : IData +{ + public string Name { get; set; } + public int Level { get; set; } + public int Experience { get; set; } +} + +// 版本化数据 +public class SaveData : IVersionedData +{ + public int Version { get; set; } = 1; + public PlayerData Player { get; set; } + public DateTime SaveTime { get; set; } +} +``` + +### 使用存档仓库 + +```csharp +using GFramework.Core.Abstractions.Controller; +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +public partial class SaveController : IController +{ + public async Task SaveGame(int slot) + { + var saveRepo = this.GetUtility>(); + + // 创建存档数据 + var saveData = new SaveData + { + Player = new PlayerData + { + Name = "Player1", + Level = 10, + Experience = 1000 + }, + SaveTime = DateTime.Now + }; + + // 保存到指定槽位 + await saveRepo.SaveAsync(slot, saveData); + Console.WriteLine($"游戏已保存到槽位 {slot}"); + } + + public async Task LoadGame(int slot) + { + var saveRepo = this.GetUtility>(); + + // 检查存档是否存在 + if (!await saveRepo.ExistsAsync(slot)) + { + Console.WriteLine($"槽位 {slot} 不存在存档"); + return; + } + + // 加载存档 + var saveData = await saveRepo.LoadAsync(slot); + Console.WriteLine($"加载存档: {saveData.Player.Name}, 等级 {saveData.Player.Level}"); + } + + public async Task DeleteSave(int slot) + { + var saveRepo = this.GetUtility>(); + + // 删除存档 + await saveRepo.DeleteAsync(slot); + Console.WriteLine($"已删除槽位 {slot} 的存档"); + } +} +``` + +### 注册存档仓库 + +```csharp +using GFramework.Game.Data; + +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 获取存储系统 + var storage = this.GetUtility(); + + // 创建存档配置 + var saveConfig = new SaveConfiguration + { + SaveRoot = "saves", + SaveSlotPrefix = "slot_", + SaveFileName = "save.json" + }; + + // 注册存档仓库 + var saveRepo = new SaveRepository(storage, saveConfig); + RegisterUtility>(saveRepo); + } +} +``` + +## 高级用法 + +### 列出所有存档 + +```csharp +public async Task ShowSaveList() +{ + var saveRepo = this.GetUtility>(); + + // 获取所有存档槽位 + var slots = await saveRepo.ListSlotsAsync(); + + Console.WriteLine($"找到 {slots.Count} 个存档:"); + foreach (var slot in slots) + { + var saveData = await saveRepo.LoadAsync(slot); + Console.WriteLine($"槽位 {slot}: {saveData.Player.Name}, " + + $"等级 {saveData.Player.Level}, " + + $"保存时间 {saveData.SaveTime}"); + } +} +``` + +### 自动保存 + +```csharp +using GFramework.Core.Abstractions.Controller; +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +public partial class AutoSaveController : IController +{ + private CancellationTokenSource? _autoSaveCts; + + public void StartAutoSave(int slot, TimeSpan interval) + { + _autoSaveCts = new CancellationTokenSource(); + + Task.Run(async () => + { + while (!_autoSaveCts.Token.IsCancellationRequested) + { + await Task.Delay(interval, _autoSaveCts.Token); + + try + { + await SaveGame(slot); + Console.WriteLine("自动保存完成"); + } + catch (Exception ex) + { + Console.WriteLine($"自动保存失败: {ex.Message}"); + } + } + }, _autoSaveCts.Token); + } + + public void StopAutoSave() + { + _autoSaveCts?.Cancel(); + _autoSaveCts?.Dispose(); + _autoSaveCts = null; + } + + private async Task SaveGame(int slot) + { + var saveRepo = this.GetUtility>(); + var saveData = CreateSaveData(); + await saveRepo.SaveAsync(slot, saveData); + } + + private SaveData CreateSaveData() + { + // 从游戏状态创建存档数据 + return new SaveData(); + } +} +``` + ### 数据版本迁移 `SaveRepository` 当前负责槽位存档的读取、写入、删除和列举,并没有内建“注册迁移器后自动升级存档”的统一迁移管线。 @@ -270,334 +270,334 @@ public partial class AutoSaveController : IController 下面示例展示的是应用层迁移策略:加载后检查版本,调用你自己的迁移逻辑,再决定是否回写新版本数据。 ```csharp -// 版本 1 的数据 -public class SaveDataV1 : IVersionedData -{ - public int Version { get; set; } = 1; - public string PlayerName { get; set; } - public int Level { get; set; } -} - -// 版本 2 的数据(添加了新字段) -public class SaveDataV2 : IVersionedData -{ - public int Version { get; set; } = 2; - public string PlayerName { get; set; } - public int Level { get; set; } - public int Experience { get; set; } // 新增字段 - public DateTime LastPlayTime { get; set; } // 新增字段 -} - -// 数据迁移器 -public class SaveDataMigrator -{ - public SaveDataV2 Migrate(SaveDataV1 oldData) - { - return new SaveDataV2 - { - Version = 2, - PlayerName = oldData.PlayerName, - Level = oldData.Level, - Experience = oldData.Level * 100, // 根据等级计算经验 - LastPlayTime = DateTime.Now - }; - } -} - +// 版本 1 的数据 +public class SaveDataV1 : IVersionedData +{ + public int Version { get; set; } = 1; + public string PlayerName { get; set; } + public int Level { get; set; } +} + +// 版本 2 的数据(添加了新字段) +public class SaveDataV2 : IVersionedData +{ + public int Version { get; set; } = 2; + public string PlayerName { get; set; } + public int Level { get; set; } + public int Experience { get; set; } // 新增字段 + public DateTime LastPlayTime { get; set; } // 新增字段 +} + +// 数据迁移器 +public class SaveDataMigrator +{ + public SaveDataV2 Migrate(SaveDataV1 oldData) + { + return new SaveDataV2 + { + Version = 2, + PlayerName = oldData.PlayerName, + Level = oldData.Level, + Experience = oldData.Level * 100, // 根据等级计算经验 + LastPlayTime = DateTime.Now + }; + } +} + // 加载后由应用层决定是否迁移 public async Task LoadWithMigration(int slot) { var saveRepo = this.GetUtility>(); - var data = await saveRepo.LoadAsync(slot); - - if (data.Version < 2) - { + var data = await saveRepo.LoadAsync(slot); + + if (data.Version < 2) + { // 需要迁移:此处调用应用层迁移器 var oldData = data as SaveDataV1; var migrator = new SaveDataMigrator(); var newData = migrator.Migrate(oldData); - - // 保存迁移后的数据 - await saveRepo.SaveAsync(slot, newData); - return newData; - } - - return data; -} -``` - -### 使用数据仓库 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class SettingsController : IController -{ - public async Task SaveSettings() - { - var dataRepo = this.GetUtility(); - - var settings = new GameSettings - { - MasterVolume = 0.8f, - MusicVolume = 0.6f, - SfxVolume = 0.7f - }; - - // 定义数据位置 - var location = new DataLocation("settings", "game_settings.json"); - - // 保存设置 - await dataRepo.SaveAsync(location, settings); - } - - public async Task LoadSettings() - { - var dataRepo = this.GetUtility(); - var location = new DataLocation("settings", "game_settings.json"); - - // 检查是否存在 - if (!await dataRepo.ExistsAsync(location)) - { - return new GameSettings(); // 返回默认设置 - } - - // 加载设置 - return await dataRepo.LoadAsync(location); - } -} -``` - -### 批量保存数据 - -```csharp -public async Task SaveAllGameData() -{ - var dataRepo = this.GetUtility(); - - var dataList = new List<(IDataLocation, IData)> - { - (new DataLocation("player", "profile.json"), playerData), - (new DataLocation("inventory", "items.json"), inventoryData), - (new DataLocation("quests", "progress.json"), questData) - }; - - // 批量保存 - await dataRepo.SaveAllAsync(dataList); - Console.WriteLine("所有数据已保存"); -} -``` - -### 存档备份 - -```csharp -public async Task BackupSave(int slot) -{ - var saveRepo = this.GetUtility>(); - - if (!await saveRepo.ExistsAsync(slot)) - { - Console.WriteLine("存档不存在"); - return; - } - - // 加载原存档 - var saveData = await saveRepo.LoadAsync(slot); - - // 保存到备份槽位 - int backupSlot = slot + 100; - await saveRepo.SaveAsync(backupSlot, saveData); - - Console.WriteLine($"存档已备份到槽位 {backupSlot}"); -} - -public async Task RestoreBackup(int slot) -{ - int backupSlot = slot + 100; - var saveRepo = this.GetUtility>(); - - if (!await saveRepo.ExistsAsync(backupSlot)) - { - Console.WriteLine("备份不存在"); - return; - } - - // 加载备份 - var backupData = await saveRepo.LoadAsync(backupSlot); - - // 恢复到原槽位 - await saveRepo.SaveAsync(slot, backupData); - - Console.WriteLine($"已从备份恢复到槽位 {slot}"); -} -``` - -## 最佳实践 - -1. **使用版本化数据**:为存档数据实现 `IVersionedData` - ```csharp - ✓ public class SaveData : IVersionedData { public int Version { get; set; } = 1; } - ✗ public class SaveData : IData { } // 无法进行版本管理 - ``` - -2. **定期自动保存**:避免玩家数据丢失 - ```csharp - // 每 5 分钟自动保存 - StartAutoSave(currentSlot, TimeSpan.FromMinutes(5)); - ``` - -3. **保存前验证数据**:确保数据完整性 - ```csharp - public async Task SaveGame(int slot) - { - var saveData = CreateSaveData(); - - if (!ValidateSaveData(saveData)) - { - throw new InvalidOperationException("存档数据无效"); - } - - await saveRepo.SaveAsync(slot, saveData); - } - ``` - -4. **处理保存失败**:使用 try-catch 捕获异常 - ```csharp - try - { - await saveRepo.SaveAsync(slot, saveData); - } - catch (Exception ex) - { - Logger.Error($"保存失败: {ex.Message}"); - ShowErrorMessage("保存失败,请重试"); - } - ``` - -5. **提供多个存档槽位**:让玩家可以管理多个存档 - ```csharp - // 支持 10 个存档槽位 - for (int i = 1; i <= 10; i++) - { - if (await saveRepo.ExistsAsync(i)) - { - ShowSaveSlot(i); - } - } - ``` - -6. **在关键时刻保存**:场景切换、关卡完成等 - ```csharp - public async Task OnLevelComplete() - { - // 关卡完成时自动保存 - await SaveGame(currentSlot); - } - ``` - -## 常见问题 - -### 问题:如何实现多个存档槽位? - -**解答**: -使用 `ISaveRepository` 的槽位参数: - -```csharp -// 保存到不同槽位 -await saveRepo.SaveAsync(1, saveData); // 槽位 1 -await saveRepo.SaveAsync(2, saveData); // 槽位 2 -await saveRepo.SaveAsync(3, saveData); // 槽位 3 -``` - + + // 保存迁移后的数据 + await saveRepo.SaveAsync(slot, newData); + return newData; + } + + return data; +} +``` + +### 使用数据仓库 + +```csharp +using GFramework.Core.Abstractions.Controller; +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +public partial class SettingsController : IController +{ + public async Task SaveSettings() + { + var dataRepo = this.GetUtility(); + + var settings = new GameSettings + { + MasterVolume = 0.8f, + MusicVolume = 0.6f, + SfxVolume = 0.7f + }; + + // 定义数据位置 + var location = new DataLocation("settings", "game_settings.json"); + + // 保存设置 + await dataRepo.SaveAsync(location, settings); + } + + public async Task LoadSettings() + { + var dataRepo = this.GetUtility(); + var location = new DataLocation("settings", "game_settings.json"); + + // 检查是否存在 + if (!await dataRepo.ExistsAsync(location)) + { + return new GameSettings(); // 返回默认设置 + } + + // 加载设置 + return await dataRepo.LoadAsync(location); + } +} +``` + +### 批量保存数据 + +```csharp +public async Task SaveAllGameData() +{ + var dataRepo = this.GetUtility(); + + var dataList = new List<(IDataLocation, IData)> + { + (new DataLocation("player", "profile.json"), playerData), + (new DataLocation("inventory", "items.json"), inventoryData), + (new DataLocation("quests", "progress.json"), questData) + }; + + // 批量保存 + await dataRepo.SaveAllAsync(dataList); + Console.WriteLine("所有数据已保存"); +} +``` + +### 存档备份 + +```csharp +public async Task BackupSave(int slot) +{ + var saveRepo = this.GetUtility>(); + + if (!await saveRepo.ExistsAsync(slot)) + { + Console.WriteLine("存档不存在"); + return; + } + + // 加载原存档 + var saveData = await saveRepo.LoadAsync(slot); + + // 保存到备份槽位 + int backupSlot = slot + 100; + await saveRepo.SaveAsync(backupSlot, saveData); + + Console.WriteLine($"存档已备份到槽位 {backupSlot}"); +} + +public async Task RestoreBackup(int slot) +{ + int backupSlot = slot + 100; + var saveRepo = this.GetUtility>(); + + if (!await saveRepo.ExistsAsync(backupSlot)) + { + Console.WriteLine("备份不存在"); + return; + } + + // 加载备份 + var backupData = await saveRepo.LoadAsync(backupSlot); + + // 恢复到原槽位 + await saveRepo.SaveAsync(slot, backupData); + + Console.WriteLine($"已从备份恢复到槽位 {slot}"); +} +``` + +## 最佳实践 + +1. **使用版本化数据**:为存档数据实现 `IVersionedData` + ```csharp + ✓ public class SaveData : IVersionedData { public int Version { get; set; } = 1; } + ✗ public class SaveData : IData { } // 无法进行版本管理 + ``` + +2. **定期自动保存**:避免玩家数据丢失 + ```csharp + // 每 5 分钟自动保存 + StartAutoSave(currentSlot, TimeSpan.FromMinutes(5)); + ``` + +3. **保存前验证数据**:确保数据完整性 + ```csharp + public async Task SaveGame(int slot) + { + var saveData = CreateSaveData(); + + if (!ValidateSaveData(saveData)) + { + throw new InvalidOperationException("存档数据无效"); + } + + await saveRepo.SaveAsync(slot, saveData); + } + ``` + +4. **处理保存失败**:使用 try-catch 捕获异常 + ```csharp + try + { + await saveRepo.SaveAsync(slot, saveData); + } + catch (Exception ex) + { + Logger.Error($"保存失败: {ex.Message}"); + ShowErrorMessage("保存失败,请重试"); + } + ``` + +5. **提供多个存档槽位**:让玩家可以管理多个存档 + ```csharp + // 支持 10 个存档槽位 + for (int i = 1; i <= 10; i++) + { + if (await saveRepo.ExistsAsync(i)) + { + ShowSaveSlot(i); + } + } + ``` + +6. **在关键时刻保存**:场景切换、关卡完成等 + ```csharp + public async Task OnLevelComplete() + { + // 关卡完成时自动保存 + await SaveGame(currentSlot); + } + ``` + +## 常见问题 + +### 问题:如何实现多个存档槽位? + +**解答**: +使用 `ISaveRepository` 的槽位参数: + +```csharp +// 保存到不同槽位 +await saveRepo.SaveAsync(1, saveData); // 槽位 1 +await saveRepo.SaveAsync(2, saveData); // 槽位 2 +await saveRepo.SaveAsync(3, saveData); // 槽位 3 +``` + ### 问题:如何处理数据版本升级? **解答**: 实现 `IVersionedData` 并在加载后检查版本。当前框架不会自动为 `ISaveRepository` 执行迁移,需要由业务层决定迁移规则与回写时机: - -```csharp -var data = await saveRepo.LoadAsync(slot); -if (data.Version < CurrentVersion) -{ - data = MigrateData(data); - await saveRepo.SaveAsync(slot, data); -} -``` - -### 问题:存档数据保存在哪里? - -**解答**: -由存储系统决定,通常在: - -- Windows: `%AppData%/GameName/saves/` -- Linux: `~/.local/share/GameName/saves/` -- macOS: `~/Library/Application Support/GameName/saves/` - -### 问题:如何实现云存档? - -**解答**: -实现自定义的 `IStorage`,将数据保存到云端: - -```csharp -public class CloudStorage : IStorage -{ - public async Task WriteAsync(string path, byte[] data) - { - await UploadToCloud(path, data); - } - - public async Task ReadAsync(string path) - { - return await DownloadFromCloud(path); - } -} -``` - -### 问题:如何加密存档数据? - -**解答**: -在保存和加载时进行加密/解密: - -```csharp -public async Task SaveEncrypted(int slot, SaveData data) -{ - var json = JsonSerializer.Serialize(data); - var encrypted = Encrypt(json); - await storage.WriteAsync(path, encrypted); -} - -public async Task LoadEncrypted(int slot) -{ - var encrypted = await storage.ReadAsync(path); - var json = Decrypt(encrypted); - return JsonSerializer.Deserialize(json); -} -``` - -### 问题:存档损坏怎么办? - -**解答**: -实现备份和恢复机制: - -```csharp -public async Task SaveWithBackup(int slot, SaveData data) -{ - // 先备份旧存档 - if (await saveRepo.ExistsAsync(slot)) - { - var oldData = await saveRepo.LoadAsync(slot); - await saveRepo.SaveAsync(slot + 100, oldData); - } - - // 保存新存档 - await saveRepo.SaveAsync(slot, data); -} -``` - -## 相关文档 - -- [设置系统](/zh-CN/game/setting) - 游戏设置管理 -- [场景系统](/zh-CN/game/scene) - 场景切换时保存 -- [存档系统实现教程](/zh-CN/tutorials/save-system) - 完整示例 -- [Godot 集成](/zh-CN/godot/index) - Godot 中的数据管理 + +```csharp +var data = await saveRepo.LoadAsync(slot); +if (data.Version < CurrentVersion) +{ + data = MigrateData(data); + await saveRepo.SaveAsync(slot, data); +} +``` + +### 问题:存档数据保存在哪里? + +**解答**: +由存储系统决定,通常在: + +- Windows: `%AppData%/GameName/saves/` +- Linux: `~/.local/share/GameName/saves/` +- macOS: `~/Library/Application Support/GameName/saves/` + +### 问题:如何实现云存档? + +**解答**: +实现自定义的 `IStorage`,将数据保存到云端: + +```csharp +public class CloudStorage : IStorage +{ + public async Task WriteAsync(string path, byte[] data) + { + await UploadToCloud(path, data); + } + + public async Task ReadAsync(string path) + { + return await DownloadFromCloud(path); + } +} +``` + +### 问题:如何加密存档数据? + +**解答**: +在保存和加载时进行加密/解密: + +```csharp +public async Task SaveEncrypted(int slot, SaveData data) +{ + var json = JsonSerializer.Serialize(data); + var encrypted = Encrypt(json); + await storage.WriteAsync(path, encrypted); +} + +public async Task LoadEncrypted(int slot) +{ + var encrypted = await storage.ReadAsync(path); + var json = Decrypt(encrypted); + return JsonSerializer.Deserialize(json); +} +``` + +### 问题:存档损坏怎么办? + +**解答**: +实现备份和恢复机制: + +```csharp +public async Task SaveWithBackup(int slot, SaveData data) +{ + // 先备份旧存档 + if (await saveRepo.ExistsAsync(slot)) + { + var oldData = await saveRepo.LoadAsync(slot); + await saveRepo.SaveAsync(slot + 100, oldData); + } + + // 保存新存档 + await saveRepo.SaveAsync(slot, data); +} +``` + +## 相关文档 + +- [设置系统](/zh-CN/game/setting) - 游戏设置管理 +- [场景系统](/zh-CN/game/scene) - 场景切换时保存 +- [存档系统实现教程](/zh-CN/tutorials/save-system) - 完整示例 +- [Godot 集成](/zh-CN/godot/index) - Godot 中的数据管理