From 442e8e7088798b856f87b3a163e60b623aaf8790 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:44:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(setting):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=B3=BB=E7=BB=9F=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=92=8C=E5=BA=94=E7=94=A8=E5=99=A8=E5=88=86?= =?UTF-8?q?=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将SettingsModel内部存储分离为_dataSettings和_applicators两个字典 - 添加IDataSettings接口用于标识纯数据设置 - 修改Get方法为GetData以明确区分数据获取 - 添加RegisterApplicator和GetApplicator方法管理可应用设置 - 更新TryGet方法支持从数据和应用器中查找设置 - 扩展SettingsPersistence支持批量保存和加载所有设置数据 - 将AudioBusMap重命名为AudioBusMapSettings并实现ISettingsData接口 - 修改Godot音频和图形设置适配新的接口变更 - [skip ci] --- .../setting/AudioSettings.cs | 2 +- .../setting/GraphicsSettings.cs | 2 +- .../setting/ISettingsData.cs | 6 ++ .../setting/ISettingsModel.cs | 22 +++-- .../setting/ISettingsPersistence.cs | 39 ++++---- GFramework.Game/setting/SettingsModel.cs | 98 +++++++++++++------ .../setting/SettingsPersistence.cs | 90 +++++++++++++---- GFramework.Godot/setting/AudioBusMap.cs | 22 ----- .../setting/AudioBusMapSettings.cs | 28 ++++++ .../setting/GodotAudioSettings.cs | 13 +-- .../setting/GodotGraphicsSettings.cs | 30 +++--- 11 files changed, 229 insertions(+), 123 deletions(-) create mode 100644 GFramework.Game.Abstractions/setting/ISettingsData.cs delete mode 100644 GFramework.Godot/setting/AudioBusMap.cs create mode 100644 GFramework.Godot/setting/AudioBusMapSettings.cs diff --git a/GFramework.Game.Abstractions/setting/AudioSettings.cs b/GFramework.Game.Abstractions/setting/AudioSettings.cs index 280b424..a0e2e2d 100644 --- a/GFramework.Game.Abstractions/setting/AudioSettings.cs +++ b/GFramework.Game.Abstractions/setting/AudioSettings.cs @@ -3,7 +3,7 @@ /// /// 音频设置类,用于管理游戏中的音频配置 /// -public class AudioSettings : ISettingsSection +public class AudioSettings : ISettingsData { /// /// 获取或设置主音量,控制所有音频的总体音量 diff --git a/GFramework.Game.Abstractions/setting/GraphicsSettings.cs b/GFramework.Game.Abstractions/setting/GraphicsSettings.cs index 4f85685..e27cab8 100644 --- a/GFramework.Game.Abstractions/setting/GraphicsSettings.cs +++ b/GFramework.Game.Abstractions/setting/GraphicsSettings.cs @@ -3,7 +3,7 @@ namespace GFramework.Game.Abstractions.setting; /// /// 图形设置类,用于管理游戏的图形相关配置 /// -public class GraphicsSettings : ISettingsSection +public class GraphicsSettings : ISettingsData { /// /// 获取或设置是否启用全屏模式 diff --git a/GFramework.Game.Abstractions/setting/ISettingsData.cs b/GFramework.Game.Abstractions/setting/ISettingsData.cs new file mode 100644 index 0000000..4894bdd --- /dev/null +++ b/GFramework.Game.Abstractions/setting/ISettingsData.cs @@ -0,0 +1,6 @@ +namespace GFramework.Game.Abstractions.setting; + +/// +/// 设置数据接口 - 纯数据,可自动创建 +/// +public interface ISettingsData : ISettingsSection; \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/ISettingsModel.cs b/GFramework.Game.Abstractions/setting/ISettingsModel.cs index 30895f3..0b8256a 100644 --- a/GFramework.Game.Abstractions/setting/ISettingsModel.cs +++ b/GFramework.Game.Abstractions/setting/ISettingsModel.cs @@ -10,11 +10,11 @@ namespace GFramework.Game.Abstractions.setting; public interface ISettingsModel : IModel { /// - /// 获取指定类型的设置节实例 + /// 获取或创建数据设置(自动创建) /// - /// 设置节的类型,必须是class、实现ISettingsSection接口且具有无参构造函数 - /// 指定类型的设置节实例 - T Get() where T : class, ISettingsSection, new(); + /// 设置数据的类型,必须继承自class、ISettingsData且具有无参构造函数 + /// 指定类型的设置数据实例 + T GetData() where T : class, ISettingsData, new(); /// /// 尝试获取指定类型的设置节实例 @@ -24,6 +24,13 @@ public interface ISettingsModel : IModel /// 如果找到指定类型的设置节则返回true,否则返回false bool TryGet(Type type, out ISettingsSection section); + /// + /// 获取已注册的可应用设置 + /// + /// 可应用设置的类型,必须继承自class和IApplyAbleSettings + /// 指定类型的可应用设置实例,如果不存在则返回null + T? GetApplicator() where T : class, IApplyAbleSettings; + /// /// 获取所有设置节的集合 /// @@ -31,8 +38,9 @@ public interface ISettingsModel : IModel IEnumerable All(); /// - /// 注册一个可应用的设置对象 + /// 注册可应用设置(必须手动注册) /// - /// 要注册的可应用设置对象 - void Register(IApplyAbleSettings applyAble); + /// 可应用设置的类型,必须继承自class和IApplyAbleSettings + /// 要注册的可应用设置实例 + void RegisterApplicator(T applicator) where T : class, IApplyAbleSettings; } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs b/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs index 2086fbd..47f66cf 100644 --- a/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs +++ b/GFramework.Game.Abstractions/setting/ISettingsPersistence.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace GFramework.Game.Abstractions.setting; @@ -9,31 +11,32 @@ namespace GFramework.Game.Abstractions.setting; public interface ISettingsPersistence { /// - /// 异步加载指定类型的设置节 + /// 异步加载指定类型的设置数据 /// - /// 设置节类型,必须实现ISettingsSection接口并具有无参构造函数 - /// 返回加载的设置节实例 - Task LoadAsync() where T : class, ISettingsSection, new(); + Task LoadAsync() where T : class, ISettingsData, new(); /// - /// 异步保存指定的设置节 + /// 异步保存指定的设置数据 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 要保存的设置节实例 - /// 异步操作任务 - Task SaveAsync(T section) where T : class, ISettingsSection; + Task SaveAsync(T section) where T : class, ISettingsData; /// - /// 异步检查指定类型的设置节是否存在 + /// 异步检查指定类型的设置数据是否存在 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 如果设置节存在则返回true,否则返回false - Task ExistsAsync() where T : class, ISettingsSection; + Task ExistsAsync() where T : class, ISettingsData; /// - /// 异步删除指定类型的设置节 + /// 异步删除指定类型的设置数据 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 异步操作任务 - Task DeleteAsync() where T : class, ISettingsSection; + Task DeleteAsync() where T : class, ISettingsData; + + /// + /// 保存所有设置数据 + /// + Task SaveAllAsync(IEnumerable allData); + + /// + /// 加载所有已知类型的设置数据 + /// + Task> LoadAllAsync(IEnumerable knownTypes); } \ No newline at end of file diff --git a/GFramework.Game/setting/SettingsModel.cs b/GFramework.Game/setting/SettingsModel.cs index 5d35785..611ab16 100644 --- a/GFramework.Game/setting/SettingsModel.cs +++ b/GFramework.Game/setting/SettingsModel.cs @@ -8,53 +8,87 @@ namespace GFramework.Game.setting; /// public class SettingsModel : AbstractModel, ISettingsModel { - private readonly Dictionary _sections = new(); + private readonly Dictionary _applicators = new(); + private readonly Dictionary _dataSettings = new(); /// - /// 获取指定类型的设置部分实例,如果不存在则创建新的实例 + /// 获取或创建数据设置 /// - /// 设置部分的类型,必须实现ISettingsSection接口并具有无参构造函数 - /// 指定类型的设置部分实例 - public T Get() where T : class, ISettingsSection, new() + /// 设置数据类型,必须实现ISettingsData接口并具有无参构造函数 + /// 指定类型的设置数据实例 + public T GetData() where T : class, ISettingsData, new() { var type = typeof(T); - // 尝试从字典中获取已存在的设置部分实例 - if (_sections.TryGetValue(type, out var existing)) + // 尝试从现有字典中获取已存在的设置数据 + if (_dataSettings.TryGetValue(type, out var existing)) return (T)existing; - // 创建新的设置部分实例并存储到字典中 + // 创建新的设置数据实例并存储到字典中 var created = new T(); - _sections[type] = created; + _dataSettings[type] = created; return created; } /// - /// 尝试获取指定类型的设置部分实例 + /// 注册可应用设置 /// - /// 设置部分的类型 - /// 输出参数,如果找到则返回对应的设置部分实例,否则为null - /// 如果找到指定类型的设置部分则返回true,否则返回false - public bool TryGet(Type type, out ISettingsSection section) - => _sections.TryGetValue(type, out section!); - - /// - /// 获取所有设置部分的集合 - /// - /// 包含所有设置部分的可枚举集合 - public IEnumerable All() - => _sections.Values; - - /// - /// 注册一个可应用的设置对象到管理器中 - /// - /// 要注册的可应用设置对象 - public void Register(IApplyAbleSettings applyAble) + /// 可应用设置类型,必须实现IApplyAbleSettings接口 + /// 要注册的可应用设置实例 + public void RegisterApplicator(T applicator) where T : class, IApplyAbleSettings { - // 获取传入对象的类型信息 - var type = applyAble.GetType(); - // 尝试将类型和对象添加到线程安全的字典中 - _sections.TryAdd(type, applyAble); + var type = typeof(T); + _applicators[type] = applicator; + } + + /// + /// 获取已注册的可应用设置 + /// + /// 可应用设置类型,必须实现IApplyAbleSettings接口 + /// 找到的可应用设置实例,如果未找到则返回null + public T? GetApplicator() where T : class, IApplyAbleSettings + { + var type = typeof(T); + return _applicators.TryGetValue(type, out var applicator) + ? (T)applicator + : null; + } + + /// + /// 尝试获取指定类型的设置节 + /// + /// 要查找的设置类型 + /// 输出参数,找到的设置节实例 + /// 如果找到设置节则返回true,否则返回false + public bool TryGet(Type type, out ISettingsSection section) + { + // 首先在数据设置字典中查找 + if (_dataSettings.TryGetValue(type, out var data)) + { + section = data; + return true; + } + + // 然后在应用器字典中查找 + if (_applicators.TryGetValue(type, out var applicator)) + { + section = applicator; + return true; + } + + section = null!; + return false; + } + + /// + /// 获取所有设置节的集合 + /// + /// 包含所有设置节的可枚举集合 + public IEnumerable All() + { + // 合并数据设置和应用器设置的所有值 + return _dataSettings.Values + .Concat(_applicators.Values.Cast()); } diff --git a/GFramework.Game/setting/SettingsPersistence.cs b/GFramework.Game/setting/SettingsPersistence.cs index 2e67f2a..a2abf95 100644 --- a/GFramework.Game/setting/SettingsPersistence.cs +++ b/GFramework.Game/setting/SettingsPersistence.cs @@ -13,11 +13,11 @@ public class SettingsPersistence : AbstractContextUtility, ISettingsPersistence private IStorage _storage = null!; /// - /// 异步加载指定类型的设置节数据 + /// 异步加载指定类型的设置数据 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 如果存在则返回已保存的设置数据,否则返回新创建的默认设置实例 - public async Task LoadAsync() where T : class, ISettingsSection, new() + /// 设置数据类型,必须实现ISettingsData接口 + /// 如果存在则返回存储的设置数据,否则返回新创建的实例 + public async Task LoadAsync() where T : class, ISettingsData, new() { var key = GetKey(); @@ -30,32 +30,32 @@ public class SettingsPersistence : AbstractContextUtility, ISettingsPersistence } /// - /// 异步保存设置节数据到存储中 + /// 异步保存设置数据到存储中 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 要保存的设置节实例 - public async Task SaveAsync(T section) where T : class, ISettingsSection + /// 设置数据类型,必须实现ISettingsData接口 + /// 要保存的设置数据实例 + public async Task SaveAsync(T section) where T : class, ISettingsData { var key = GetKey(); await _storage.WriteAsync(key, section); } /// - /// 异步检查指定类型的设置节是否存在 + /// 检查指定类型的设置数据是否存在 /// - /// 设置节类型,必须实现ISettingsSection接口 - /// 如果设置节存在返回true,否则返回false - public async Task ExistsAsync() where T : class, ISettingsSection + /// 设置数据类型,必须实现ISettingsData接口 + /// 如果存在返回true,否则返回false + public async Task ExistsAsync() where T : class, ISettingsData { var key = GetKey(); return await _storage.ExistsAsync(key); } /// - /// 异步删除指定类型的设置节数据 + /// 异步删除指定类型的设置数据 /// - /// 设置节类型,必须实现ISettingsSection接口 - public async Task DeleteAsync() where T : class, ISettingsSection + /// 设置数据类型,必须实现ISettingsData接口 + public async Task DeleteAsync() where T : class, ISettingsData { var key = GetKey(); _storage.Delete(key); @@ -63,18 +63,66 @@ public class SettingsPersistence : AbstractContextUtility, ISettingsPersistence } /// - /// 初始化方法,获取存储服务实例 + /// 异步保存所有设置数据到存储中 /// + /// 包含所有设置数据的可枚举集合 + public async Task SaveAllAsync(IEnumerable allData) + { + foreach (var data in allData) + { + var type = data.GetType(); + var key = GetKey(type); + await _storage.WriteAsync(key, data); + } + } + + /// + /// 异步加载所有已知类型的设置数据 + /// + /// 已知设置数据类型的集合 + /// 类型与对应设置数据的字典映射 + public async Task> LoadAllAsync(IEnumerable knownTypes) + { + var result = new Dictionary(); + + foreach (var type in knownTypes) + { + var key = GetKey(type); + + if (!await _storage.ExistsAsync(key)) continue; + // 使用反射调用泛型方法 + var method = typeof(IStorage) + .GetMethod(nameof(IStorage.ReadAsync))! + .MakeGenericMethod(type); + + var task = (Task)method.Invoke(_storage, [key])!; + await task; + + var loaded = (ISettingsData)((dynamic)task).Result; + result[type] = loaded; + } + + return result; + } + protected override void OnInit() { _storage = this.GetUtility()!; } /// - /// 获取设置节对应的存储键名 + /// 获取指定类型的存储键名 /// - /// 设置节类型 - /// 格式为"Settings_类型名称"的键名字符串 - private static string GetKey() where T : ISettingsSection - => $"Settings_{typeof(T).Name}"; + /// 设置数据类型 + /// 格式为"Settings_类型名称"的键名 + private static string GetKey() where T : ISettingsData + => GetKey(typeof(T)); + + /// + /// 获取指定类型的存储键名 + /// + /// 设置数据类型 + /// 格式为"Settings_类型名称"的键名 + private static string GetKey(Type type) + => $"Settings_{type.Name}"; } \ No newline at end of file diff --git a/GFramework.Godot/setting/AudioBusMap.cs b/GFramework.Godot/setting/AudioBusMap.cs deleted file mode 100644 index 6b9ddc0..0000000 --- a/GFramework.Godot/setting/AudioBusMap.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace GFramework.Godot.setting; - -/// -/// 音频总线映射配置类,用于定义音频系统中不同类型的音频总线名称 -/// -public sealed class AudioBusMap -{ - /// - /// 主音频总线名称,默认值为"Master" - /// - public string Master { get; init; } = "Master"; - - /// - /// 背景音乐音频总线名称,默认值为"BGM" - /// - public string Bgm { get; init; } = "BGM"; - - /// - /// 音效音频总线名称,默认值为"SFX" - /// - public string Sfx { get; init; } = "SFX"; -} \ No newline at end of file diff --git a/GFramework.Godot/setting/AudioBusMapSettings.cs b/GFramework.Godot/setting/AudioBusMapSettings.cs new file mode 100644 index 0000000..a8c8d55 --- /dev/null +++ b/GFramework.Godot/setting/AudioBusMapSettings.cs @@ -0,0 +1,28 @@ +using GFramework.Game.Abstractions.setting; + +namespace GFramework.Godot.setting; + +/// +/// 音频总线映射设置 +/// 定义了游戏中不同音频类型的总线名称配置 +/// +public class AudioBusMapSettings : ISettingsData +{ + /// + /// 主音频总线名称 + /// 默认值为"Master" + /// + public string Master { get; set; } = "Master"; + + /// + /// 背景音乐总线名称 + /// 默认值为"BGM" + /// + public string Bgm { get; set; } = "BGM"; + + /// + /// 音效总线名称 + /// 默认值为"SFX" + /// + public string Sfx { get; set; } = "SFX"; +} \ No newline at end of file diff --git a/GFramework.Godot/setting/GodotAudioSettings.cs b/GFramework.Godot/setting/GodotAudioSettings.cs index 56c15c6..5dd13ce 100644 --- a/GFramework.Godot/setting/GodotAudioSettings.cs +++ b/GFramework.Godot/setting/GodotAudioSettings.cs @@ -6,9 +6,10 @@ namespace GFramework.Godot.setting; /// /// Godot音频设置实现类,用于应用音频配置到Godot音频系统 /// -/// 音频设置对象,包含主音量、背景音乐音量和音效音量 -/// 音频总线映射对象,定义了不同音频类型的总线名称 -public class GodotAudioSettings(AudioSettings settings, AudioBusMap busMap) : IApplyAbleSettings +/// 音频设置对象,包含主音量、背景音乐音量和音效音量 +/// 音频总线映射对象,定义了不同音频类型的总线名称 +public class GodotAudioSettings(AudioSettings audioSettings, AudioBusMapSettings audioBusMapSettings) + : IApplyAbleSettings { /// /// 应用音频设置到Godot音频系统 @@ -16,9 +17,9 @@ public class GodotAudioSettings(AudioSettings settings, AudioBusMap busMap) : IA /// 表示异步操作的任务 public Task Apply() { - SetBus(busMap.Master, settings.MasterVolume); - SetBus(busMap.Bgm, settings.BgmVolume); - SetBus(busMap.Sfx, settings.SfxVolume); + SetBus(audioBusMapSettings.Master, audioSettings.MasterVolume); + SetBus(audioBusMapSettings.Bgm, audioSettings.BgmVolume); + SetBus(audioBusMapSettings.Sfx, audioSettings.SfxVolume); return Task.CompletedTask; } diff --git a/GFramework.Godot/setting/GodotGraphicsSettings.cs b/GFramework.Godot/setting/GodotGraphicsSettings.cs index 4c3c34e..eac439f 100644 --- a/GFramework.Godot/setting/GodotGraphicsSettings.cs +++ b/GFramework.Godot/setting/GodotGraphicsSettings.cs @@ -4,34 +4,34 @@ using Godot; namespace GFramework.Godot.setting; /// -/// Godot图形设置类,继承自GraphicsSettings并实现IApplyAbleSettings接口 -/// 用于管理游戏的图形显示设置,包括分辨率、全屏模式等 +/// Godot图形设置应用器 /// -public class GodotGraphicsSettings : GraphicsSettings, IApplyAbleSettings +/// 图形设置配置对象 +public class GodotGraphicsSettings(GraphicsSettings settings) : IApplyAbleSettings { /// - /// 异步应用当前图形设置到游戏窗口 - /// 该方法会根据设置的分辨率、全屏状态等参数调整Godot窗口的显示属性 + /// 应用图形设置到Godot引擎 /// - /// 表示异步操作的任务 + /// 异步任务 public async Task Apply() { - var size = new Vector2I(ResolutionWidth, ResolutionHeight); + // 创建分辨率向量 + var size = new Vector2I(settings.ResolutionWidth, settings.ResolutionHeight); - // 直接调用DisplayServer API,不使用异步或延迟 - // 1. 设置边框标志 - DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, Fullscreen); + // 设置窗口边框状态 + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, settings.Fullscreen); - // 2. 设置窗口模式 + // 设置窗口模式(全屏或窗口化) DisplayServer.WindowSetMode( - Fullscreen ? DisplayServer.WindowMode.ExclusiveFullscreen : DisplayServer.WindowMode.Windowed + settings.Fullscreen + ? DisplayServer.WindowMode.ExclusiveFullscreen + : DisplayServer.WindowMode.Windowed ); - // 3. 窗口化下设置尺寸和位置 - if (!Fullscreen) + // 非全屏模式下设置窗口大小和居中位置 + if (!settings.Fullscreen) { DisplayServer.WindowSetSize(size); - // 居中窗口 var screen = DisplayServer.GetPrimaryScreen(); var screenSize = DisplayServer.ScreenGetSize(screen); var pos = (screenSize - size) / 2;