diff --git a/docs/zh-CN/game/data.md b/docs/zh-CN/game/data.md index 53a48738..c6c92dad 100644 --- a/docs/zh-CN/game/data.md +++ b/docs/zh-CN/game/data.md @@ -1,21 +1,21 @@ --- title: 数据与存档系统 -description: 数据与存档系统提供了完整的数据持久化解决方案,支持多槽位存档、版本管理和数据迁移。 +description: 数据与存档系统提供统一的数据持久化基础能力,支持多槽位存档、版本化数据和仓库抽象。 --- # 数据与存档系统 ## 概述 -数据与存档系统是 GFramework.Game 中用于管理游戏数据持久化的核心组件。它提供了统一的数据加载和保存接口,支持多槽位存档管理、数据版本控制和自动迁移,让你可以轻松实现游戏存档、设置保存等功能。 - -通过数据系统,你可以将游戏数据保存到本地存储,支持多个存档槽位,并在数据结构变化时自动进行版本迁移。 +数据与存档系统是 GFramework.Game 中用于管理游戏数据持久化的核心组件。它提供了统一的数据加载和保存接口,支持多槽位存档管理、版本化数据模式,以及与存储系统、序列化系统的组合使用。 + +通过数据系统,你可以将游戏数据保存到本地存储,支持多个存档槽位,并在需要时于应用层实现版本迁移。 **主要特性**: - 统一的数据持久化接口 - 多槽位存档管理 -- 数据版本控制和迁移 +- 数据版本控制模式 - 异步加载和保存 - 批量数据操作 - 与存储系统集成 @@ -69,10 +69,11 @@ public interface ISaveRepository : IUtility `IVersionedData` 支持数据版本管理: ```csharp -public interface IVersionedData : IData -{ - int Version { get; set; } -} +public interface IVersionedData : IData +{ + int Version { get; } + DateTime LastModified { get; } +} ``` ## 基本用法 @@ -262,9 +263,13 @@ public partial class AutoSaveController : IController } ``` -### 数据版本迁移 - -```csharp +### 数据版本迁移 + +`SaveRepository` 当前负责槽位存档的读取、写入、删除和列举,并没有内建“注册迁移器后自动升级存档”的统一迁移管线。 + +下面示例展示的是应用层迁移策略:加载后检查版本,调用你自己的迁移逻辑,再决定是否回写新版本数据。 + +```csharp // 版本 1 的数据 public class SaveDataV1 : IVersionedData { @@ -299,18 +304,18 @@ public class SaveDataMigrator } } -// 加载时自动迁移 -public async Task LoadWithMigration(int slot) -{ - var saveRepo = this.GetUtility>(); +// 加载后由应用层决定是否迁移 +public async Task LoadWithMigration(int slot) +{ + var saveRepo = this.GetUtility>(); var data = await saveRepo.LoadAsync(slot); if (data.Version < 2) { - // 需要迁移 - var oldData = data as SaveDataV1; - var migrator = new SaveDataMigrator(); - var newData = migrator.Migrate(oldData); + // 需要迁移:此处调用应用层迁移器 + var oldData = data as SaveDataV1; + var migrator = new SaveDataMigrator(); + var newData = migrator.Migrate(oldData); // 保存迁移后的数据 await saveRepo.SaveAsync(slot, newData); @@ -506,10 +511,10 @@ await saveRepo.SaveAsync(2, saveData); // 槽位 2 await saveRepo.SaveAsync(3, saveData); // 槽位 3 ``` -### 问题:如何处理数据版本升级? - -**解答**: -实现 `IVersionedData` 并在加载时检查版本: +### 问题:如何处理数据版本升级? + +**解答**: +实现 `IVersionedData` 并在加载后检查版本。当前框架不会自动为 `ISaveRepository` 执行迁移,需要由业务层决定迁移规则与回写时机: ```csharp var data = await saveRepo.LoadAsync(slot); diff --git a/docs/zh-CN/game/index.md b/docs/zh-CN/game/index.md index 2919e278..7beee74b 100644 --- a/docs/zh-CN/game/index.md +++ b/docs/zh-CN/game/index.md @@ -675,6 +675,7 @@ public class CachedStorage : IStorage ```csharp using GFramework.Game.Serializer; +using Newtonsoft.Json; public class GameDataSerializer { diff --git a/docs/zh-CN/game/setting.md b/docs/zh-CN/game/setting.md index 7b452aa2..93d2232b 100644 --- a/docs/zh-CN/game/setting.md +++ b/docs/zh-CN/game/setting.md @@ -1,192 +1,199 @@ -# 设置系统 (Settings System) +# 设置系统 -## 概述 +设置系统负责管理 `ISettingsData`、持久化加载/保存,以及把设置真正应用到运行时环境。 -设置系统是 GFramework.Game 的核心组件之一,负责管理游戏中各种设置配置。该系统采用了模型-系统分离的设计模式,支持设置部分(Section)的管理和设置应用器模式。 +当前实现以 `SettingsModel` 和 `SettingsSystem` 为核心,已经不是旧文档中的 +`Get() / Register(IApplyAbleSettings)` 接口模型。 -## 核心类 +## 核心概念 -### SettingsModel +### ISettingsData -设置模型类,继承自 `AbstractModel` 并实现 `ISettingsModel` 接口。 - -**主要功能:** - -- 管理不同类型的设置部分(Settings Section) -- 提供类型安全的设置访问 -- 支持可应用设置对象的注册 - -**关键方法:** - -- `Get()` - 获取或创建指定类型的设置部分 -- `TryGet(Type, out ISettingsSection)` - 尝试获取设置部分 -- `Register(IApplyAbleSettings)` - 注册可应用的设置对象 -- `All()` - 获取所有设置部分 - -### SettingsSystem - -设置系统类,继承自 `AbstractSystem` 并实现 `ISettingsSystem` 接口。 - -**主要功能:** - -- 应用设置配置到相应系统 -- 支持单个或批量设置应用 -- 自动识别可应用设置类型 - -**关键方法:** - -- `ApplyAll()` - 应用所有设置配置 -- `Apply()` - 应用指定类型的设置 -- `Apply(IEnumerable)` - 应用指定类型集合的设置 - -## 架构设计 - -```mermaid -graph TD - A[ISettingsModel] --> B[SettingsModel] - C[ISettingsSystem] --> D[SettingsSystem] - - B --> E[Dictionary] - D --> B - - F[ISettingsSection] --> G[IApplyAbleSettings] - H[AudioSettings] --> G - I[GraphicsSettings] --> G - - E --> H - E --> I - - J[Application] --> D - D --> G -``` - -## 使用示例 - -### 基本使用 +设置数据对象负责保存设置值、提供默认值,并在加载后把外部数据回填到当前实例。 ```csharp -// 获取设置模型 -var settingsModel = this.GetModel(); - -// 获取或创建音频设置 -var audioSettings = settingsModel.Get(); -audioSettings.MasterVolume = 0.8f; -audioSettings.BgmVolume = 0.6f; -audioSettings.SfxVolume = 0.9f; - -// 注册设置到模型 -settingsModel.Register(audioSettings); +public interface ISettingsData : IResettable, IVersionedData, ILoadableFrom; ``` -### 应用设置 +这意味着一个设置数据类型通常需要实现: + +- `Reset()`:恢复默认值 +- `Version` / `LastModified`:暴露版本化信息 +- `LoadFrom(ISettingsData)`:把已加载或迁移后的数据复制到当前实例 + +### IResetApplyAbleSettings + +应用器负责把设置数据作用到引擎或运行时环境: ```csharp -// 获取设置系统 -var settingsSystem = this.GetSystem(); - -// 应用所有设置 -await settingsSystem.ApplyAll(); - -// 应用特定类型设置 -await settingsSystem.Apply(); - -// 应用多个类型设置 -var types = new[] { typeof(GodotAudioSettings), typeof(GodotGraphicsSettings) }; -await settingsSystem.Apply(types); -``` - -### 创建自定义设置 - -```csharp -public class GameSettings : ISettingsSection +public interface IResetApplyAbleSettings : IResettable, IApplyAbleSettings { - public float GameSpeed { get; set; } = 1.0f; - public int Difficulty { get; set; } = 1; - public bool AutoSave { get; set; } = true; + ISettingsData Data { get; } + Type DataType { get; } } - -// 使用自定义设置 -var gameSettings = settingsModel.Get(); -gameSettings.GameSpeed = 1.5f; ``` -### 创建可应用设置 +常见用途包括: + +- 把音量设置同步到音频总线 +- 把图形设置同步到窗口系统 +- 把语言设置同步到本地化管理器 + +## ISettingsModel + +当前 `ISettingsModel` 的主要 API 如下: ```csharp -public class GameSettings : ISettingsSection, IApplyAbleSettings +public interface ISettingsModel : IModel +{ + bool IsInitialized { get; } + + T GetData() where T : class, ISettingsData, new(); + IEnumerable AllData(); + + ISettingsModel RegisterApplicator(T applicator) + where T : class, IResetApplyAbleSettings; + T? GetApplicator() where T : class, IResetApplyAbleSettings; + IEnumerable AllApplicators(); + + ISettingsModel RegisterMigration(ISettingsMigration migration); + + Task InitializeAsync(); + Task SaveAllAsync(); + Task ApplyAllAsync(); + void Reset() where T : class, ISettingsData, new(); + void ResetAll(); +} +``` + +行为说明: + +- `GetData()` 返回某个设置数据的唯一实例 +- `RegisterApplicator()` 注册应用器,并把其 `Data` 纳入模型管理 +- `InitializeAsync()` 从 `ISettingsDataRepository` 读取所有已注册设置,并在需要时执行迁移 +- `SaveAllAsync()` 持久化当前所有设置数据 +- `ApplyAllAsync()` 依次调用所有 applicator 的 `Apply()` + +## SettingsSystem + +`SettingsSystem` 是对模型的系统级封装,面向业务代码提供更直接的入口: + +```csharp +public interface ISettingsSystem : ISystem +{ + Task ApplyAll(); + Task Apply() where T : class, IResetApplyAbleSettings; + Task SaveAll(); + Task Reset() where T : class, ISettingsData, IResetApplyAbleSettings, new(); + Task ResetAll(); +} +``` + +它不会自己保存数据,而是把保存、重置和应用逻辑委托给 `ISettingsModel`。 + +## 基本用法 + +### 定义设置数据 + +```csharp +public sealed class GameplaySettings : ISettingsData { public float GameSpeed { get; set; } = 1.0f; - public int Difficulty { get; set; } = 1; - + + public int Version { get; private set; } = 1; + public DateTime LastModified { get; } = DateTime.UtcNow; + + public void Reset() + { + GameSpeed = 1.0f; + } + + public void LoadFrom(ISettingsData source) + { + if (source is not GameplaySettings settings) + { + return; + } + + GameSpeed = settings.GameSpeed; + Version = settings.Version; + } +} +``` + +### 定义 applicator + +```csharp +public sealed class GameplaySettingsApplicator : IResetApplyAbleSettings +{ + public GameplaySettingsApplicator(GameplaySettings data) + { + Data = data; + } + + public ISettingsData Data { get; } + public Type DataType => typeof(GameplaySettings); + + public void Reset() + { + Data.Reset(); + } + public Task Apply() { - // 应用游戏速度 - Time.timeScale = GameSpeed; - - // 应用难度设置 - GameDifficulty.Current = Difficulty; - + var settings = (GameplaySettings)Data; + TimeScale.Current = settings.GameSpeed; return Task.CompletedTask; } } ``` -## 接口定义 - -### ISettingsSection +### 使用模型和系统 ```csharp -public interface ISettingsSection +var settingsModel = this.GetModel(); + +var gameplayData = settingsModel.GetData(); +gameplayData.GameSpeed = 1.25f; + +settingsModel.RegisterApplicator(new GameplaySettingsApplicator(gameplayData)); + +await settingsModel.InitializeAsync(); +await settingsModel.SaveAllAsync(); + +var settingsSystem = this.GetSystem(); +await settingsSystem.ApplyAll(); +``` + +## 迁移 + +设置系统内建了迁移注册入口: + +```csharp +public interface ISettingsMigration { - // 设置部分的标识接口 + Type SettingsType { get; } + int FromVersion { get; } + int ToVersion { get; } + ISettingsSection Migrate(ISettingsSection oldData); } ``` -### IApplyAbleSettings +当 `InitializeAsync()` 读取到旧版本设置时,会按已注册迁移链逐步升级,再通过 `LoadFrom` 回填到当前实例。 -```csharp -public interface IApplyAbleSettings : ISettingsSection -{ - Task Apply(); -} -``` +## 依赖项 -### ISettingsModel +要让设置系统完整工作,通常需要准备: -```csharp -public interface ISettingsModel -{ - T Get() where T : class, ISettingsSection, new(); - bool TryGet(Type type, out ISettingsSection section); - IEnumerable All(); - void Register(IApplyAbleSettings applyAble); -} -``` +- `ISettingsDataRepository` +- `IDataLocationProvider` +- 一个具体的存储实现和序列化器 -### ISettingsSystem +如果使用 `UnifiedSettingsDataRepository`,多个设置节会被合并到单个设置文件中统一保存。 -```csharp -public interface ISettingsSystem -{ - Task ApplyAll(); - Task Apply() where T : class, ISettingsSection; - Task Apply(Type settingsType); - Task Apply(IEnumerable settingsTypes); -} -``` +## 当前边界 -## 设计模式 - -该系统使用了以下设计模式: - -1. **Repository Pattern** - SettingsModel 作为设置数据的仓库 -2. **Command Pattern** - IApplyAbleSettings 的 Apply 方法作为命令 -3. **Factory Pattern** - Get``() 方法创建设置实例 -4. **Template Method** - AbstractSystem 提供初始化模板 - -## 最佳实践 - -1. **设置分类** - 将相关设置组织到同一个设置类中 -2. **延迟应用** - 批量修改后再应用,而不是每次修改都应用 -3. **类型安全** - 使用泛型方法确保类型安全 -4. **可测试性** - 通过接口实现便于单元测试 \ No newline at end of file +- 设置迁移是内建能力 +- 设置持久化是内建能力 +- 设置如何应用到具体引擎由 applicator 决定 +- 存档系统的迁移能力不等同于设置系统;`ISaveRepository` 当前仍需要业务层自己实现迁移策略