mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
refactor(setting): 将Godot音频设置应用逻辑合并到设置类中
- 移除独立的GodotAudioApplier类,将其功能整合到GodotAudioSettings中 - 在GodotAudioSettings类中实现音频总线音量设置逻辑 - 更新项目文件移除对已删除文件的引用 - 添加设置系统和信号连接系统的技术文档 - 完善Godot扩展方法和设置模块的使用说明
This commit is contained in:
parent
807dbc482e
commit
fcac697663
204
GFramework.Game/setting/README.md
Normal file
204
GFramework.Game/setting/README.md
Normal file
@ -0,0 +1,204 @@
|
||||
# 设置系统 (Settings System)
|
||||
|
||||
## 概述
|
||||
|
||||
设置系统是 GFramework.Game 的核心组件之一,负责管理游戏中各种设置配置。该系统采用了模型-系统分离的设计模式,支持设置部分(Section)的管理和设置应用器模式。
|
||||
|
||||
## 核心类
|
||||
|
||||
### SettingsModel
|
||||
|
||||
设置模型类,继承自 `AbstractModel` 并实现 `ISettingsModel` 接口。
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 管理不同类型的设置部分(Settings Section)
|
||||
- 提供类型安全的设置访问
|
||||
- 支持可应用设置对象的注册
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `Get<T>()` - 获取或创建指定类型的设置部分
|
||||
- `TryGet(Type, out ISettingsSection)` - 尝试获取设置部分
|
||||
- `Register(IApplyAbleSettings)` - 注册可应用的设置对象
|
||||
- `All()` - 获取所有设置部分
|
||||
|
||||
### SettingsSystem
|
||||
|
||||
设置系统类,继承自 `AbstractSystem` 并实现 `ISettingsSystem` 接口。
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 应用设置配置到相应系统
|
||||
- 支持单个或批量设置应用
|
||||
- 自动识别可应用设置类型
|
||||
|
||||
**关键方法:**
|
||||
|
||||
- `ApplyAll()` - 应用所有设置配置
|
||||
- `Apply<T>()` - 应用指定类型的设置
|
||||
- `Apply(IEnumerable<Type>)` - 应用指定类型集合的设置
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[ISettingsModel] --> B[SettingsModel]
|
||||
C[ISettingsSystem] --> D[SettingsSystem]
|
||||
|
||||
B --> E[Dictionary<Type, ISettingsSection>]
|
||||
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<ISettingsModel>();
|
||||
|
||||
// 获取或创建音频设置
|
||||
var audioSettings = settingsModel.Get<GodotAudioSettings>();
|
||||
audioSettings.MasterVolume = 0.8f;
|
||||
audioSettings.BgmVolume = 0.6f;
|
||||
audioSettings.SfxVolume = 0.9f;
|
||||
|
||||
// 注册设置到模型
|
||||
settingsModel.Register(audioSettings);
|
||||
```
|
||||
|
||||
### 应用设置
|
||||
|
||||
```csharp
|
||||
// 获取设置系统
|
||||
var settingsSystem = this.GetSystem<ISettingsSystem>();
|
||||
|
||||
// 应用所有设置
|
||||
await settingsSystem.ApplyAll();
|
||||
|
||||
// 应用特定类型设置
|
||||
await settingsSystem.Apply<GodotAudioSettings>();
|
||||
|
||||
// 应用多个类型设置
|
||||
var types = new[] { typeof(GodotAudioSettings), typeof(GodotGraphicsSettings) };
|
||||
await settingsSystem.Apply(types);
|
||||
```
|
||||
|
||||
### 创建自定义设置
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 游戏设置类
|
||||
/// </summary>
|
||||
public class GameSettings : ISettingsSection
|
||||
{
|
||||
public float GameSpeed { get; set; } = 1.0f;
|
||||
public int Difficulty { get; set; } = 1;
|
||||
public bool AutoSave { get; set; } = true;
|
||||
}
|
||||
|
||||
// 使用自定义设置
|
||||
var gameSettings = settingsModel.Get<GameSettings>();
|
||||
gameSettings.GameSpeed = 1.5f;
|
||||
```
|
||||
|
||||
### 创建可应用设置
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 游戏设置应用器
|
||||
/// </summary>
|
||||
public class GameSettings : ISettingsSection, IApplyAbleSettings
|
||||
{
|
||||
public float GameSpeed { get; set; } = 1.0f;
|
||||
public int Difficulty { get; set; } = 1;
|
||||
|
||||
public Task Apply()
|
||||
{
|
||||
// 应用游戏速度
|
||||
Time.timeScale = GameSpeed;
|
||||
|
||||
// 应用难度设置
|
||||
GameDifficulty.Current = Difficulty;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接口定义
|
||||
|
||||
### ISettingsSection
|
||||
|
||||
```csharp
|
||||
public interface ISettingsSection
|
||||
{
|
||||
// 设置部分的标识接口
|
||||
}
|
||||
```
|
||||
|
||||
### IApplyAbleSettings
|
||||
|
||||
```csharp
|
||||
public interface IApplyAbleSettings : ISettingsSection
|
||||
{
|
||||
Task Apply();
|
||||
}
|
||||
```
|
||||
|
||||
### ISettingsModel
|
||||
|
||||
```csharp
|
||||
public interface ISettingsModel
|
||||
{
|
||||
T Get<T>() where T : class, ISettingsSection, new();
|
||||
bool TryGet(Type type, out ISettingsSection section);
|
||||
IEnumerable<ISettingsSection> All();
|
||||
void Register(IApplyAbleSettings applyAble);
|
||||
}
|
||||
```
|
||||
|
||||
### ISettingsSystem
|
||||
|
||||
```csharp
|
||||
public interface ISettingsSystem
|
||||
{
|
||||
Task ApplyAll();
|
||||
Task Apply<T>() where T : class, ISettingsSection;
|
||||
Task Apply(Type settingsType);
|
||||
Task Apply(IEnumerable<Type> settingsTypes);
|
||||
}
|
||||
```
|
||||
|
||||
## 设计模式
|
||||
|
||||
该系统使用了以下设计模式:
|
||||
|
||||
1. **Repository Pattern** - SettingsModel 作为设置数据的仓库
|
||||
2. **Command Pattern** - IApplyAbleSettings 的 Apply 方法作为命令
|
||||
3. **Factory Pattern** - Get<T>() 方法创建设置实例
|
||||
4. **Template Method** - AbstractSystem 提供初始化模板
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **设置分类** - 将相关设置组织到同一个设置类中
|
||||
2. **延迟应用** - 批量修改后再应用,而不是每次修改都应用
|
||||
3. **类型安全** - 使用泛型方法确保类型安全
|
||||
4. **可测试性** - 通过接口实现便于单元测试
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [Godot 设置模块](../../GFramework.Godot/setting/README.md)
|
||||
- [存储模块](../../GFramework.Godot/storage/README.md)
|
||||
- [抽象接口定义](../../../GFramework.Core/Abstractions/)
|
||||
@ -17,9 +17,4 @@
|
||||
<ProjectReference Include="..\GFramework.Game.Abstractions\GFramework.Game.Abstractions.csproj" PrivateAssets="all"/>
|
||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="extensions\ControlExtensions.cs"/>
|
||||
<Compile Remove="setting\GodotAudioSettings.cs"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
335
GFramework.Godot/extensions/README.md
Normal file
335
GFramework.Godot/extensions/README.md
Normal file
@ -0,0 +1,335 @@
|
||||
# Godot 扩展方法 (Godot Extensions)
|
||||
|
||||
## 概述
|
||||
|
||||
Godot 扩展方法模块为 Godot 引擎提供了丰富的便捷扩展方法集合。这些扩展方法简化了常见的 Godot
|
||||
开发任务,提高了代码的可读性和开发效率。该模块遵循流畅接口设计原则,支持链式调用。
|
||||
|
||||
## 模块结构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Extensions] --> B[GodotPathExtensions]
|
||||
A --> C[NodeExtensions]
|
||||
A --> D[SignalFluentExtensions]
|
||||
A --> E[UnRegisterExtension]
|
||||
D --> F[SignalBuilder]
|
||||
|
||||
B --> G[路径判断扩展]
|
||||
C --> H[节点生命周期]
|
||||
C --> I[节点查询]
|
||||
C --> J[场景树操作]
|
||||
C --> K[输入控制]
|
||||
C --> L[调试工具]
|
||||
D --> M[信号连接系统]
|
||||
E --> N[事件管理]
|
||||
```
|
||||
|
||||
## 扩展模块详解
|
||||
|
||||
### 1. 路径扩展 (GodotPathExtensions)
|
||||
|
||||
提供 Godot 虚拟路径的判断和识别功能。
|
||||
|
||||
**主要方法:**
|
||||
|
||||
- `IsUserPath()` - 判断是否为 `user://` 路径
|
||||
- `IsResPath()` - 判断是否为 `res://` 路径
|
||||
- `IsGodotPath()` - 判断是否为 Godot 虚拟路径
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
string savePath = "user://save.dat";
|
||||
string configPath = "res://config.json";
|
||||
string logPath = "C:/logs/debug.log";
|
||||
|
||||
if (savePath.IsUserPath()) Console.WriteLine("用户数据路径");
|
||||
if (configPath.IsResPath()) Console.WriteLine("资源路径");
|
||||
if (logPath.IsGodotPath()) Console.WriteLine("Godot 虚拟路径");
|
||||
else Console.WriteLine("文件系统路径");
|
||||
```
|
||||
|
||||
### 2. 节点扩展 (NodeExtensions)
|
||||
|
||||
最丰富的扩展模块,提供全面的节点操作功能。
|
||||
|
||||
#### 节点生命周期管理
|
||||
|
||||
```csharp
|
||||
// 安全释放节点
|
||||
node.QueueFreeX(); // 延迟释放
|
||||
node.FreeX(); // 立即释放
|
||||
|
||||
// 等待节点就绪
|
||||
await node.WaitUntilReady();
|
||||
|
||||
// 检查节点有效性
|
||||
if (node.IsValidNode()) Console.WriteLine("节点有效");
|
||||
if (node.IsInvalidNode()) Console.WriteLine("节点无效");
|
||||
```
|
||||
|
||||
#### 节点查询操作
|
||||
|
||||
```csharp
|
||||
// 查找子节点
|
||||
var sprite = node.FindChildX<Sprite2D>("Sprite");
|
||||
var parent = node.GetParentX<Control>();
|
||||
|
||||
// 获取或创建节点
|
||||
var panel = parent.GetOrCreateNode<Panel>("MainPanel");
|
||||
|
||||
// 遍历子节点
|
||||
node.ForEachChild<Sprite2D>(sprite => {
|
||||
sprite.Modulate = Colors.White;
|
||||
});
|
||||
```
|
||||
|
||||
#### 场景树操作
|
||||
|
||||
```csharp
|
||||
// 获取根节点
|
||||
var root = node.GetRootNodeX();
|
||||
|
||||
// 异步添加子节点
|
||||
await parent.AddChildX(childNode);
|
||||
|
||||
// 设置场景树暂停状态
|
||||
node.Paused(true); // 暂停
|
||||
node.Paused(false); // 恢复
|
||||
```
|
||||
|
||||
#### 输入控制
|
||||
|
||||
```csharp
|
||||
// 标记输入事件已处理
|
||||
node.SetInputAsHandled();
|
||||
|
||||
// 禁用/启用输入
|
||||
node.DisableInput();
|
||||
node.EnableInput();
|
||||
```
|
||||
|
||||
#### 调试工具
|
||||
|
||||
```csharp
|
||||
// 打印节点路径
|
||||
node.LogNodePath();
|
||||
|
||||
// 打印节点树
|
||||
node.PrintTreeX();
|
||||
|
||||
// 安全延迟调用
|
||||
node.SafeCallDeferred("UpdateUI");
|
||||
```
|
||||
|
||||
#### 类型转换
|
||||
|
||||
```csharp
|
||||
// 安全的类型转换
|
||||
var button = node.OfType<Button>();
|
||||
var sprite = childNode.OfType<Sprite2D>();
|
||||
```
|
||||
|
||||
### 3. 信号扩展 (SignalFluentExtensions)
|
||||
|
||||
提供流畅的信号连接 API,详见 [signal/README.md](signal/README.md)。
|
||||
|
||||
**快速示例:**
|
||||
|
||||
```csharp
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.ToAndCall(new Callable(this, nameof(OnButtonPressed)));
|
||||
```
|
||||
|
||||
### 4. 取消注册扩展 (UnRegisterExtension)
|
||||
|
||||
自动管理事件监听器的生命周期。
|
||||
|
||||
**主要方法:**
|
||||
|
||||
- `UnRegisterWhenNodeExitTree()` - 节点退出场景树时自动取消注册
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var unRegister = eventManager.Subscribe<GameEvent>(OnGameEvent);
|
||||
unRegister.UnRegisterWhenNodeExitTree(node);
|
||||
```
|
||||
|
||||
## 快速参考
|
||||
|
||||
### 常用代码片段
|
||||
|
||||
#### 场景节点管理
|
||||
|
||||
```csharp
|
||||
public class GameManager : Node
|
||||
{
|
||||
private Node _uiRoot;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_uiRoot = GetNode<Node>("UI");
|
||||
|
||||
// 创建游戏面板
|
||||
var gamePanel = _uiRoot.GetOrCreateNode<Panel>("GamePanel");
|
||||
|
||||
// 安全添加子节点
|
||||
var player = new Player();
|
||||
await AddChildX(player);
|
||||
|
||||
// 查找并配置玩家
|
||||
var sprite = player.FindChildX<Sprite2D>("Sprite");
|
||||
if (sprite.IsValidNode()) sprite.Modulate = Colors.Red;
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// 安全释放所有子节点
|
||||
ForEachChild<Node>(child => child.QueueFreeX());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### UI 事件处理
|
||||
|
||||
```csharp
|
||||
public class MainMenu : Control
|
||||
{
|
||||
private Button _startButton;
|
||||
private Button _quitButton;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_startButton = FindChildX<Button>("StartButton");
|
||||
_quitButton = FindChildX<Button>("QuitButton");
|
||||
|
||||
// 流畅的信号连接
|
||||
_startButton.Signal(BaseButton.SignalName.Pressed)
|
||||
.ToAndCall(new Callable(this, nameof(OnStartPressed)));
|
||||
|
||||
_quitButton.Signal(BaseButton.SignalName.Pressed)
|
||||
.To(new Callable(this, nameof(OnQuitPressed)));
|
||||
}
|
||||
|
||||
private void OnStartPressed()
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/game.tscn");
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 异步场景管理
|
||||
|
||||
```csharp
|
||||
public class SceneManager : Node
|
||||
{
|
||||
public async Task<T> LoadSceneAsync<T>(string scenePath) where T : Node
|
||||
{
|
||||
var packedScene = GD.Load<PackedScene>(scenePath);
|
||||
var instance = packedScene.Instantiate<T>();
|
||||
|
||||
// 等待场景加载完成
|
||||
await instance.WaitUntilReady();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public async Task TransitionToScene(string scenePath)
|
||||
{
|
||||
var newScene = await LoadSceneAsync<Node>(scenePath);
|
||||
|
||||
// 清理当前场景
|
||||
ForEachChild<Node>(child => child.QueueFreeX());
|
||||
|
||||
// 加载新场景
|
||||
await AddChildX(newScene);
|
||||
|
||||
// 设置输入处理
|
||||
newScene.SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
### 1. 安全性
|
||||
|
||||
- 所有节点操作都包含有效性检查
|
||||
- 提供安全的类型转换方法
|
||||
- 避免空引用异常
|
||||
|
||||
### 2. 便利性
|
||||
|
||||
- 流畅的 API 设计
|
||||
- 支持链式调用
|
||||
- 减少样板代码
|
||||
|
||||
### 3. 一致性
|
||||
|
||||
- 统一的命名约定
|
||||
- 一致的返回类型
|
||||
- 预测性方法行为
|
||||
|
||||
### 4. 性能
|
||||
|
||||
- 避免不必要的节点查找
|
||||
- 最小化内存分配
|
||||
- 优化常见操作
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 节点生命周期
|
||||
|
||||
```csharp
|
||||
// ✅ 推荐:使用安全释放
|
||||
node.QueueFreeX();
|
||||
|
||||
// ❌ 避免:直接释放可能导致错误
|
||||
node.QueueFree();
|
||||
```
|
||||
|
||||
### 2. 节点查询
|
||||
|
||||
```csharp
|
||||
// ✅ 推荐:类型安全的查找
|
||||
var button = node.FindChildX<Button>("Button");
|
||||
|
||||
// ❌ 避免:需要手动类型转换
|
||||
var button = node.FindChild("Button") as Button;
|
||||
```
|
||||
|
||||
### 3. 异步操作
|
||||
|
||||
```csharp
|
||||
// ✅ 推荐:等待节点就绪
|
||||
await child.WaitUntilReady();
|
||||
|
||||
// ❌ 避免:假设节点已就绪
|
||||
child.DoSomething();
|
||||
```
|
||||
|
||||
### 4. 事件管理
|
||||
|
||||
```csharp
|
||||
// ✅ 推荐:自动清理事件
|
||||
var unRegister = eventSystem.Subscribe(eventHandler);
|
||||
unRegister.UnRegisterWhenNodeExitTree(node);
|
||||
|
||||
// ❌ 避免:手动管理事件生命周期
|
||||
// 可能导致内存泄漏
|
||||
```
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [信号连接系统](signal/README.md) - 详细的信号连接 API 文档
|
||||
- [存储模块](../storage/README.md) - 文件存储系统
|
||||
- [设置模块](../setting/README.md) - 游戏设置系统
|
||||
- [设置系统](../../GFramework.Game/setting/README.md) - 通用设置框架
|
||||
427
GFramework.Godot/extensions/signal/README.md
Normal file
427
GFramework.Godot/extensions/signal/README.md
Normal file
@ -0,0 +1,427 @@
|
||||
# 信号连接系统 (Signal Connection System)
|
||||
|
||||
## 概述
|
||||
|
||||
信号连接系统是 Godot 扩展方法模块中的一个专门子模块,提供流畅、类型安全的 Godot 信号连接 API。该系统采用构建器模式(Builder
|
||||
Pattern)和流畅接口设计(Fluent Interface),大大简化了信号订阅代码,提高了代码的可读性和可维护性。
|
||||
|
||||
## 核心类
|
||||
|
||||
### SignalBuilder
|
||||
|
||||
信号连接构建器,负责构建和执行信号连接操作。
|
||||
|
||||
**特性:**
|
||||
|
||||
- 支持链式调用
|
||||
- 可配置连接标志
|
||||
- 支持连接后立即调用
|
||||
- 返回原始对象以便继续操作
|
||||
|
||||
### SignalFluentExtensions
|
||||
|
||||
为 `GodotObject` 提供信号连接扩展方法,创建 `SignalBuilder` 实例。
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[GodotObject] --> B[SignalFluentExtensions]
|
||||
B --> C[Signal Extension Method]
|
||||
C --> D[SignalBuilder]
|
||||
D --> E[WithFlags]
|
||||
D --> F[To]
|
||||
D --> G[ToAndCall]
|
||||
D --> H[End]
|
||||
|
||||
F --> I[Connect Signal]
|
||||
G --> J[Connect + Call]
|
||||
H --> K[Return GodotObject]
|
||||
|
||||
L[ConnectFlags] --> E
|
||||
M[Callable] --> F
|
||||
M --> G
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本信号连接
|
||||
|
||||
```csharp
|
||||
// 基本连接
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.To(new Callable(this, nameof(OnButtonPressed)));
|
||||
|
||||
// 带连接标志
|
||||
timer.Signal(Timer.SignalName.Timeout)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.To(new Callable(this, nameof(OnTimerTimeout)));
|
||||
```
|
||||
|
||||
### 连接并立即调用
|
||||
|
||||
```csharp
|
||||
// 连接信号并立即调用一次
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.ToAndCall(new Callable(this, nameof(OnButtonPressed)));
|
||||
|
||||
// 连接带参数的信号并立即调用
|
||||
area2D.Signal(Area2D.SignalName.BodyEntered)
|
||||
.ToAndCall(new Callable(this, nameof(OnBodyEntered)), new Variant[] { node });
|
||||
```
|
||||
|
||||
### 复杂的连接链
|
||||
|
||||
```csharp
|
||||
// 设置连接标志并连接
|
||||
player.Signal(Player.SignalName.HealthChanged)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(new Callable(this, nameof(OnHealthChanged)));
|
||||
|
||||
// 连接多个信号
|
||||
var button = GetNode<Button>("StartButton");
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.ToAndCall(new Callable(this, nameof(OnGameStarted)));
|
||||
```
|
||||
|
||||
## API 详细说明
|
||||
|
||||
### SignalBuilder 构造函数
|
||||
|
||||
```csharp
|
||||
public SignalBuilder(GodotObject target, StringName signal)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `target` - 要连接信号的 Godot 对象
|
||||
- `signal` - 要连接的信号名称
|
||||
|
||||
### SignalBuilder 方法
|
||||
|
||||
#### WithFlags
|
||||
|
||||
设置连接标志。
|
||||
|
||||
```csharp
|
||||
public SignalBuilder WithFlags(GodotObject.ConnectFlags flags)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `flags` - Godot 连接标志枚举值
|
||||
|
||||
**常用的连接标志:**
|
||||
|
||||
- `ConnectFlags.Deferred` - 延迟调用
|
||||
- `ConnectFlags.OneShot` - 一次性连接
|
||||
- `ConnectFlags.ConnectPersisted` - 连接持久化
|
||||
- `ConnectFlags.ReferenceCounted` - 引用计数
|
||||
|
||||
#### To
|
||||
|
||||
连接信号到指定的可调用对象。
|
||||
|
||||
```csharp
|
||||
public SignalBuilder To(Callable callable, GodotObject.ConnectFlags? flags = null)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `callable` - 要连接的可调用对象
|
||||
- `flags` - 可选的连接标志,覆盖之前设置的标志
|
||||
|
||||
#### ToAndCall
|
||||
|
||||
连接信号并立即调用一次。
|
||||
|
||||
```csharp
|
||||
public SignalBuilder ToAndCall(Callable callable, GodotObject.ConnectFlags? flags = null, params Variant[] args)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `callable` - 要连接的可调用对象
|
||||
- `flags` - 可选的连接标志
|
||||
- `args` - 调用时传递的参数
|
||||
|
||||
#### End
|
||||
|
||||
显式结束构建,返回原始对象。
|
||||
|
||||
```csharp
|
||||
public GodotObject End()
|
||||
```
|
||||
|
||||
### SignalFluentExtensions 扩展方法
|
||||
|
||||
#### Signal
|
||||
|
||||
为 Godot 对象创建信号构建器。
|
||||
|
||||
```csharp
|
||||
public static SignalBuilder Signal(this GodotObject @object, StringName signal)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `@object` - 扩展方法的目标对象
|
||||
- `signal` - 要连接的信号名称
|
||||
|
||||
## 实际应用场景
|
||||
|
||||
### UI 事件处理
|
||||
|
||||
```csharp
|
||||
public class MainMenu : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var startButton = GetNode<Button>("StartButton");
|
||||
var quitButton = GetNode<Button>("QuitButton");
|
||||
var settingsButton = GetNode<Button>("SettingsButton");
|
||||
|
||||
// 开始按钮 - 一次性连接并立即禁用
|
||||
startButton.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.ToAndCall(new Callable(this, nameof(OnStartPressed)));
|
||||
|
||||
// 退出按钮
|
||||
quitButton.Signal(Button.SignalName.Pressed)
|
||||
.To(new Callable(this, nameof(OnQuitPressed)));
|
||||
|
||||
// 设置按钮 - 延迟调用避免嵌套问题
|
||||
settingsButton.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(new Callable(this, nameof(OnSettingsPressed)));
|
||||
}
|
||||
|
||||
private void OnStartPressed()
|
||||
{
|
||||
GetTree().ChangeSceneToFile("res://scenes/game.tscn");
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
}
|
||||
|
||||
private void OnSettingsPressed()
|
||||
{
|
||||
// 打开设置面板
|
||||
GetNode<Control>("SettingsPanel").Show();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 游戏逻辑事件
|
||||
|
||||
```csharp
|
||||
public class Player : CharacterBody2D
|
||||
{
|
||||
private HealthComponent _health;
|
||||
private AnimationPlayer _animPlayer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_health = GetNode<HealthComponent>("HealthComponent");
|
||||
_animPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
|
||||
|
||||
// 生命值变化 - 延迟处理避免在动画中修改状态
|
||||
_health.Signal(HealthComponent.SignalName.HealthChanged)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(new Callable(this, nameof(OnHealthChanged)));
|
||||
|
||||
// 死亡事件 - 一次性连接
|
||||
_health.Signal(HealthComponent.SignalName.Died)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.To(new Callable(this, nameof(OnDied)));
|
||||
}
|
||||
|
||||
private void OnHealthChanged(float newHealth, float maxHealth)
|
||||
{
|
||||
// 更新UI或状态
|
||||
UpdateHealthBar(newHealth / maxHealth);
|
||||
|
||||
// 播放受伤动画
|
||||
if (newHealth < _health.PreviousHealth)
|
||||
{
|
||||
_animPlayer.Play("hurt");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDied()
|
||||
{
|
||||
// 播放死亡动画
|
||||
_animPlayer.Play("death");
|
||||
|
||||
// 游戏结束
|
||||
GetTree().CallDeferred(SceneTree.MethodName.Quit);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 音频管理
|
||||
|
||||
```csharp
|
||||
public class AudioManager : Node
|
||||
{
|
||||
private AudioStreamPlayer _bgmPlayer;
|
||||
private AudioStreamPlayer _sfxPlayer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_bgmPlayer = GetNode<AudioStreamPlayer>("BGMPlayer");
|
||||
_sfxPlayer = GetNode<AudioStreamPlayer>("SFXPlayer");
|
||||
|
||||
// 背景音乐播放完成
|
||||
_bgmPlayer.Signal(AudioStreamPlayer.SignalName.Finished)
|
||||
.To(new Callable(this, nameof(OnBGMFinished)));
|
||||
|
||||
// 音效播放完成 - 延迟清理
|
||||
_sfxPlayer.Signal(AudioStreamPlayer.SignalName.Finished)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(new Callable(this, nameof(OnSFXFinished)));
|
||||
}
|
||||
|
||||
private void OnBGMFinished()
|
||||
{
|
||||
// 循环播放背景音乐
|
||||
PlayBGM(_currentBGM);
|
||||
}
|
||||
|
||||
private void OnSFXFinished()
|
||||
{
|
||||
// 清理音效资源或播放队列中的下一个音效
|
||||
CleanupSFXResources();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设计模式分析
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
SignalBuilder 实现了构建器模式:
|
||||
|
||||
- 分步构建复杂的信号连接
|
||||
- 支持链式调用
|
||||
- 延迟执行到最终调用时
|
||||
|
||||
### Fluent Interface
|
||||
|
||||
流畅接口设计:
|
||||
|
||||
- 方法链式调用
|
||||
- 可读性强
|
||||
- 表达力强
|
||||
|
||||
### Extension Method Pattern
|
||||
|
||||
扩展方法模式:
|
||||
|
||||
- 为现有类型添加功能
|
||||
- 不修改原始类
|
||||
- 保持向后兼容
|
||||
|
||||
## 与原生 API 对比
|
||||
|
||||
### 原生 Godot API
|
||||
|
||||
```csharp
|
||||
// 传统方式
|
||||
button.Connect(Button.SignalName.Pressed, new Callable(this, nameof(OnButtonPressed)));
|
||||
|
||||
// 带标志的方式
|
||||
button.Connect(Button.SignalName.Pressed, new Callable(this, nameof(OnButtonPressed)), (uint)GodotObject.ConnectFlags.OneShot);
|
||||
|
||||
// 连接并立即调用
|
||||
button.Connect(Button.SignalName.Pressed, new Callable(this, nameof(OnButtonPressed)));
|
||||
new Callable(this, nameof(OnButtonPressed)).Call();
|
||||
```
|
||||
|
||||
### 信号连接系统 API
|
||||
|
||||
```csharp
|
||||
// 流畅方式
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.To(new Callable(this, nameof(OnButtonPressed)));
|
||||
|
||||
// 带标志的方式
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.To(new Callable(this, nameof(OnButtonPressed)));
|
||||
|
||||
// 连接并立即调用
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.ToAndCall(new Callable(this, nameof(OnButtonPressed)));
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
### 内存分配
|
||||
|
||||
- SignalBuilder 是轻量级对象
|
||||
- 创建开销很小
|
||||
- 使用后可被垃圾回收
|
||||
|
||||
### 调用开销
|
||||
|
||||
- 与原生 API 性能基本相同
|
||||
- 主要开销在方法链调用
|
||||
- 运行时性能无差异
|
||||
|
||||
### 推荐做法
|
||||
|
||||
- 避免在热循环中创建大量 SignalBuilder
|
||||
- 适合 UI 事件、游戏逻辑等场景
|
||||
- 可以放心使用,性能影响可忽略
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 选择合适的连接标志
|
||||
|
||||
```csharp
|
||||
// UI 事件通常使用延迟调用
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(callable);
|
||||
|
||||
// 一次性事件使用一次性标志
|
||||
dialog.Signal(CustomDialog.SignalName.Accepted)
|
||||
.WithFlags(GodotObject.ConnectFlags.OneShot)
|
||||
.To(callable);
|
||||
```
|
||||
|
||||
### 2. 合理使用 ToAndCall
|
||||
|
||||
```csharp
|
||||
// ✅ 适合:初始化时立即触发
|
||||
settingsSlider.Signal(Slider.SignalName.ValueChanged)
|
||||
.ToAndCall(new Callable(this, nameof(OnSettingsChanged)), initialSliderValue);
|
||||
|
||||
// ❌ 避免:重复连接并调用
|
||||
button.Signal(Button.SignalName.Pressed)
|
||||
.ToAndCall(new Callable(this, nameof(OnButtonPressed))); // 可能不必要
|
||||
```
|
||||
|
||||
### 3. 链式调用可读性
|
||||
|
||||
```csharp
|
||||
// ✅ 推荐:清晰的链式调用
|
||||
player.Signal(Player.SignalName.HealthChanged)
|
||||
.WithFlags(GodotObject.ConnectFlags.Deferred)
|
||||
.To(new Callable(this, nameof(UpdateHealthUI)));
|
||||
|
||||
// ❌ 避免:过度嵌套
|
||||
node.Signal(CustomSignal.Signal1).WithFlags(Flags1).To(callable1)
|
||||
.Signal(CustomSignal.Signal2).WithFlags(Flags2).To(callable2);
|
||||
```
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [Godot 扩展方法](../README.md) - 扩展方法总览
|
||||
- [节点扩展](../README.md#nodeextensions) - 更多节点操作方法
|
||||
- [取消注册扩展](../README.md#unregisterextension) - 事件生命周期管理
|
||||
- [Godot 官方信号文档](https://docs.godotengine.org/en/stable/tutorials/scripting_signals.html) - Godot 信号系统基础
|
||||
@ -1,46 +0,0 @@
|
||||
using GFramework.Game.Abstractions.setting;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.setting;
|
||||
|
||||
/// <summary>
|
||||
/// Godot音频设置应用器,用于将音频设置应用到Godot引擎的音频总线系统
|
||||
/// </summary>
|
||||
/// <param name="settings">音频设置对象,包含主音量、背景音乐音量和音效音量</param>
|
||||
/// <param name="busMap">音频总线映射对象,定义了不同音频类型的总线名称</param>
|
||||
public sealed class GodotAudioApplier(AudioSettings settings, AudioBusMap busMap) : IApplyAbleSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用音频设置到Godot音频系统
|
||||
/// </summary>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public Task Apply()
|
||||
{
|
||||
SetBus(busMap.Master, settings.MasterVolume);
|
||||
SetBus(busMap.Bgm, settings.BgmVolume);
|
||||
SetBus(busMap.Sfx, settings.SfxVolume);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定音频总线的音量
|
||||
/// </summary>
|
||||
/// <param name="busName">音频总线名称</param>
|
||||
/// <param name="linear">线性音量值(0-1之间)</param>
|
||||
private static void SetBus(string busName, float linear)
|
||||
{
|
||||
// 获取音频总线索引
|
||||
var idx = AudioServer.GetBusIndex(busName);
|
||||
if (idx < 0)
|
||||
{
|
||||
GD.PushWarning($"Audio bus not found: {busName}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将线性音量转换为分贝并设置到音频总线
|
||||
AudioServer.SetBusVolumeDb(
|
||||
idx,
|
||||
Mathf.LinearToDb(Mathf.Clamp(linear, 0.0001f, 1f))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,46 @@
|
||||
|
||||
using GFramework.Game.Abstractions.setting;
|
||||
using GFramework.Game.Abstractions.setting;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.setting;
|
||||
|
||||
public class GodotAudioSettings: AudioSettings,IApplyAbleSettings
|
||||
/// <summary>
|
||||
/// Godot音频设置实现类,用于应用音频配置到Godot音频系统
|
||||
/// </summary>
|
||||
/// <param name="settings">音频设置对象,包含主音量、背景音乐音量和音效音量</param>
|
||||
/// <param name="busMap">音频总线映射对象,定义了不同音频类型的总线名称</param>
|
||||
public class GodotAudioSettings(AudioSettings settings, AudioBusMap busMap) : IApplyAbleSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用音频设置到Godot音频系统
|
||||
/// </summary>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public Task Apply()
|
||||
{
|
||||
|
||||
SetBus(busMap.Master, settings.MasterVolume);
|
||||
SetBus(busMap.Bgm, settings.BgmVolume);
|
||||
SetBus(busMap.Sfx, settings.SfxVolume);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定音频总线的音量
|
||||
/// </summary>
|
||||
/// <param name="busName">音频总线名称</param>
|
||||
/// <param name="linear">线性音量值(0-1之间)</param>
|
||||
private static void SetBus(string busName, float linear)
|
||||
{
|
||||
// 获取音频总线索引
|
||||
var idx = AudioServer.GetBusIndex(busName);
|
||||
if (idx < 0)
|
||||
{
|
||||
GD.PushWarning($"Audio bus not found: {busName}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将线性音量转换为分贝并设置到音频总线
|
||||
AudioServer.SetBusVolumeDb(
|
||||
idx,
|
||||
Mathf.LinearToDb(Mathf.Clamp(linear, 0.0001f, 1f))
|
||||
);
|
||||
}
|
||||
}
|
||||
547
GFramework.Godot/setting/README.md
Normal file
547
GFramework.Godot/setting/README.md
Normal file
@ -0,0 +1,547 @@
|
||||
# Godot 设置模块 (Godot Settings Module)
|
||||
|
||||
## 概述
|
||||
|
||||
Godot 设置模块是 GFramework.Godot 的核心组件之一,专门为 Godot 引擎提供游戏设置系统的实现。该模块将通用的设置框架与 Godot
|
||||
引擎的特定功能相结合,提供了音频设置和图形设置的完整解决方案。
|
||||
|
||||
## 核心类
|
||||
|
||||
### 音频设置系统
|
||||
|
||||
#### AudioBusMap
|
||||
|
||||
音频总线映射配置类,用于定义音频系统中不同类型音频的总线名称。
|
||||
|
||||
**属性:**
|
||||
|
||||
- `Master` - 主音频总线名称(默认:"Master")
|
||||
- `Bgm` - 背景音乐音频总线名称(默认:"BGM")
|
||||
- `Sfx` - 音效音频总线名称(默认:"SFX")
|
||||
|
||||
#### GodotAudioApplier
|
||||
|
||||
音频设置应用器,负责将音频设置应用到 Godot 引擎的音频总线系统。
|
||||
|
||||
**功能:**
|
||||
|
||||
- 应用音量设置到指定音频总线
|
||||
- 处理音量格式转换(线性值到分贝)
|
||||
- 音频总线存在性检查和警告
|
||||
|
||||
#### GodotAudioSettings
|
||||
|
||||
Godot 音频设置实现类,继承自 AudioSettings 并实现 IApplyAbleSettings。
|
||||
|
||||
**继承关系:**
|
||||
|
||||
```
|
||||
AudioSettings (基础设置类)
|
||||
↓
|
||||
GodotAudioSettings (Godot 特定实现)
|
||||
↓
|
||||
IApplyAbleSettings (可应用设置接口)
|
||||
```
|
||||
|
||||
### 图形设置系统
|
||||
|
||||
#### GodotGraphicsSettings
|
||||
|
||||
Godot 图形设置实现类,继承自 GraphicsSettings 并实现 IApplyAbleSettings。
|
||||
|
||||
**功能:**
|
||||
|
||||
- 分辨率设置和窗口尺寸调整
|
||||
- 全屏模式切换
|
||||
- 窗口位置自动居中
|
||||
- 多显示器支持
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[AudioSettings] --> B[GodotAudioSettings]
|
||||
C[GraphicsSettings] --> D[GodotGraphicsSettings]
|
||||
E[IApplyAbleSettings] --> B
|
||||
E --> D
|
||||
|
||||
F[AudioBusMap] --> G[GodotAudioApplier]
|
||||
B --> G
|
||||
|
||||
G --> H[AudioServer API]
|
||||
D --> I[DisplayServer API]
|
||||
|
||||
J[SettingsSystem] --> K[Apply Method]
|
||||
K --> G
|
||||
K --> D
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 音频设置配置
|
||||
|
||||
#### 基本音频设置
|
||||
|
||||
```csharp
|
||||
// 创建音频设置
|
||||
var audioSettings = new GodotAudioSettings
|
||||
{
|
||||
MasterVolume = 0.8f, // 80% 主音量
|
||||
BgmVolume = 0.6f, // 60% 背景音乐音量
|
||||
SfxVolume = 0.9f // 90% 音效音量
|
||||
};
|
||||
|
||||
// 应用设置
|
||||
await audioSettings.Apply();
|
||||
```
|
||||
|
||||
#### 自定义音频总线映射
|
||||
|
||||
```csharp
|
||||
// 自定义音频总线映射
|
||||
var customBusMap = new AudioBusMap
|
||||
{
|
||||
Master = "Master_Bus",
|
||||
Bgm = "Background_Music",
|
||||
Sfx = "Sound_Effects"
|
||||
};
|
||||
|
||||
// 创建音频设置应用器
|
||||
var settings = new GodotAudioSettings
|
||||
{
|
||||
MasterVolume = 0.7f,
|
||||
BgmVolume = 0.5f,
|
||||
SfxVolume = 0.8f
|
||||
};
|
||||
|
||||
var applier = new GodotAudioApplier(settings, customBusMap);
|
||||
await applier.Apply();
|
||||
```
|
||||
|
||||
#### 通过设置系统使用
|
||||
|
||||
```csharp
|
||||
// 注册音频设置到设置模型
|
||||
var settingsModel = this.GetModel<ISettingsModel>();
|
||||
var audioSettings = settingsModel.Get<GodotAudioSettings>();
|
||||
audioSettings.MasterVolume = 0.8f;
|
||||
audioSettings.BgmVolume = 0.6f;
|
||||
audioSettings.SfxVolume = 0.9f;
|
||||
|
||||
// 通过设置系统应用
|
||||
var settingsSystem = this.GetSystem<ISettingsSystem>();
|
||||
await settingsSystem.Apply<GodotAudioSettings>();
|
||||
```
|
||||
|
||||
### 图形设置配置
|
||||
|
||||
#### 基本图形设置
|
||||
|
||||
```csharp
|
||||
// 创建图形设置
|
||||
var graphicsSettings = new GodotGraphicsSettings
|
||||
{
|
||||
ResolutionWidth = 1920,
|
||||
ResolutionHeight = 1080,
|
||||
Fullscreen = true
|
||||
};
|
||||
|
||||
// 应用设置
|
||||
await graphicsSettings.Apply();
|
||||
```
|
||||
|
||||
#### 窗口模式切换
|
||||
|
||||
```csharp
|
||||
public class DisplayManager : Node
|
||||
{
|
||||
private GodotGraphicsSettings _graphicsSettings;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_graphicsSettings = new GodotGraphicsSettings();
|
||||
}
|
||||
|
||||
public async Task ToggleFullscreen()
|
||||
{
|
||||
_graphicsSettings.Fullscreen = !_graphicsSettings.Fullscreen;
|
||||
await _graphicsSettings.Apply();
|
||||
}
|
||||
|
||||
public async Task SetResolution(int width, int height)
|
||||
{
|
||||
_graphicsSettings.ResolutionWidth = width;
|
||||
_graphicsSettings.ResolutionHeight = height;
|
||||
_graphicsSettings.Fullscreen = false; // 窗口化时自动关闭全屏
|
||||
await _graphicsSettings.Apply();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 预设分辨率配置
|
||||
|
||||
```csharp
|
||||
public class ResolutionPresets
|
||||
{
|
||||
public static readonly (int width, int height)[] CommonResolutions =
|
||||
{
|
||||
(1920, 1080), // Full HD
|
||||
(2560, 1440), // QHD
|
||||
(3840, 2160), // 4K
|
||||
(1280, 720), // HD
|
||||
(1366, 768), // 常见笔记本分辨率
|
||||
};
|
||||
|
||||
public static async Task ApplyResolution(GodotGraphicsSettings settings, int width, int height)
|
||||
{
|
||||
settings.ResolutionWidth = width;
|
||||
settings.ResolutionHeight = height;
|
||||
settings.Fullscreen = false;
|
||||
await settings.Apply();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API 详细说明
|
||||
|
||||
### AudioBusMap
|
||||
|
||||
```csharp
|
||||
public sealed class AudioBusMap
|
||||
{
|
||||
public string Master { get; init; } = "Master";
|
||||
public string Bgm { get; init; } = "BGM";
|
||||
public string Sfx { get; init; } = "SFX";
|
||||
}
|
||||
```
|
||||
|
||||
**特点:**
|
||||
|
||||
- 使用 `init` 属性,创建后不可修改
|
||||
- 提供合理的默认值
|
||||
- 支持对象初始化语法
|
||||
|
||||
### GodotAudioApplier
|
||||
|
||||
```csharp
|
||||
public sealed class GodotAudioApplier(AudioSettings settings, AudioBusMap busMap) : IApplyAbleSettings
|
||||
{
|
||||
public Task Apply();
|
||||
}
|
||||
```
|
||||
|
||||
**实现细节:**
|
||||
|
||||
- 将线性音量值(0-1)转换为分贝值
|
||||
- 使用 `AudioServer.GetBusIndex()` 查找总线
|
||||
- 使用 `AudioServer.SetBusVolumeDb()` 设置音量
|
||||
- 自动处理无效总线名称
|
||||
|
||||
### GodotAudioSettings
|
||||
|
||||
```csharp
|
||||
public class GodotAudioSettings : AudioSettings, IApplyAbleSettings
|
||||
{
|
||||
public Task Apply();
|
||||
}
|
||||
```
|
||||
|
||||
**音量设置:**
|
||||
|
||||
- `MasterVolume` - 主音量控制
|
||||
- `BgmVolume` - 背景音乐音量控制
|
||||
- `SfxVolume` - 音效音量控制
|
||||
|
||||
**Apply 方法实现:**
|
||||
|
||||
```csharp
|
||||
public Task Apply()
|
||||
{
|
||||
SetBusVolume("Master", MasterVolume);
|
||||
SetBusVolume("BGM", BgmVolume);
|
||||
SetBusVolume("SFX", SfxVolume);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
```
|
||||
|
||||
### GodotGraphicsSettings
|
||||
|
||||
```csharp
|
||||
public class GodotGraphicsSettings : GraphicsSettings, IApplyAbleSettings
|
||||
{
|
||||
public Task Apply();
|
||||
}
|
||||
```
|
||||
|
||||
**Apply 方法功能:**
|
||||
|
||||
- 设置窗口边框标志
|
||||
- 切换窗口模式(窗口化/全屏)
|
||||
- 调整窗口尺寸
|
||||
- 自动居中窗口
|
||||
|
||||
## 技术实现细节
|
||||
|
||||
### 音频音量转换
|
||||
|
||||
Godot 音频系统使用分贝(dB)作为音量单位,而我们通常使用线性值(0-1):
|
||||
|
||||
```csharp
|
||||
// 线性值到分贝转换
|
||||
float linearVolume = 0.5f; // 50% 音量
|
||||
float dbVolume = Mathf.LinearToDb(linearVolume); // 转换为分贝
|
||||
|
||||
// 应用到音频总线
|
||||
AudioServer.SetBusVolumeDb(busIndex, dbVolume);
|
||||
```
|
||||
|
||||
### 音量限制和保护
|
||||
|
||||
为避免完全静音(-inf dB),应用了最小音量限制:
|
||||
|
||||
```csharp
|
||||
float clampedVolume = Mathf.Clamp(linear, 0.0001f, 1f);
|
||||
float dbVolume = Mathf.LinearToDb(clampedVolume);
|
||||
```
|
||||
|
||||
### 窗口管理
|
||||
|
||||
#### 全屏模式
|
||||
|
||||
```csharp
|
||||
// 设置全屏
|
||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.ExclusiveFullscreen);
|
||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
|
||||
```
|
||||
|
||||
#### 窗口化模式
|
||||
|
||||
```csharp
|
||||
// 设置窗口化
|
||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
||||
DisplayServer.WindowSetSize(newSize);
|
||||
|
||||
// 居中窗口
|
||||
var screen = DisplayServer.GetPrimaryScreen();
|
||||
var screenSize = DisplayServer.ScreenGetSize(screen);
|
||||
var position = (screenSize - newSize) / 2;
|
||||
DisplayServer.WindowSetPosition(position);
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 音频设置管理
|
||||
|
||||
#### 音量变化平滑过渡
|
||||
|
||||
```csharp
|
||||
public class AudioManager : Node
|
||||
{
|
||||
private Tween _volumeTween;
|
||||
|
||||
public async Task SmoothVolumeTransition(float targetMasterVolume, float duration = 1.0f)
|
||||
{
|
||||
var audioSettings = new GodotAudioSettings();
|
||||
var currentVolume = AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex("Master"));
|
||||
var currentLinear = Mathf.DbToLinear(currentVolume);
|
||||
|
||||
_volumeTween?.Kill();
|
||||
_volumeTween = CreateTween();
|
||||
|
||||
_volumeTween.TweenMethod(
|
||||
new Callable(this, nameof(SetMasterVolume)),
|
||||
currentLinear,
|
||||
targetMasterVolume,
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
private void SetMasterVolume(float linearVolume)
|
||||
{
|
||||
var settings = new GodotAudioSettings { MasterVolume = linearVolume };
|
||||
settings.Apply();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 音频设置验证
|
||||
|
||||
```csharp
|
||||
public static class AudioSettingsValidator
|
||||
{
|
||||
public static bool ValidateBusNames(AudioBusMap busMap)
|
||||
{
|
||||
var masterIndex = AudioServer.GetBusIndex(busMap.Master);
|
||||
var bgmIndex = AudioServer.GetBusIndex(busMap.Bgm);
|
||||
var sfxIndex = AudioServer.GetBusIndex(busMap.Sfx);
|
||||
|
||||
return masterIndex >= 0 && bgmIndex >= 0 && sfxIndex >= 0;
|
||||
}
|
||||
|
||||
public static void LogMissingBuses(AudioBusMap busMap)
|
||||
{
|
||||
if (AudioServer.GetBusIndex(busMap.Master) < 0)
|
||||
GD.PrintErr($"Master bus not found: {busMap.Master}");
|
||||
|
||||
if (AudioServer.GetBusIndex(busMap.Bgm) < 0)
|
||||
GD.PrintErr($"BGM bus not found: {busMap.Bgm}");
|
||||
|
||||
if (AudioServer.GetBusIndex(busMap.Sfx) < 0)
|
||||
GD.PrintErr($"SFX bus not found: {busMap.Sfx}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 图形设置管理
|
||||
|
||||
#### 分辨率变更安全检查
|
||||
|
||||
```csharp
|
||||
public static class DisplayValidator
|
||||
{
|
||||
public static bool IsResolutionSupported(int width, int height)
|
||||
{
|
||||
var screen = DisplayServer.GetPrimaryScreen();
|
||||
var screenSize = DisplayServer.ScreenGetSize(screen);
|
||||
|
||||
return width <= screenSize.x && height <= screenSize.y;
|
||||
}
|
||||
|
||||
public static (int width, int height) GetMaxSafeResolution()
|
||||
{
|
||||
var screen = DisplayServer.GetPrimaryScreen();
|
||||
var screenSize = DisplayServer.ScreenGetSize(screen);
|
||||
|
||||
return ((int)screenSize.x, (int)screenSize.y);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 图形设置持久化
|
||||
|
||||
```csharp
|
||||
public class GraphicsSettingsManager : Node
|
||||
{
|
||||
private const string SettingsKey = "graphics_settings";
|
||||
private GodotGraphicsSettings _settings;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var storage = new GodotFileStorage(new JsonSerializer());
|
||||
|
||||
try
|
||||
{
|
||||
_settings = storage.Read<GodotGraphicsSettings>(SettingsKey);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
_settings = new GodotGraphicsSettings
|
||||
{
|
||||
ResolutionWidth = 1920,
|
||||
ResolutionHeight = 1080,
|
||||
Fullscreen = false
|
||||
};
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
var storage = new GodotFileStorage(new JsonSerializer());
|
||||
storage.Write(SettingsKey, _settings);
|
||||
}
|
||||
|
||||
public async Task ApplyAndSave()
|
||||
{
|
||||
await _settings.Apply();
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
### 1. 音频设置应用
|
||||
|
||||
- 音频总线查找是 O(1) 操作
|
||||
- 音量转换计算开销很小
|
||||
- 建议批量应用多个音量设置
|
||||
|
||||
### 2. 图形设置应用
|
||||
|
||||
- 窗口操作需要系统调用,相对较慢
|
||||
- 分辨率变更可能触发窗口重建
|
||||
- 避免频繁切换显示模式
|
||||
|
||||
### 3. 设置持久化
|
||||
|
||||
- 使用异步文件 I/O
|
||||
- 考虑设置变更防抖机制
|
||||
- 压缩设置文件以减少 I/O 开销
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. 音频总线未找到
|
||||
|
||||
```
|
||||
错误:Audio bus not found: CustomBGM
|
||||
解决:确保在 Godot 项目中创建了对应的音频总线
|
||||
```
|
||||
|
||||
#### 2. 分辨率设置无效
|
||||
|
||||
```
|
||||
错误:分辨率无法设置到指定值
|
||||
解决:检查分辨率是否超出显示器支持范围
|
||||
```
|
||||
|
||||
#### 3. 全屏模式问题
|
||||
|
||||
```
|
||||
错误:全屏切换失败
|
||||
解决:检查是否在调试器中运行,某些全屏模式在调试时可能不可用
|
||||
```
|
||||
|
||||
### 调试技巧
|
||||
|
||||
#### 音频调试
|
||||
|
||||
```csharp
|
||||
// 打印所有音频总线信息
|
||||
for (int i = 0; i < AudioServer.GetBusCount(); i++)
|
||||
{
|
||||
var name = AudioServer.GetBusName(i);
|
||||
var volume = AudioServer.GetBusVolumeDb(i);
|
||||
GD.Print($"Bus {i}: {name} ({volume} dB)");
|
||||
}
|
||||
```
|
||||
|
||||
#### 图形调试
|
||||
|
||||
```csharp
|
||||
// 打印当前显示信息
|
||||
var screen = DisplayServer.GetPrimaryScreen();
|
||||
var screenSize = DisplayServer.ScreenGetSize(screen);
|
||||
var windowSize = DisplayServer.WindowGetSize();
|
||||
var windowPos = DisplayServer.WindowGetPosition();
|
||||
var windowMode = DisplayServer.WindowGetMode();
|
||||
|
||||
GD.Print($"Screen: {screenSize}");
|
||||
GD.Print($"Window: {windowSize} at {windowPos}");
|
||||
GD.Print($"Mode: {windowMode}");
|
||||
```
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [设置系统](../../GFramework.Game/setting/README.md) - 通用设置框架
|
||||
- [存储模块](../storage/README.md) - 设置持久化存储
|
||||
- [扩展方法](../extensions/README.md) - Godot 扩展功能
|
||||
- [Godot 音频文档](https://docs.godotengine.org/en/stable/tutorials/audio/audio_buses.html) - Godot 音频总线系统
|
||||
- [Godot 显示文档](https://docs.godotengine.org/en/stable/tutorials/rendering/window_management.html) - Godot 窗口管理
|
||||
281
GFramework.Godot/storage/README.md
Normal file
281
GFramework.Godot/storage/README.md
Normal file
@ -0,0 +1,281 @@
|
||||
# 存储模块 (Storage Module)
|
||||
|
||||
## 概述
|
||||
|
||||
存储模块是 GFramework.Godot 的核心存储实现,专门为 Godot 引擎设计的文件存储系统。该模块支持 Godot 的虚拟路径系统(如
|
||||
`res://` 和 `user://`),并提供了按键级别的细粒度锁机制来保证线程安全。
|
||||
|
||||
## 核心类
|
||||
|
||||
### GodotFileStorage
|
||||
|
||||
Godot 特化的文件存储实现,实现了 `IStorage` 接口。
|
||||
|
||||
**主要特性:**
|
||||
|
||||
- ✅ Godot 虚拟路径支持(`res://`, `user://`)
|
||||
- ✅ 线程安全(按键级别的细粒度锁)
|
||||
- ✅ 同步/异步读写操作
|
||||
- ✅ 自动创建目录结构
|
||||
- ❌ 删除操作(Delete 方法未实现)
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 路径处理
|
||||
|
||||
该存储系统支持三种路径类型:
|
||||
|
||||
#### 1. Godot 资源路径 (`res://`)
|
||||
|
||||
- **用途**:存储游戏资源文件
|
||||
- **特点**:只读,包含在游戏构建中
|
||||
- **示例**:`res://config/game_settings.json`
|
||||
|
||||
#### 2. Godot 用户数据路径 (`user://`)
|
||||
|
||||
- **用途**:存储用户数据、存档、配置等
|
||||
- **特点**:可读写,游戏可访问的用户目录
|
||||
- **示例**:`user://saves/save_001.dat`
|
||||
|
||||
#### 3. 普通文件系统路径
|
||||
|
||||
- **用途**:存储临时文件或调试数据
|
||||
- **特点**:完整的文件系统访问
|
||||
- **示例**:`C:/Games/MyGame/logs/debug.log`
|
||||
|
||||
### 路径验证与清理
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[输入路径] --> B{包含 ".." ?}
|
||||
B -->|是| C[抛出异常]
|
||||
B -->|否| D{是 Godot 路径?}
|
||||
D -->|是| E[直接使用]
|
||||
D -->|否| F[清理路径段]
|
||||
F --> G[替换无效字符]
|
||||
G --> H[创建目录结构]
|
||||
H --> I[返回绝对路径]
|
||||
C --> J[结束]
|
||||
E --> J
|
||||
I --> J
|
||||
```
|
||||
|
||||
### 线程安全机制
|
||||
|
||||
每个文件路径都有独立的锁对象,确保:
|
||||
|
||||
1. **细粒度锁** - 不同文件可以并发访问
|
||||
2. **避免死锁** - 锁的获取顺序一致
|
||||
3. **高性能** - 减少锁竞争
|
||||
|
||||
## API 接口
|
||||
|
||||
### IStorage 接口
|
||||
|
||||
```csharp
|
||||
public interface IStorage
|
||||
{
|
||||
// 读取操作
|
||||
T Read<T>(string key);
|
||||
T Read<T>(string key, T defaultValue);
|
||||
Task<T> ReadAsync<T>(string key);
|
||||
|
||||
// 写入操作
|
||||
void Write<T>(string key, T value);
|
||||
Task WriteAsync<T>(string key, T value);
|
||||
|
||||
// 检查存在性
|
||||
bool Exists(string key);
|
||||
Task<bool> ExistsAsync(string key);
|
||||
|
||||
// 删除操作(未实现)
|
||||
void Delete(string key);
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本使用
|
||||
|
||||
```csharp
|
||||
// 创建存储实例(需要序列化器)
|
||||
var serializer = new JsonSerializer(); // 或其他序列化器
|
||||
var storage = new GodotFileStorage(serializer);
|
||||
|
||||
// 写入用户数据
|
||||
var userData = new UserData
|
||||
{
|
||||
PlayerName = "Alice",
|
||||
Level = 5,
|
||||
Score = 1000
|
||||
};
|
||||
storage.Write("user://player.dat", userData);
|
||||
|
||||
// 读取用户数据
|
||||
var loadedData = storage.Read<UserData>("user://player.dat");
|
||||
Console.WriteLine($"Player: {loadedData.PlayerName}, Level: {loadedData.Level}");
|
||||
```
|
||||
|
||||
### 异步操作
|
||||
|
||||
```csharp
|
||||
// 异步写入游戏配置
|
||||
var config = new GameConfig
|
||||
{
|
||||
Resolution = "1920x1080",
|
||||
Fullscreen = true,
|
||||
Volume = 0.8f
|
||||
};
|
||||
await storage.WriteAsync("user://config.json", config);
|
||||
|
||||
// 异步读取配置
|
||||
var loadedConfig = await storage.ReadAsync<GameConfig>("user://config.json");
|
||||
```
|
||||
|
||||
### 不同路径类型使用
|
||||
|
||||
```csharp
|
||||
// 读取游戏资源(只读)
|
||||
var levelData = storage.Read<LevelData>("res://levels/level_001.json");
|
||||
|
||||
// 存储用户存档
|
||||
var saveData = new SaveData { /* ... */ };
|
||||
storage.Write("user://saves/slot_001.dat", saveData);
|
||||
|
||||
// 存储调试信息(普通路径)
|
||||
var debugLog = new DebugLog { /* ... */ };
|
||||
storage.Write("logs/debug_" + DateTime.Now.Ticks + ".json", debugLog);
|
||||
```
|
||||
|
||||
### 存在性检查
|
||||
|
||||
```csharp
|
||||
// 检查文件是否存在
|
||||
if (storage.Exists("user://settings.json"))
|
||||
{
|
||||
var settings = storage.Read<AppSettings>("user://settings.json");
|
||||
// 使用设置...
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用默认设置
|
||||
var defaultSettings = new AppSettings();
|
||||
storage.Write("user://settings.json", defaultSettings);
|
||||
}
|
||||
```
|
||||
|
||||
### 带默认值的读取
|
||||
|
||||
```csharp
|
||||
// 尝试读取,如果文件不存在则返回默认值
|
||||
var settings = storage.Read("user://user_prefs.json", new UserPrefs
|
||||
{
|
||||
Language = "en",
|
||||
Volume = 1.0f,
|
||||
Difficulty = 1
|
||||
});
|
||||
```
|
||||
|
||||
## 路径扩展
|
||||
|
||||
该模块使用了路径扩展方法:
|
||||
|
||||
```csharp
|
||||
public static class GodotPathExtensions
|
||||
{
|
||||
public static bool IsUserPath(this string path);
|
||||
public static bool IsResPath(this string path);
|
||||
public static bool IsGodotPath(this string path);
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
string path1 = "user://save.dat";
|
||||
string path2 = "res://config.json";
|
||||
string path3 = "C:/temp/file.txt";
|
||||
|
||||
Console.WriteLine(path1.IsGodotPath()); // true
|
||||
Console.WriteLine(path1.IsUserPath()); // true
|
||||
Console.WriteLine(path2.IsResPath()); // true
|
||||
Console.WriteLine(path3.IsGodotPath()); // false
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
### 1. 锁机制
|
||||
|
||||
- 每个文件路径独立锁,减少锁竞争
|
||||
- 读写操作串行化,避免数据损坏
|
||||
|
||||
### 2. 文件访问
|
||||
|
||||
- Godot 虚拟路径使用 `FileAccess` API
|
||||
- 普通路径使用标准 .NET 文件 I/O
|
||||
- 自动创建目录结构
|
||||
|
||||
### 3. 内存使用
|
||||
|
||||
- 锁对象使用 `ConcurrentDictionary` 管理
|
||||
- 锁对象按需创建,避免内存泄漏
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见异常
|
||||
|
||||
1. **ArgumentException** - 路径参数无效
|
||||
- 空路径
|
||||
- 包含 ".." 的路径
|
||||
- 无效的存储键
|
||||
|
||||
2. **FileNotFoundException** - 文件不存在
|
||||
- 读取不存在的文件时抛出
|
||||
|
||||
3. **IOException** - 文件操作失败
|
||||
- 写入权限不足
|
||||
- 磁盘空间不足
|
||||
|
||||
### 错误处理示例
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var data = storage.Read<UserData>("user://save.dat");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
Console.WriteLine("存档文件不存在,创建新的存档");
|
||||
var newSave = new UserData();
|
||||
storage.Write("user://save.dat", newSave);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"读取存档失败: {ex.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **路径选择**
|
||||
- 游戏资源使用 `res://`
|
||||
- 用户数据使用 `user://`
|
||||
- 调试/临时文件使用普通路径
|
||||
|
||||
2. **异常处理**
|
||||
- 总是处理 `FileNotFoundException`
|
||||
- 使用带默认值的 `Read` 重载方法
|
||||
|
||||
3. **性能优化**
|
||||
- 批量读写时使用异步方法
|
||||
- 避免频繁的小文件操作
|
||||
|
||||
4. **序列化器选择**
|
||||
- JSON:人类可读,调试友好
|
||||
- 二进制:性能更好,文件更小
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [路径扩展](../extensions/README.md#godotpathextensions)
|
||||
- [设置系统](../setting/README.md)
|
||||
- [抽象接口定义](../../../GFramework.Core/Abstractions/storage/)
|
||||
Loading…
x
Reference in New Issue
Block a user