--- title: 实现存档系统 description: 学习如何实现完整的游戏存档系统,支持多槽位和自动保存 --- # 实现存档系统 ## 学习目标 完成本教程后,你将能够: - 理解游戏存档系统的设计原则 - 定义存档数据结构 - 实现多槽位存档管理 - 实现自动保存功能 - 处理存档加载和保存错误 - 实现存档列表和删除功能 ## 前置条件 - 已安装 GFramework.Game NuGet 包 - 了解 C# 基础语法和 async/await - 阅读过[快速开始](/zh-CN/getting-started/quick-start) - 了解[数据与存档系统](/zh-CN/game/data) ## 步骤 1:定义存档数据结构 首先,让我们定义游戏存档需要保存的数据结构。 ```csharp using GFramework.Game.Abstractions.data; using System; using System.Collections.Generic; namespace MyGame.Data { /// /// 玩家数据 /// public class PlayerData { public string Name { get; set; } = "Player"; public int Level { get; set; } = 1; public int Experience { get; set; } = 0; public int Health { get; set; } = 100; public int MaxHealth { get; set; } = 100; public int Gold { get; set; } = 0; public Vector3 Position { get; set; } = new(); } /// /// 位置数据 /// public class Vector3 { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } } /// /// 关卡进度数据 /// public class ProgressData { public int CurrentLevel { get; set; } = 1; public List CompletedLevels { get; set; } = new(); public Dictionary Achievements { get; set; } = new(); public float PlayTime { get; set; } = 0f; } /// /// 物品数据 /// public class InventoryData { public List Items { get; set; } = new(); public List EquippedItems { get; set; } = new(); } /// /// 单个物品数据 /// public class ItemData { public string Id { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public int Quantity { get; set; } = 1; } /// /// 完整的存档数据 /// public class GameSaveData : IVersionedData { // 数据版本号 public int Version { get; set; } = 1; // 存档元数据 public DateTime SaveTime { get; set; } public string SaveName { get; set; } = "New Save"; public float TotalPlayTime { get; set; } // 游戏数据 public PlayerData Player { get; set; } = new(); public ProgressData Progress { get; set; } = new(); public InventoryData Inventory { get; set; } = new(); } } ``` **代码说明**: - `GameSaveData` 实现 `IVersionedData` 支持版本管理 - 将数据分为玩家、进度、物品等模块 - 包含存档元数据(保存时间、名称等) - 使用属性初始化器设置默认值 ## 步骤 2:创建存档管理系统 实现一个系统来管理存档的创建、加载和保存。 ```csharp using GFramework.Core.system; using GFramework.Core.Abstractions.resource; using GFramework.Core.extensions; using GFramework.Game.Abstractions.data; using MyGame.Data; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace MyGame.Systems { /// /// 存档管理系统 /// public class SaveSystem : AbstractSystem { private GameSaveData? _currentSave; private int _currentSlot = -1; /// /// 创建新存档 /// public GameSaveData CreateNewSave(string saveName) { Console.WriteLine($"创建新存档: {saveName}"); var saveData = new GameSaveData { SaveName = saveName, SaveTime = DateTime.Now, TotalPlayTime = 0f, Player = new PlayerData { Name = "Player", Level = 1, Experience = 0, Health = 100, MaxHealth = 100, Gold = 0 }, Progress = new ProgressData { CurrentLevel = 1, CompletedLevels = new List(), Achievements = new Dictionary(), PlayTime = 0f }, Inventory = new InventoryData { Items = new List(), EquippedItems = new List() } }; _currentSave = saveData; return saveData; } /// /// 保存游戏 /// public async Task SaveGameAsync(int slot) { if (_currentSave == null) { Console.WriteLine("错误: 没有当前存档数据"); return false; } try { Console.WriteLine($"\n=== 保存游戏到槽位 {slot} ==="); var saveRepo = this.GetUtility>(); // 更新保存时间 _currentSave.SaveTime = DateTime.Now; _currentSlot = slot; // 保存到存储 await saveRepo.SaveAsync(slot, _currentSave); Console.WriteLine($"存档已保存: {_currentSave.SaveName}"); Console.WriteLine($"玩家: {_currentSave.Player.Name}, 等级 {_currentSave.Player.Level}"); Console.WriteLine($"游戏时间: {_currentSave.TotalPlayTime:F1} 小时"); Console.WriteLine($"保存时间: {_currentSave.SaveTime}\n"); return true; } catch (Exception ex) { Console.WriteLine($"保存失败: {ex.Message}"); return false; } } /// /// 加载游戏 /// public async Task LoadGameAsync(int slot) { try { Console.WriteLine($"\n=== 从槽位 {slot} 加载游戏 ==="); var saveRepo = this.GetUtility>(); // 检查存档是否存在 if (!await saveRepo.ExistsAsync(slot)) { Console.WriteLine($"槽位 {slot} 不存在存档\n"); return false; } // 加载存档 _currentSave = await saveRepo.LoadAsync(slot); _currentSlot = slot; Console.WriteLine($"存档已加载: {_currentSave.SaveName}"); Console.WriteLine($"玩家: {_currentSave.Player.Name}, 等级 {_currentSave.Player.Level}"); Console.WriteLine($"当前关卡: {_currentSave.Progress.CurrentLevel}"); Console.WriteLine($"金币: {_currentSave.Player.Gold}"); Console.WriteLine($"物品数量: {_currentSave.Inventory.Items.Count}"); Console.WriteLine($"保存时间: {_currentSave.SaveTime}\n"); return true; } catch (Exception ex) { Console.WriteLine($"加载失败: {ex.Message}\n"); return false; } } /// /// 删除存档 /// public async Task DeleteSaveAsync(int slot) { try { Console.WriteLine($"\n删除槽位 {slot} 的存档"); var saveRepo = this.GetUtility>(); if (!await saveRepo.ExistsAsync(slot)) { Console.WriteLine($"槽位 {slot} 不存在存档\n"); return false; } await saveRepo.DeleteAsync(slot); if (_currentSlot == slot) { _currentSave = null; _currentSlot = -1; } Console.WriteLine($"槽位 {slot} 的存档已删除\n"); return true; } catch (Exception ex) { Console.WriteLine($"删除失败: {ex.Message}\n"); return false; } } /// /// 列出所有存档 /// public async Task> ListSavesAsync() { var result = new List(); try { var saveRepo = this.GetUtility>(); var slots = await saveRepo.ListSlotsAsync(); Console.WriteLine($"\n=== 存档列表 ({slots.Count} 个) ==="); foreach (var slot in slots) { var saveData = await saveRepo.LoadAsync(slot); var info = new SaveSlotInfo { Slot = slot, SaveName = saveData.SaveName, PlayerName = saveData.Player.Name, Level = saveData.Player.Level, SaveTime = saveData.SaveTime, PlayTime = saveData.TotalPlayTime }; result.Add(info); Console.WriteLine($"槽位 {slot}: {info.SaveName}"); Console.WriteLine($" 玩家: {info.PlayerName}, 等级 {info.Level}"); Console.WriteLine($" 保存时间: {info.SaveTime}"); Console.WriteLine($" 游戏时间: {info.PlayTime:F1} 小时"); } Console.WriteLine(); } catch (Exception ex) { Console.WriteLine($"列出存档失败: {ex.Message}\n"); } return result; } /// /// 获取当前存档 /// public GameSaveData? GetCurrentSave() => _currentSave; /// /// 获取当前槽位 /// public int GetCurrentSlot() => _currentSlot; } /// /// 存档槽位信息 /// public class SaveSlotInfo { public int Slot { get; set; } public string SaveName { get; set; } = string.Empty; public string PlayerName { get; set; } = string.Empty; public int Level { get; set; } public DateTime SaveTime { get; set; } public float PlayTime { get; set; } } } ``` **代码说明**: - `CreateNewSave` 创建新的存档数据 - `SaveGameAsync` 保存当前存档到指定槽位 - `LoadGameAsync` 从槽位加载存档 - `DeleteSaveAsync` 删除指定槽位的存档 - `ListSavesAsync` 列出所有存档信息 - 使用 try-catch 处理异常 ## 步骤 3:实现自动保存功能 创建自动保存系统,定期保存游戏进度。 ```csharp using GFramework.Core.system; using GFramework.Core.extensions; using System; using System.Threading; using System.Threading.Tasks; namespace MyGame.Systems { /// /// 自动保存系统 /// public class AutoSaveSystem : AbstractSystem { private CancellationTokenSource? _autoSaveCts; private bool _isAutoSaveEnabled; private TimeSpan _autoSaveInterval = TimeSpan.FromMinutes(5); /// /// 启动自动保存 /// public void StartAutoSave(int slot, TimeSpan? interval = null) { if (_isAutoSaveEnabled) { Console.WriteLine("自动保存已在运行"); return; } if (interval.HasValue) { _autoSaveInterval = interval.Value; } _autoSaveCts = new CancellationTokenSource(); _isAutoSaveEnabled = true; Console.WriteLine($"\n启动自动保存 (间隔: {_autoSaveInterval.TotalSeconds} 秒)"); // 在后台线程运行自动保存 Task.Run(async () => { while (!_autoSaveCts.Token.IsCancellationRequested) { try { // 等待指定间隔 await Task.Delay(_autoSaveInterval, _autoSaveCts.Token); // 执行自动保存 await PerformAutoSaveAsync(slot); } catch (OperationCanceledException) { // 正常取消 break; } catch (Exception ex) { Console.WriteLine($"自动保存错误: {ex.Message}"); } } }, _autoSaveCts.Token); } /// /// 停止自动保存 /// public void StopAutoSave() { if (!_isAutoSaveEnabled) { return; } Console.WriteLine("停止自动保存"); _autoSaveCts?.Cancel(); _autoSaveCts?.Dispose(); _autoSaveCts = null; _isAutoSaveEnabled = false; } /// /// 执行自动保存 /// private async Task PerformAutoSaveAsync(int slot) { Console.WriteLine($"\n[自动保存] 保存到槽位 {slot}..."); var saveSystem = this.GetSystem(); var success = await saveSystem.SaveGameAsync(slot); if (success) { Console.WriteLine("[自动保存] 完成"); } else { Console.WriteLine("[自动保存] 失败"); } } /// /// 检查自动保存是否启用 /// public bool IsAutoSaveEnabled() => _isAutoSaveEnabled; /// /// 设置自动保存间隔 /// public void SetAutoSaveInterval(TimeSpan interval) { _autoSaveInterval = interval; Console.WriteLine($"自动保存间隔已设置为 {interval.TotalSeconds} 秒"); } } } ``` **代码说明**: - 使用 `CancellationTokenSource` 控制后台任务 - `StartAutoSave` 启动定时保存任务 - `StopAutoSave` 停止自动保存 - 使用 `Task.Delay` 实现定时触发 - 捕获并处理异常,避免自动保存失败影响游戏 ## 步骤 4:实现游戏数据更新 创建一个系统来模拟游戏数据的变化。 ```csharp using GFramework.Core.system; using GFramework.Core.extensions; using MyGame.Data; using System; namespace MyGame.Systems { /// /// 游戏逻辑系统(模拟) /// public class GameLogicSystem : AbstractSystem { private Random _random = new(); /// /// 模拟玩家升级 /// public void LevelUp() { var saveSystem = this.GetSystem(); var save = saveSystem.GetCurrentSave(); if (save == null) { Console.WriteLine("没有当前存档"); return; } save.Player.Level++; save.Player.Experience = 0; save.Player.MaxHealth += 10; save.Player.Health = save.Player.MaxHealth; Console.WriteLine($"\n玩家升级! 当前等级: {save.Player.Level}"); } /// /// 模拟获得金币 /// public void AddGold(int amount) { var saveSystem = this.GetSystem(); var save = saveSystem.GetCurrentSave(); if (save == null) return; save.Player.Gold += amount; Console.WriteLine($"\n获得金币 +{amount}, 当前: {save.Player.Gold}"); } /// /// 模拟完成关卡 /// public void CompleteLevel(int level) { var saveSystem = this.GetSystem(); var save = saveSystem.GetCurrentSave(); if (save == null) return; if (!save.Progress.CompletedLevels.Contains(level)) { save.Progress.CompletedLevels.Add(level); Console.WriteLine($"\n完成关卡 {level}!"); } save.Progress.CurrentLevel = level + 1; } /// /// 模拟获得物品 /// public void AddItem(string itemId, string itemName, int quantity = 1) { var saveSystem = this.GetSystem(); var save = saveSystem.GetCurrentSave(); if (save == null) return; // 查找已有物品 var existingItem = save.Inventory.Items.Find(i => i.Id == itemId); if (existingItem != null) { existingItem.Quantity += quantity; } else { save.Inventory.Items.Add(new ItemData { Id = itemId, Name = itemName, Quantity = quantity }); } Console.WriteLine($"\n获得物品: {itemName} x{quantity}"); } /// /// 模拟游戏进行 /// public void SimulateGameplay() { Console.WriteLine("\n=== 模拟游戏进行 ==="); // 随机事件 int eventType = _random.Next(0, 4); switch (eventType) { case 0: AddGold(_random.Next(10, 100)); break; case 1: LevelUp(); break; case 2: CompleteLevel(_random.Next(1, 10)); break; case 3: AddItem($"item_{_random.Next(1, 5)}", $"物品 {_random.Next(1, 5)}", 1); break; } // 增加游戏时间 var saveSystem = this.GetSystem(); var save = saveSystem.GetCurrentSave(); if (save != null) { save.TotalPlayTime += 0.1f; save.Progress.PlayTime += 0.1f; } } } } ``` **代码说明**: - 提供各种游戏事件的模拟方法 - 直接修改当前存档数据 - 用于测试存档系统功能 ## 步骤 5:注册系统并测试 在架构中注册所有系统并进行测试。 ```csharp using GFramework.Core.architecture; using GFramework.Game.Abstractions.data; using GFramework.Game.Abstractions.storage; using GFramework.Game.data; using GFramework.Game.storage; using MyGame.Data; using MyGame.Systems; namespace MyGame { public class GameArchitecture : Architecture { public static IArchitecture Interface { get; private set; } protected override void Init() { Interface = this; // 注册存储系统 var storage = new FileStorage("./game_data"); RegisterUtility(storage); // 注册存档仓库 var saveConfig = new SaveConfiguration { SaveRoot = "saves", SaveSlotPrefix = "slot_", SaveFileName = "save.json" }; var saveRepo = new SaveRepository(storage, saveConfig); RegisterUtility>(saveRepo); // 注册系统 RegisterSystem(new SaveSystem()); RegisterSystem(new AutoSaveSystem()); RegisterSystem(new GameLogicSystem()); Console.WriteLine("游戏架构初始化完成"); } } } ``` ### 测试代码 ```csharp using MyGame; using MyGame.Systems; using System; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { Console.WriteLine("=== 存档系统测试 ===\n"); // 1. 初始化架构 var architecture = new GameArchitecture(); architecture.Initialize(); await architecture.WaitUntilReadyAsync(); // 2. 获取系统 var saveSystem = architecture.GetSystem(); var autoSaveSystem = architecture.GetSystem(); var gameLogic = architecture.GetSystem(); // 3. 创建新存档 Console.WriteLine("--- 测试 1: 创建新存档 ---"); saveSystem.CreateNewSave("我的冒险"); await saveSystem.SaveGameAsync(1); await Task.Delay(1000); // 4. 模拟游戏进行 Console.WriteLine("--- 测试 2: 游戏进行 ---"); for (int i = 0; i < 5; i++) { gameLogic.SimulateGameplay(); await Task.Delay(500); } // 5. 手动保存 Console.WriteLine("\n--- 测试 3: 手动保存 ---"); await saveSystem.SaveGameAsync(1); await Task.Delay(1000); // 6. 创建第二个存档 Console.WriteLine("--- 测试 4: 创建第二个存档 ---"); saveSystem.CreateNewSave("新的旅程"); gameLogic.AddGold(500); gameLogic.LevelUp(); await saveSystem.SaveGameAsync(2); await Task.Delay(1000); // 7. 列出所有存档 Console.WriteLine("--- 测试 5: 列出所有存档 ---"); await saveSystem.ListSavesAsync(); await Task.Delay(1000); // 8. 加载第一个存档 Console.WriteLine("--- 测试 6: 加载存档 ---"); await saveSystem.LoadGameAsync(1); await Task.Delay(1000); // 9. 启动自动保存 Console.WriteLine("--- 测试 7: 启动自动保存 ---"); autoSaveSystem.StartAutoSave(1, TimeSpan.FromSeconds(3)); // 模拟游戏进行 for (int i = 0; i < 10; i++) { gameLogic.SimulateGameplay(); await Task.Delay(1000); } // 10. 停止自动保存 autoSaveSystem.StopAutoSave(); await Task.Delay(1000); // 11. 删除存档 Console.WriteLine("--- 测试 8: 删除存档 ---"); await saveSystem.DeleteSaveAsync(2); await Task.Delay(1000); // 12. 最终存档列表 Console.WriteLine("--- 测试 9: 最终存档列表 ---"); await saveSystem.ListSavesAsync(); Console.WriteLine("=== 测试完成 ==="); } } ``` **代码说明**: - 注册存储系统和存档仓库 - 注册所有游戏系统 - 测试存档的创建、保存、加载、删除 - 测试自动保存功能 - 测试多槽位管理 ## 完整代码 所有代码文件已在上述步骤中提供。项目结构如下: ``` MyGame/ ├── Data/ │ ├── PlayerData.cs │ ├── ProgressData.cs │ ├── InventoryData.cs │ └── GameSaveData.cs ├── Systems/ │ ├── SaveSystem.cs │ ├── AutoSaveSystem.cs │ └── GameLogicSystem.cs ├── GameArchitecture.cs └── Program.cs ``` ## 运行结果 运行程序后,你将看到类似以下的输出: ``` === 存档系统测试 === 游戏架构初始化完成 --- 测试 1: 创建新存档 --- 创建新存档: 我的冒险 === 保存游戏到槽位 1 === 存档已保存: 我的冒险 玩家: Player, 等级 1 游戏时间: 0.0 小时 保存时间: 2026-03-07 10:30:00 --- 测试 2: 游戏进行 --- === 模拟游戏进行 === 获得金币 +45, 当前: 45 === 模拟游戏进行 === 玩家升级! 当前等级: 2 === 模拟游戏进行 === 完成关卡 3! === 模拟游戏进行 === 获得物品: 物品 2 x1 === 模拟游戏进行 === 获得金币 +78, 当前: 123 --- 测试 3: 手动保存 --- === 保存游戏到槽位 1 === 存档已保存: 我的冒险 玩家: Player, 等级 2 游戏时间: 0.5 小时 保存时间: 2026-03-07 10:30:05 --- 测试 4: 创建第二个存档 --- 创建新存档: 新的旅程 获得金币 +500, 当前: 500 玩家升级! 当前等级: 2 === 保存游戏到槽位 2 === 存档已保存: 新的旅程 玩家: Player, 等级 2 游戏时间: 0.0 小时 保存时间: 2026-03-07 10:30:06 --- 测试 5: 列出所有存档 --- === 存档列表 (2 个) === 槽位 1: 我的冒险 玩家: Player, 等级 2 保存时间: 2026-03-07 10:30:05 游戏时间: 0.5 小时 槽位 2: 新的旅程 玩家: Player, 等级 2 保存时间: 2026-03-07 10:30:06 游戏时间: 0.0 小时 --- 测试 6: 加载存档 --- === 从槽位 1 加载游戏 === 存档已加载: 我的冒险 玩家: Player, 等级 2 当前关卡: 4 金币: 123 物品数量: 1 保存时间: 2026-03-07 10:30:05 --- 测试 7: 启动自动保存 --- 启动自动保存 (间隔: 3 秒) === 模拟游戏进行 === ... [自动保存] 保存到槽位 1... === 保存游戏到槽位 1 === 存档已保存: 我的冒险 ... [自动保存] 完成 停止自动保存 --- 测试 8: 删除存档 --- 删除槽位 2 的存档 槽位 2 的存档已删除 --- 测试 9: 最终存档列表 --- === 存档列表 (1 个) === 槽位 1: 我的冒险 玩家: Player, 等级 3 保存时间: 2026-03-07 10:30:15 游戏时间: 1.5 小时 === 测试完成 === ``` **验证步骤**: 1. 存档创建和保存成功 2. 游戏数据正确更新 3. 多槽位管理正常 4. 存档加载恢复数据 5. 自动保存定时触发 6. 存档删除功能正常 7. 存档列表显示正确 ## 下一步 恭喜!你已经实现了一个完整的存档系统。接下来可以学习: - [Godot 完整项目搭建](/zh-CN/tutorials/godot-complete-project) - 在 Godot 中使用存档系统 - [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 异步加载存档 - [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明 ## 相关文档 - [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明 - [对象池系统](/zh-CN/core/pool) - 结合对象池复用资源 - [协程系统](/zh-CN/core/coroutine) - 异步加载资源 - [System 层](/zh-CN/core/system) - System 详细说明