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;