mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
docs(core): 添加核心模块架构文档
- 添加 Architecture 包使用说明文档,介绍 MVC 架构模式实现 - 添加 Command 包使用说明文档,阐述命令模式设计和实现 - 添加 Controller 包使用说明文档,描述控制器接口规范 - 添加 Environment 包使用说明文档,定义环境配置功能 - 添加 Events 包使用说明文档,提供事件系统完整介绍
This commit is contained in:
parent
6b48c92710
commit
1dc173e4df
509
docs/core/architecture.md
Normal file
509
docs/core/architecture.md
Normal file
@ -0,0 +1,509 @@
|
||||
# Architecture 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Architecture 包是整个框架的核心,提供了基于 MVC 架构模式的应用程序架构基础。它实现了依赖注入(IoC)容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。
|
||||
|
||||
**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### IArchitecture
|
||||
|
||||
架构接口,定义了框架的核心功能契约。
|
||||
|
||||
**主要职责:**
|
||||
|
||||
- 组件注册:注册 System、Model、Utility
|
||||
- 组件获取:从容器中获取已注册的组件
|
||||
- 命令处理:发送并执行命令
|
||||
- 查询处理:发送并执行查询
|
||||
- 事件管理:发送、注册、注销事件
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
// 组件注册
|
||||
void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem;
|
||||
void RegisterModel<TModel>(TModel model) where TModel : IModel;
|
||||
void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility;
|
||||
|
||||
// 组件获取(通过容器)
|
||||
T GetModel<T>() where T : class, IModel;
|
||||
T GetSystem<T>() where T : class, ISystem;
|
||||
T GetUtility<T>() where T : class, IUtility;
|
||||
|
||||
// 命令处理
|
||||
void SendCommand(ICommand command);
|
||||
TResult SendCommand<TResult>(ICommand<TResult> command);
|
||||
|
||||
// 查询处理
|
||||
TResult SendQuery<TResult>(IQuery<TResult> query);
|
||||
|
||||
// 事件管理
|
||||
void SendEvent<T>() where T : new();
|
||||
void SendEvent<T>(T e);
|
||||
IUnRegister RegisterEvent<T>(Action<T> onEvent);
|
||||
void UnRegisterEvent<T>(Action<T> onEvent);
|
||||
```
|
||||
|
||||
### IArchitecturePhaseAware
|
||||
|
||||
架构阶段感知接口,允许组件监听架构阶段变化。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
void OnArchitecturePhase(ArchitecturePhase phase);
|
||||
```
|
||||
|
||||
### IArchitectureModule
|
||||
|
||||
架构模块接口,支持模块化架构扩展。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
void Install(IArchitecture architecture);
|
||||
```
|
||||
|
||||
### IAsyncInitializable
|
||||
|
||||
异步初始化接口,支持组件异步初始化。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
Task InitializeAsync();
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`Architecture`](Architecture.cs)
|
||||
|
||||
架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。
|
||||
|
||||
**构造函数参数:**
|
||||
|
||||
```csharp
|
||||
public abstract class Architecture(
|
||||
IArchitectureConfiguration? configuration = null,
|
||||
IEnvironment? environment = null,
|
||||
IArchitectureServices? services = null,
|
||||
IArchitectureContext? context = null
|
||||
)
|
||||
```
|
||||
|
||||
**特性:**
|
||||
|
||||
- **阶段式生命周期管理**
|
||||
:支持多个架构阶段(BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、Destroying、Destroyed)
|
||||
- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能
|
||||
- **异步初始化**:支持同步和异步两种初始化方式
|
||||
- **IoC 容器集成**:内置依赖注入容器
|
||||
- **事件系统集成**:集成类型化事件系统
|
||||
- **与平台无关**:Core 模块不依赖 Godot,可以在任何 .NET 环境中使用
|
||||
|
||||
**架构阶段:**
|
||||
|
||||
```csharp
|
||||
public enum ArchitecturePhase
|
||||
{
|
||||
None = 0, // 初始阶段
|
||||
BeforeUtilityInit = 1, // 工具初始化前
|
||||
AfterUtilityInit = 2, // 工具初始化后
|
||||
BeforeModelInit = 3, // 模型初始化前
|
||||
AfterModelInit = 4, // 模型初始化后
|
||||
BeforeSystemInit = 5, // 系统初始化前
|
||||
AfterSystemInit = 6, // 系统初始化后
|
||||
Ready = 7, // 就绪状态
|
||||
FailedInitialization = 8, // 初始化失败
|
||||
Destroying = 9, // 正在销毁
|
||||
Destroyed = 10 // 已销毁
|
||||
}
|
||||
```
|
||||
|
||||
**初始化流程:**
|
||||
|
||||
1. 创建架构实例(传入配置或使用默认配置)
|
||||
2. 调用用户自定义的 `Init()` 方法
|
||||
3. 初始化上下文工具(Context Utility)
|
||||
4. 初始化所有注册的 Model
|
||||
5. 初始化所有注册的 System
|
||||
6. 冻结 IOC 容器
|
||||
7. 进入 Ready 阶段
|
||||
|
||||
**销毁流程:**
|
||||
|
||||
1. 进入 Destroying 阶段
|
||||
2. 发送 `ArchitectureDestroyingEvent` 事件
|
||||
3. 销毁所有 System
|
||||
4. 进入 Destroyed 阶段
|
||||
5. 发送 `ArchitectureDestroyedEvent` 事件
|
||||
|
||||
**使用示例:**
|
||||
|
||||
``csharp
|
||||
// 1. 定义你的架构(继承 Architecture 基类)
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new InventoryModel());
|
||||
|
||||
// 注册 System
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterSystem(new SaveSystem());
|
||||
|
||||
// 注册 Utility
|
||||
RegisterUtility(new StorageUtility());
|
||||
RegisterUtility(new TimeUtility());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2. 创建并初始化架构
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize();
|
||||
|
||||
// 或者异步初始化
|
||||
// var architecture = new GameArchitecture();
|
||||
// await architecture.InitializeAsync();
|
||||
|
||||
// 3. 通过依赖注入使用架构
|
||||
// 在 Controller 或其他组件中注入架构实例
|
||||
public class GameController : IController
|
||||
{
|
||||
private readonly IArchitecture _architecture;
|
||||
|
||||
// 通过构造函数注入架构
|
||||
public GameController(IArchitecture architecture)
|
||||
{
|
||||
_architecture = architecture;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// 获取 Model
|
||||
var playerModel = _architecture.GetModel<PlayerModel>();
|
||||
|
||||
// 发送命令
|
||||
_architecture.SendCommand(new StartGameCommand());
|
||||
|
||||
// 发送查询
|
||||
var score = _architecture.SendQuery(new GetScoreQuery());
|
||||
|
||||
// 注册事件
|
||||
_architecture.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
// 处理玩家死亡事件
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**核心方法与属性:**
|
||||
|
||||
### 初始化方法
|
||||
|
||||
#### `Initialize()`
|
||||
|
||||
同步初始化方法,阻塞当前线程直到初始化完成。
|
||||
|
||||
```csharp
|
||||
public void Initialize()
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize(); // 阻塞等待初始化完成
|
||||
```
|
||||
|
||||
**异常处理:**
|
||||
|
||||
如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段并发送 `ArchitectureFailedInitializationEvent` 事件。
|
||||
|
||||
#### `InitializeAsync()`
|
||||
|
||||
异步初始化方法,返回 Task 以便调用者可以等待初始化完成。
|
||||
|
||||
```csharp
|
||||
public async Task InitializeAsync()
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var architecture = new GameArchitecture();
|
||||
await architecture.InitializeAsync(); // 异步等待初始化完成
|
||||
```
|
||||
|
||||
**优势:**
|
||||
|
||||
- 支持异步初始化 Model 和 System
|
||||
- 可以利用异步 I/O 操作(如异步加载数据)
|
||||
- 提高初始化性能
|
||||
|
||||
### 模块管理
|
||||
|
||||
#### `InstallModule(IArchitectureModule module)`
|
||||
|
||||
安装架构模块,用于扩展架构功能。
|
||||
|
||||
```csharp
|
||||
public void InstallModule(IArchitectureModule module)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `module`:要安装的模块实例
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义模块
|
||||
public class NetworkModule : IArchitectureModule
|
||||
{
|
||||
public void Install(IArchitecture architecture)
|
||||
{
|
||||
// 注册网络相关的 System 和 Utility
|
||||
architecture.RegisterSystem(new NetworkSystem());
|
||||
architecture.RegisterUtility(new HttpClientUtility());
|
||||
}
|
||||
}
|
||||
|
||||
// 安装模块
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.InstallModule(new NetworkModule());
|
||||
```
|
||||
|
||||
### 生命周期钩子
|
||||
|
||||
#### `RegisterLifecycleHook(IArchitectureLifecycle hook)`
|
||||
|
||||
注册生命周期钩子,用于在架构阶段变化时执行自定义逻辑。
|
||||
|
||||
```csharp
|
||||
public void RegisterLifecycleHook(IArchitectureLifecycle hook)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `hook`:生命周期钩子实例
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义生命周期钩子
|
||||
public class PerformanceMonitorHook : IArchitectureLifecycle
|
||||
{
|
||||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.BeforeModelInit:
|
||||
Console.WriteLine("开始监控 Model 初始化性能");
|
||||
break;
|
||||
case ArchitecturePhase.AfterModelInit:
|
||||
Console.WriteLine("Model 初始化完成,停止监控");
|
||||
break;
|
||||
case ArchitecturePhase.Ready:
|
||||
Console.WriteLine("架构就绪,开始性能统计");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册生命周期钩子
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.RegisterLifecycleHook(new PerformanceMonitorHook());
|
||||
architecture.Initialize();
|
||||
```
|
||||
|
||||
**注意:** 生命周期钩子只能在架构进入 Ready 阶段之前注册。
|
||||
|
||||
### 属性
|
||||
|
||||
#### `CurrentPhase`
|
||||
|
||||
获取当前架构的阶段。
|
||||
|
||||
```csharp
|
||||
public ArchitecturePhase CurrentPhase { get; }
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var architecture = new GameArchitecture();
|
||||
|
||||
// 检查架构是否已就绪
|
||||
if (architecture.CurrentPhase == ArchitecturePhase.Ready)
|
||||
{
|
||||
Console.WriteLine("架构已就绪,可以开始游戏");
|
||||
}
|
||||
|
||||
// 在异步操作中检查阶段变化
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (architecture.CurrentPhase != ArchitecturePhase.Ready)
|
||||
{
|
||||
Console.WriteLine($"当前阶段: {architecture.CurrentPhase}");
|
||||
await Task.Delay(100);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### `Context`
|
||||
|
||||
获取架构上下文,提供对架构服务的访问。
|
||||
|
||||
```csharp
|
||||
public IArchitectureContext Context { get; }
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 通过 Context 访问服务
|
||||
var context = architecture.Context;
|
||||
var eventBus = context.EventBus;
|
||||
var commandBus = context.CommandBus;
|
||||
var queryBus = context.QueryBus;
|
||||
var environment = context.Environment;
|
||||
```
|
||||
|
||||
**高级特性:**
|
||||
|
||||
``csharp
|
||||
// 1. 使用自定义配置
|
||||
var config = new ArchitectureConfiguration();
|
||||
var architecture = new GameArchitecture(configuration: config);
|
||||
|
||||
// 2. 模块安装
|
||||
var module = new GameModule();
|
||||
architecture.InstallModule(module);
|
||||
|
||||
// 3. 监听架构阶段变化
|
||||
public class GamePhaseListener : IArchitecturePhaseAware
|
||||
{
|
||||
public void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.Ready:
|
||||
GD.Print("架构已就绪,可以开始游戏了");
|
||||
break;
|
||||
case ArchitecturePhase.Destroying:
|
||||
GD.Print("架构正在销毁");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 生命周期钩子
|
||||
public class LifecycleHook : IArchitectureLifecycle
|
||||
{
|
||||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||||
{
|
||||
GD.Print($"架构阶段变化: {phase}");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### [`ArchitectureConfiguration`](ArchitectureConfiguration.cs)
|
||||
|
||||
架构配置类,用于配置架构的行为。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
``csharp
|
||||
var config = new ArchitectureConfiguration
|
||||
{
|
||||
// 严格阶段验证
|
||||
StrictPhaseValidation = true,
|
||||
// 允许延迟注册
|
||||
AllowLateRegistration = false
|
||||
};
|
||||
|
||||
var architecture = new GameArchitecture(configuration: config);
|
||||
```
|
||||
|
||||
### [`ArchitectureServices`](ArchitectureServices.cs)
|
||||
|
||||
架构服务类,管理命令总线、查询总线、IOC容器和类型事件系统。
|
||||
|
||||
### [`ArchitectureContext`](ArchitectureContext.cs)
|
||||
|
||||
架构上下文类,提供对架构服务的访问。
|
||||
|
||||
### [`GameContext`](GameContext.cs)
|
||||
|
||||
游戏上下文类,管理架构上下文与类型的绑定关系。
|
||||
|
||||
## 设计模式
|
||||
|
||||
### 1. 依赖注入
|
||||
|
||||
通过构造函数注入或容器解析获取架构实例。
|
||||
|
||||
### 2. 依赖注入(IoC)
|
||||
|
||||
使用内置 IoC 容器管理组件生命周期和依赖关系。
|
||||
|
||||
### 3. 命令模式
|
||||
|
||||
通过 `ICommand` 封装所有用户操作。
|
||||
|
||||
### 4. 查询模式(CQRS)
|
||||
|
||||
通过 `IQuery<T>` 分离查询和命令操作。
|
||||
|
||||
### 5. 观察者模式
|
||||
|
||||
通过事件系统实现组件间的松耦合通信。
|
||||
|
||||
### 6. 阶段式生命周期管理
|
||||
|
||||
通过 `ArchitecturePhase` 枚举和生命周期钩子管理架构状态。
|
||||
|
||||
### 7. 组合优于继承
|
||||
|
||||
通过接口组合获得不同能力,而不是深层继承链。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑
|
||||
2. **合理划分职责**:
|
||||
- Model:数据和状态
|
||||
- System:业务逻辑和规则
|
||||
- Utility:无状态的工具方法
|
||||
3. **使用依赖注入**:通过构造函数注入架构实例,便于测试
|
||||
4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent`
|
||||
5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信
|
||||
6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性
|
||||
7. **Core 模块与平台解耦**:GFramework.Core 不包含 Godot 相关代码,Godot 集成在单独模块中
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`command`](./command.md) - 命令模式实现
|
||||
- [`query`](./query.md) - 查询模式实现
|
||||
- [`events`](./events.md) - 事件系统
|
||||
- [`ioc`](./ioc.md) - IoC 容器
|
||||
- [`model`](./model.md) - 数据模型
|
||||
- [`system`](./system.md) - 业务系统
|
||||
- [`utility`](./utility.md) - 工具类
|
||||
- **GFramework.Godot** - Godot 特定集成(GodotNode 扩展、GodotLogger 等)
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
309
docs/core/command.md
Normal file
309
docs/core/command.md
Normal file
@ -0,0 +1,309 @@
|
||||
# Command 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Command 包实现了命令模式(Command Pattern),用于封装用户操作和业务逻辑。通过命令模式,可以将请求封装为对象,实现操作的参数化、队列化、日志记录、撤销等功能。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### 1. [`ICommand`](ICommand.cs)
|
||||
|
||||
无返回值命令接口,定义了命令的基本契约。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
void Execute(); // 执行命令
|
||||
```
|
||||
|
||||
### 2. [`ICommand<TResult>`](ICommand.cs)
|
||||
|
||||
带返回值的命令接口,用于需要返回执行结果的命令。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
TResult Execute(); // 执行命令并返回结果
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. [`AbstractCommand<TInput>`](AbstractCommand.cs)
|
||||
|
||||
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 [ContextAwareBase](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L4-L28)
|
||||
,具有上下文感知能力。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义一个命令输入参数
|
||||
public struct StartGameCommandInput : ICommandInput
|
||||
{
|
||||
public int LevelId { get; set; }
|
||||
public string PlayerName { get; set; }
|
||||
}
|
||||
|
||||
// 定义一个开始游戏的命令
|
||||
public class StartGameCommand : AbstractCommand<StartGameCommandInput>
|
||||
{
|
||||
public StartGameCommand(StartGameCommandInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnExecute(StartGameCommandInput input)
|
||||
{
|
||||
// 获取需要的模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameModel = this.GetModel<GameModel>();
|
||||
|
||||
// 执行业务逻辑
|
||||
playerModel.PlayerName.Value = input.PlayerName;
|
||||
gameModel.CurrentLevel.Value = input.LevelId;
|
||||
gameModel.GameState.Value = GameState.Playing;
|
||||
|
||||
// 发送事件通知其他模块
|
||||
this.SendEvent(new GameStartedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
// 使用命令
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void OnStartButtonClicked()
|
||||
{
|
||||
// 发送命令实例
|
||||
this.SendCommand(new StartGameCommand(new StartGameCommandInput
|
||||
{
|
||||
LevelId = 1,
|
||||
PlayerName = "Player1"
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. [`AbstractCommand<TInput, TResult>`](AbstractCommand.cs)
|
||||
|
||||
带返回值命令的抽象基类,同样继承自 [ContextAwareBase](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L4-L28)。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 定义一个计算伤害的命令输入
|
||||
public struct CalculateDamageCommandInput : ICommandInput
|
||||
{
|
||||
public int AttackerAttackPower { get; set; }
|
||||
public int DefenderDefense { get; set; }
|
||||
}
|
||||
|
||||
// 定义一个计算伤害的命令
|
||||
public class CalculateDamageCommand : AbstractCommand<CalculateDamageCommandInput, int>
|
||||
{
|
||||
public CalculateDamageCommand(CalculateDamageCommandInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
protected override int OnExecute(CalculateDamageCommandInput input)
|
||||
{
|
||||
// 获取游戏配置
|
||||
var config = this.GetModel<GameConfigModel>();
|
||||
|
||||
// 计算最终伤害
|
||||
var baseDamage = input.AttackerAttackPower - input.DefenderDefense;
|
||||
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
|
||||
|
||||
return (int)finalDamage;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用带返回值的命令
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void Attack(Character attacker, Character defender)
|
||||
{
|
||||
// 发送命令并获取返回值
|
||||
var damage = this.SendCommand(new CalculateDamageCommand(new CalculateDamageCommandInput
|
||||
{
|
||||
AttackerAttackPower = attacker.AttackPower,
|
||||
DefenderDefense = defender.Defense
|
||||
}));
|
||||
|
||||
// 应用伤害
|
||||
defender.Health -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new DamageDealtEvent(attacker, defender, damage));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令的生命周期
|
||||
|
||||
1. **创建命令**:实例化命令对象,传入必要的参数
|
||||
2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()`
|
||||
3. **返回结果**:对于带返回值的命令,返回执行结果
|
||||
4. **命令销毁**:命令执行完毕后可以被垃圾回收
|
||||
|
||||
## CommandBus - 命令总线
|
||||
|
||||
### 功能说明
|
||||
|
||||
[CommandBus](file:///d:/Project/Rider/GFramework/GFramework.Core/command/CommandBus.cs#L8-L34) 是命令执行的核心组件,负责发送和执行命令。
|
||||
|
||||
**主要方法:**
|
||||
|
||||
```csharp
|
||||
void Send(ICommand command); // 发送无返回值命令
|
||||
TResult Send<TResult>(ICommand<TResult> command); // 发送带返回值命令
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```csharp
|
||||
var commandBus = new CommandBus();
|
||||
|
||||
// 发送无返回值命令
|
||||
commandBus.Send(new StartGameCommand(new StartGameCommandInput()));
|
||||
|
||||
// 发送带返回值命令
|
||||
var result = commandBus.Send(new CalculateDamageCommand(new CalculateDamageCommandInput()));
|
||||
```
|
||||
|
||||
## EmptyCommandInput - 空命令输入
|
||||
|
||||
当命令不需要输入参数时,可以使用 `EmptyCommandInput` 类:
|
||||
|
||||
```csharp
|
||||
public class SimpleActionCommand : AbstractCommand<EmptyCommandInput>
|
||||
{
|
||||
public SimpleActionCommand(EmptyCommandInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnExecute(EmptyCommandInput input)
|
||||
{
|
||||
// 执行简单操作,无需额外参数
|
||||
this.SendEvent(new SimpleActionEvent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 用户交互操作
|
||||
|
||||
```csharp
|
||||
public struct SaveGameCommandInput : ICommandInput
|
||||
{
|
||||
public string SaveSlot { get; set; }
|
||||
}
|
||||
|
||||
public class SaveGameCommand : AbstractCommand<SaveGameCommandInput>
|
||||
{
|
||||
public SaveGameCommand(SaveGameCommandInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnExecute(SaveGameCommandInput input)
|
||||
{
|
||||
var saveSystem = this.GetSystem<SaveSystem>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
saveSystem.SavePlayerData(playerModel, input.SaveSlot);
|
||||
this.SendEvent(new GameSavedEvent(input.SaveSlot));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 业务流程控制
|
||||
|
||||
```csharp
|
||||
public struct LoadLevelCommandInput : ICommandInput
|
||||
{
|
||||
public int LevelId { get; set; }
|
||||
}
|
||||
|
||||
public class LoadLevelCommand : AbstractCommand<LoadLevelCommandInput>
|
||||
{
|
||||
public LoadLevelCommand(LoadLevelCommandInput input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnExecute(LoadLevelCommandInput input)
|
||||
{
|
||||
var levelSystem = this.GetSystem<LevelSystem>();
|
||||
var uiSystem = this.GetSystem<UISystem>();
|
||||
|
||||
// 显示加载界面
|
||||
uiSystem.ShowLoadingScreen();
|
||||
|
||||
// 加载关卡
|
||||
levelSystem.LoadLevel(input.LevelId);
|
||||
|
||||
// 发送事件
|
||||
this.SendEvent(new LevelLoadedEvent(input.LevelId));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持命令原子性**:一个命令应该完成一个完整的业务操作
|
||||
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
|
||||
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
|
||||
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
|
||||
5. **合理使用返回值**:只在确实需要返回结果时使用 `AbstractCommand<TInput, TResult>`
|
||||
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand`、`SavePlayerCommand`
|
||||
7. **输入参数结构化**:使用 `ICommandInput` 接口的实现类来组织命令参数
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 命令撤销/重做(可扩展)
|
||||
|
||||
```csharp
|
||||
public struct MoveCommandInput : ICommandInput
|
||||
{
|
||||
public Vector3 NewPosition { get; set; }
|
||||
}
|
||||
|
||||
// 实现可撤销命令
|
||||
public class MoveCommand : AbstractCommand<MoveCommandInput>, IUndoableCommand
|
||||
{
|
||||
private Vector3 _oldPosition;
|
||||
private Vector3 _newPosition;
|
||||
|
||||
public MoveCommand(MoveCommandInput input) : base(input)
|
||||
{
|
||||
_newPosition = input.NewPosition;
|
||||
}
|
||||
|
||||
protected override void OnExecute(MoveCommandInput input)
|
||||
{
|
||||
var player = this.GetModel<PlayerModel>();
|
||||
_oldPosition = player.Position;
|
||||
player.Position = input.NewPosition;
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
var player = this.GetModel<PlayerModel>();
|
||||
player.Position = _oldPosition;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行
|
||||
- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法
|
||||
- [`query`](./query.md) - 查询模式,用于数据查询
|
||||
- [`events`](./events.md) - 事件系统,命令执行后的通知机制
|
||||
- [`system`](./system.md) - 业务系统,命令的主要执行者
|
||||
- [`model`](./model.md) - 数据模型,命令操作的数据
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
413
docs/core/controller.md
Normal file
413
docs/core/controller.md
Normal file
@ -0,0 +1,413 @@
|
||||
# Controller 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Controller 包定义了控制器(Controller)的接口规范。控制器是 MVC 架构中的 C 层,负责处理用户交互、协调视图和模型,是连接表现层和业务层的桥梁。
|
||||
|
||||
**注意**:本框架使用依赖注入模式,Controller 通过构造函数或属性注入获取架构实例,而非使用全局单例。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IController`](IController.cs)
|
||||
|
||||
控制器接口,定义了控制器需要实现的所有功能契约。
|
||||
|
||||
**继承的能力接口:**
|
||||
|
||||
- [`ICanSendCommand`](../command/ICanSendCommand.cs) - 可发送命令
|
||||
- [`ICanGetSystem`](../system/ICanGetSystem.cs) - 可获取系统
|
||||
- [`ICanGetModel`](../model/ICanGetModel.cs) - 可获取模型
|
||||
- [`ICanRegisterEvent`](../events/ICanRegisterEvent.cs) - 可注册事件
|
||||
- [`ICanSendQuery`](../query/ICanSendQuery.cs) - 可发送查询
|
||||
- [`ICanGetUtility`](../utility/ICanGetUtility.cs) - 可获取工具
|
||||
|
||||
**能力说明:**
|
||||
|
||||
控制器拥有框架中最全面的能力集合,可以:
|
||||
|
||||
1. 发送命令执行业务逻辑
|
||||
2. 获取系统调用服务
|
||||
3. 获取模型读写数据
|
||||
4. 注册事件监听变化
|
||||
5. 发送查询获取信息
|
||||
6. 获取工具使用辅助功能
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础控制器实现(依赖注入模式)
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.architecture;
|
||||
|
||||
// 通过依赖注入获取架构
|
||||
public class PlayerController : IController
|
||||
{
|
||||
private readonly IArchitecture _architecture;
|
||||
private readonly IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
// 通过构造函数注入架构
|
||||
public PlayerController(IArchitecture architecture)
|
||||
{
|
||||
_architecture = architecture;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 获取模型
|
||||
var playerModel = _architecture.GetModel<PlayerModel>();
|
||||
|
||||
// 监听模型变化
|
||||
playerModel.Health.RegisterWithInitValue(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 注册事件
|
||||
_architecture.RegisterEvent<PlayerLevelUpEvent>(OnPlayerLevelUp)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
// 处理用户输入
|
||||
public void ProcessInput(double delta)
|
||||
{
|
||||
if (Input.IsActionJustPressed("attack"))
|
||||
{
|
||||
// 发送命令
|
||||
_architecture.SendCommand(new AttackCommand());
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed("use_item"))
|
||||
{
|
||||
// 发送查询
|
||||
var inventory = _architecture.SendQuery(new GetInventoryQuery());
|
||||
if (inventory.HasItem("potion"))
|
||||
{
|
||||
_architecture.SendCommand(new UseItemCommand("potion"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int newHealth)
|
||||
{
|
||||
// 更新 UI 显示
|
||||
UpdateHealthBar(newHealth);
|
||||
}
|
||||
|
||||
private void OnPlayerLevelUp(PlayerLevelUpEvent e)
|
||||
{
|
||||
// 显示升级特效
|
||||
ShowLevelUpEffect();
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// 清理事件注册
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
|
||||
private void UpdateHealthBar(int health) { /* UI 更新逻辑 */ }
|
||||
private void ShowLevelUpEffect() { /* 特效逻辑 */ }
|
||||
}
|
||||
```
|
||||
|
||||
### UI 控制器示例
|
||||
|
||||
```csharp
|
||||
// UI 面板控制器
|
||||
public class MainMenuController : IController
|
||||
{
|
||||
[Inject] private IArchitecture _architecture;
|
||||
[Inject] private IUISystem _uiSystem;
|
||||
|
||||
[Export] private Button _startButton;
|
||||
[Export] private Button _settingsButton;
|
||||
[Export] private Button _quitButton;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 绑定按钮事件
|
||||
_startButton.Pressed += OnStartButtonPressed;
|
||||
_settingsButton.Pressed += OnSettingsButtonPressed;
|
||||
_quitButton.Pressed += OnQuitButtonPressed;
|
||||
|
||||
// 获取模型更新 UI
|
||||
var gameModel = _architecture.GetModel<GameModel>();
|
||||
UpdateUI(gameModel);
|
||||
}
|
||||
|
||||
private void OnStartButtonPressed()
|
||||
{
|
||||
// 通过命令启动游戏
|
||||
_architecture.SendCommand<StartGameCommand>();
|
||||
}
|
||||
|
||||
private void OnSettingsButtonPressed()
|
||||
{
|
||||
// 查询当前设置
|
||||
var settings = _architecture.SendQuery(new GetSettingsQuery());
|
||||
|
||||
// 打开设置面板
|
||||
_uiSystem.OpenSettingsPanel(settings);
|
||||
}
|
||||
|
||||
private void OnQuitButtonPressed()
|
||||
{
|
||||
// 发送退出命令
|
||||
_architecture.SendCommand<QuitGameCommand>();
|
||||
}
|
||||
|
||||
private void UpdateUI(GameModel model) { /* UI 更新逻辑 */ }
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂交互控制器
|
||||
|
||||
```csharp
|
||||
// 战斗控制器
|
||||
public class CombatController : IController
|
||||
{
|
||||
[Inject] protected IArchitecture _architecture;
|
||||
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
private PlayerModel _playerModel;
|
||||
private CombatSystem _combatSystem;
|
||||
|
||||
[PostConstruct]
|
||||
public void Init()
|
||||
{
|
||||
// 缓存常用引用
|
||||
_playerModel = _architecture.GetModel<PlayerModel>();
|
||||
_combatSystem = _architecture.GetSystem<CombatSystem>();
|
||||
|
||||
// 注册多个事件
|
||||
_architecture.RegisterEvent<EnemySpawnedEvent>(OnEnemySpawned)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
_architecture.RegisterEvent<CombatEndedEvent>(OnCombatEnded)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 监听模型状态
|
||||
_playerModel.CombatState.Register(OnCombatStateChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnEnemySpawned(EnemySpawnedEvent e)
|
||||
{
|
||||
// 进入战斗状态
|
||||
_architecture.SendCommand(new EnterCombatCommand(e.Enemy));
|
||||
}
|
||||
|
||||
private void OnCombatEnded(CombatEndedEvent e)
|
||||
{
|
||||
if (e.Victory)
|
||||
{
|
||||
// 查询奖励
|
||||
var rewards = _architecture.SendQuery(new CalculateRewardsQuery(e.Enemy));
|
||||
|
||||
// 发放奖励
|
||||
_architecture.SendCommand(new GiveRewardsCommand(rewards));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理失败
|
||||
_architecture.SendCommand<GameOverCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCombatStateChanged(CombatState state)
|
||||
{
|
||||
// 根据战斗状态更新 UI
|
||||
_architecture.GetSystem<UISystem>().UpdateCombatUI(state);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 控制器职责
|
||||
|
||||
### ✅ 应该做的事
|
||||
|
||||
1. **处理用户输入**
|
||||
- 键盘、鼠标、触摸输入
|
||||
- UI 按钮点击
|
||||
- 手势识别
|
||||
|
||||
2. **协调视图和模型**
|
||||
- 监听模型变化更新视图
|
||||
- 将用户操作转换为命令
|
||||
|
||||
3. **管理界面逻辑**
|
||||
- UI 元素的显示/隐藏
|
||||
- 动画播放控制
|
||||
- 视觉反馈
|
||||
|
||||
4. **事件监听**
|
||||
- 注册关心的事件
|
||||
- 响应事件更新界面
|
||||
|
||||
### ❌ 不应该做的事
|
||||
|
||||
1. **不包含业务逻辑**
|
||||
- 业务逻辑应该在 System 中
|
||||
- 控制器只负责调用和协调
|
||||
|
||||
2. **不直接修改模型**
|
||||
- 应该通过 Command 修改模型
|
||||
- 避免直接访问 Model 的 setter
|
||||
|
||||
3. **不处理复杂计算**
|
||||
- 复杂计算应该在 Query 或 System 中
|
||||
- 控制器保持简洁
|
||||
|
||||
4. **不保存核心状态**
|
||||
- 核心状态应该在 Model 中
|
||||
- 控制器可以保存临时 UI 状态
|
||||
|
||||
## 生命周期管理
|
||||
|
||||
### 事件注销
|
||||
|
||||
```csharp
|
||||
public class MyController : IController
|
||||
{
|
||||
[Inject] private IArchitecture _architecture;
|
||||
|
||||
// 使用 UnRegisterList 统一管理
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 所有事件注册都添加到列表
|
||||
_architecture.RegisterEvent<GameEvent>(OnGameEvent)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
_architecture.GetModel<PlayerModel>().Health.Register(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// 统一注销所有事件
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用依赖注入获取依赖**
|
||||
- 通过构造函数注入 `IArchitecture`
|
||||
- 使用 `[Inject]` 属性标记注入字段
|
||||
|
||||
2. **保持控制器轻量**
|
||||
- 复杂逻辑放在 Command、Query、System 中
|
||||
- 控制器只做协调和转发
|
||||
|
||||
3. **合理使用缓存**
|
||||
- 频繁使用的 Model、System 可以缓存引用
|
||||
- 平衡性能和内存占用
|
||||
|
||||
4. **统一管理事件注销**
|
||||
- 使用 `IUnRegisterList` 统一管理
|
||||
- 在 `Cleanup()` 中统一注销
|
||||
|
||||
5. **命名规范**
|
||||
- 控制器类名:`XxxController`
|
||||
- 使用 `[Inject]` 或构造函数注入获取架构
|
||||
|
||||
## 常见模式
|
||||
|
||||
### 数据绑定模式
|
||||
|
||||
```csharp
|
||||
public class ScoreController : IController
|
||||
{
|
||||
[Inject] private IArchitecture _architecture;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 绑定模型数据到 UI
|
||||
_architecture.GetModel<GameModel>()
|
||||
.Score
|
||||
.RegisterWithInitValue(score => UpdateDisplay(score))
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void UpdateDisplay(int score)
|
||||
{
|
||||
// 更新分数显示
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态机模式
|
||||
|
||||
```csharp
|
||||
public class PlayerStateController : IController
|
||||
{
|
||||
[Inject] private IArchitecture _architecture;
|
||||
private Dictionary<PlayerState, Action> _stateHandlers;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_stateHandlers = new Dictionary<PlayerState, Action>
|
||||
{
|
||||
{ PlayerState.Idle, HandleIdleState },
|
||||
{ PlayerState.Moving, HandleMovingState },
|
||||
{ PlayerState.Attacking, HandleAttackingState }
|
||||
};
|
||||
|
||||
_architecture.GetModel<PlayerModel>()
|
||||
.State
|
||||
.Register(OnStateChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnStateChanged(PlayerState state)
|
||||
{
|
||||
_stateHandlers[state]?.Invoke();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 与 Godot 集成
|
||||
|
||||
在 Godot 项目中,可以使用 GFramework.Godot 提供的扩展:
|
||||
|
||||
```csharp
|
||||
using GFramework.Godot;
|
||||
|
||||
public partial class GodotPlayerController : Node, IController
|
||||
{
|
||||
[Inject] private IArchitecture _architecture;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 使用 Godot 特定的自动注销扩展
|
||||
_architecture.RegisterEvent<PlayerDiedEvent>(OnPlayerDied)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
|
||||
_architecture.GetModel<PlayerModel>()
|
||||
.Health
|
||||
.RegisterWithInitValue(OnHealthChanged)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 提供架构访问能力
|
||||
- [`command`](./command.md) - 控制器发送命令执行业务逻辑
|
||||
- [`query`](./query.md) - 控制器发送查询获取数据
|
||||
- [`events`](./events.md) - 控制器注册事件监听变化
|
||||
- [`model`](./model.md) - 控制器读取模型数据
|
||||
- [`system`](./system.md) - 控制器调用系统服务
|
||||
- [`extensions`](./extensions.md) - 提供便捷的扩展方法
|
||||
- **GFramework.Godot** - Godot 特定的控制器扩展
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
218
docs/core/environment.md
Normal file
218
docs/core/environment.md
Normal file
@ -0,0 +1,218 @@
|
||||
# Environment 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Environment 包定义了环境配置功能。Environment 提供了一个键值对存储系统,用于在运行时存储和获取各种环境特定的值,如配置选项、路径设置等。它允许应用程序在不同环境下灵活调整行为。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### IEnvironment
|
||||
|
||||
环境接口,定义了环境值的存储和获取功能。
|
||||
|
||||
**核心成员:**
|
||||
|
||||
```csharp
|
||||
string Name { get; } // 环境名称
|
||||
T? Get<T>(string key) where T : class; // 根据键获取值
|
||||
bool TryGet<T>(string key, out T value) where T : class; // 尝试获取值
|
||||
T GetRequired<T>(string key) where T : class; // 获取必需值(不存在则抛异常)
|
||||
void Register(string key, object value); // 注册键值对
|
||||
void Initialize(); // 初始化环境
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`EnvironmentBase`](EnvironmentBase.cs)
|
||||
|
||||
环境基础抽象类,实现了 IEnvironment 接口,提供环境值的存储和获取功能。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public abstract class EnvironmentBase : ContextAwareBase, IEnvironment
|
||||
{
|
||||
protected readonly Dictionary<string, object> Values = new(); // 存储环境值
|
||||
|
||||
public abstract string Name { get; } // 环境名称
|
||||
|
||||
public virtual T? Get<T>(string key) where T : class
|
||||
{
|
||||
return TryGet<T>(key, out var value) ? value : null;
|
||||
}
|
||||
|
||||
public virtual bool TryGet<T>(string key, out T value) where T : class
|
||||
{
|
||||
if (Values.TryGetValue(key, out var obj) && obj is T typed)
|
||||
{
|
||||
value = typed;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual T GetRequired<T>(string key) where T : class
|
||||
{
|
||||
if (TryGet<T>(key, out var value))
|
||||
return value;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Environment [{Name}] missing required value: key='{key}', type='{typeof(T).Name}'");
|
||||
}
|
||||
|
||||
void IEnvironment.Register(string key, object value);
|
||||
protected void Register(string key, object value); // 注册键值对
|
||||
public abstract void Initialize(); // 初始化环境
|
||||
}
|
||||
```
|
||||
|
||||
### [`DefaultEnvironment`](DefaultEnvironment.cs)
|
||||
|
||||
默认环境实现类,继承自 EnvironmentBase。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public class DefaultEnvironment : EnvironmentBase
|
||||
{
|
||||
public override string Name { get; } = "Default"; // 环境名称
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
// 默认环境初始化逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义自定义环境
|
||||
|
||||
```csharp
|
||||
public class GameEnvironment : EnvironmentBase
|
||||
{
|
||||
public override string Name { get; } = "Game";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
// 注册一些环境特定的值
|
||||
Register("GameMode", "Survival");
|
||||
Register("MaxPlayers", 4);
|
||||
Register("ServerAddress", "localhost");
|
||||
}
|
||||
|
||||
// 便捷方法
|
||||
public string GameMode => Get<string>("GameMode") ?? "Default";
|
||||
public int MaxPlayers => Get<int>("MaxPlayers") ?? 1;
|
||||
public string ServerAddress => Get<string>("ServerAddress") ?? "localhost";
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在架构中使用环境
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 环境已经在架构初始化过程中自动初始化
|
||||
// 但是我们可以在需要的时候获取环境值
|
||||
var gameMode = this.Context.Environment.Get<string>("GameMode");
|
||||
|
||||
// 注册模型和系统
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new GameplaySystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用环境值
|
||||
|
||||
```csharp
|
||||
public class NetworkSystem : AbstractSystem
|
||||
{
|
||||
private string _serverAddress;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 从环境中获取服务器地址
|
||||
_serverAddress = this.GetContext().Environment.Get<string>("ServerAddress") ?? "localhost";
|
||||
|
||||
// 注册事件
|
||||
this.RegisterEvent<ConnectToServerEvent>(OnConnectToServer);
|
||||
}
|
||||
|
||||
private void OnConnectToServer(ConnectToServerEvent e)
|
||||
{
|
||||
// 使用环境中的服务器地址
|
||||
Connect(_serverAddress, e.Port);
|
||||
}
|
||||
|
||||
private void Connect(string address, int port)
|
||||
{
|
||||
// 连接逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment vs Configuration
|
||||
|
||||
### Environment(环境)
|
||||
|
||||
- **动态性**:运行时可以更改
|
||||
- **灵活性**:根据部署环境调整行为
|
||||
- **存储类型**:运行时值、连接信息等
|
||||
- **访问方式**:通过键值对访问
|
||||
|
||||
### Configuration(配置)
|
||||
|
||||
- **静态性**:通常在启动时确定
|
||||
- **持久性**:保存在文件或外部源
|
||||
- **存储类型**:应用设置、参数等
|
||||
- **访问方式**:通过配置对象访问
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **明确环境名称**:为每个环境提供有意义的名称
|
||||
2. **类型安全**:使用泛型方法确保类型安全
|
||||
3. **错误处理**:使用 GetRequired 方法获取必需值
|
||||
4. **初始化时机**:在架构初始化期间完成环境设置
|
||||
5. **避免过度使用**:仅存储环境相关的值
|
||||
|
||||
## 错误示例
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:获取必需值但不存在
|
||||
public class BadExampleSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 如果"RequiredValue"不存在,这会抛出异常
|
||||
var value = this.GetContext().Environment.GetRequired<string>("RequiredValue");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:安全获取值
|
||||
public class GoodExampleSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 使用 TryGet 安全获取值
|
||||
if (this.GetContext().Environment.TryGet<string>("OptionalValue", out var value))
|
||||
{
|
||||
// 处理值
|
||||
}
|
||||
|
||||
// 或者提供默认值
|
||||
var gameMode = this.GetContext().Environment.Get<string>("GameMode") ?? "Default";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 在架构中使用环境配置
|
||||
- [`rule`](./rule.md) - 环境基类继承自 ContextAwareBase
|
||||
- [`ioc`](./ioc.md) - 环境值可通过IoC容器管理
|
||||
524
docs/core/events.md
Normal file
524
docs/core/events.md
Normal file
@ -0,0 +1,524 @@
|
||||
# Events 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Events 包提供了一套完整的事件系统,实现了观察者模式(Observer Pattern)。通过事件系统,可以实现组件间的松耦合通信,支持无参和带参事件、事件注册/注销、以及灵活的事件组合。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### 1. [`IEvent`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IEvent.cs#L7-L11)
|
||||
|
||||
基础事件接口,定义了事件注册的基本功能。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
IUnRegister Register(Action onEvent); // 注册事件处理函数
|
||||
```
|
||||
|
||||
### 2. [`IUnRegister`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IUnRegister.cs#L6-L10)
|
||||
|
||||
注销接口,用于取消事件注册。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
void UnRegister(); // 执行注销操作
|
||||
```
|
||||
|
||||
### 3. [
|
||||
|
||||
`IUnRegisterList`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IUnRegisterList.cs#L6-L10)
|
||||
|
||||
注销列表接口,用于批量管理注销对象。
|
||||
|
||||
**属性:**
|
||||
|
||||
```csharp
|
||||
IList<IUnRegister> UnregisterList { get; } // 获取注销列表
|
||||
```
|
||||
|
||||
### 4. [`IEventBus`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IEventBus.cs#L6-L22)
|
||||
|
||||
事件总线接口,提供基于类型的事件发送和注册。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
IUnRegister Register<T>(Action<T> onEvent); // 注册类型化事件
|
||||
void Send<T>(T e); // 发送事件实例
|
||||
void Send<T>() where T : new(); // 发送事件(自动创建实例)
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. [`EasyEvent`](EasyEvent.cs)
|
||||
|
||||
无参事件类,支持注册、注销和触发无参事件。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建事件
|
||||
var onClicked = new EasyEvent();
|
||||
|
||||
// 注册监听
|
||||
var unregister = onClicked.Register(() =>
|
||||
{
|
||||
GD.Print("Button clicked!");
|
||||
});
|
||||
|
||||
// 触发事件
|
||||
onClicked.Trigger();
|
||||
|
||||
// 取消注册
|
||||
unregister.UnRegister();
|
||||
```
|
||||
|
||||
### 2. [`Event<T>`](EasyEventGeneric.cs)
|
||||
|
||||
单参数泛型事件类,支持一个参数的事件。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建带参数的事件
|
||||
var onScoreChanged = new Event<int>();
|
||||
|
||||
// 注册监听
|
||||
onScoreChanged.Register(newScore =>
|
||||
{
|
||||
GD.Print($"Score changed to: {newScore}");
|
||||
});
|
||||
|
||||
// 触发事件并传递参数
|
||||
onScoreChanged.Trigger(100);
|
||||
```
|
||||
|
||||
### 3. [`Event<T, TK>`](EasyEventGeneric.cs)
|
||||
|
||||
双参数泛型事件类。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 伤害事件:攻击者、伤害值
|
||||
var onDamageDealt = new Event<string, int>();
|
||||
|
||||
onDamageDealt.Register((attacker, damage) =>
|
||||
{
|
||||
GD.Print($"{attacker} dealt {damage} damage!");
|
||||
});
|
||||
|
||||
onDamageDealt.Trigger("Player", 50);
|
||||
```
|
||||
|
||||
### 4. [`EasyEvents`](EasyEvents.cs)
|
||||
|
||||
全局事件管理器,提供类型安全的事件注册和获取。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 注册全局事件类型
|
||||
EasyEvents.Register<GameStartEvent>();
|
||||
|
||||
// 获取事件实例
|
||||
var gameStartEvent = EasyEvents.Get<GameStartEvent>();
|
||||
|
||||
// 注册监听
|
||||
gameStartEvent.Register(() =>
|
||||
{
|
||||
GD.Print("Game started!");
|
||||
});
|
||||
|
||||
// 触发事件
|
||||
gameStartEvent.Trigger();
|
||||
```
|
||||
|
||||
### 5. [`EventBus`](EventBus.cs)
|
||||
|
||||
类型化事件系统,支持基于类型的事件发送和注册。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 使用全局事件系统
|
||||
var eventBus = new EventBus();
|
||||
|
||||
// 注册类型化事件
|
||||
eventBus.Register<PlayerDiedEvent>(e =>
|
||||
{
|
||||
GD.Print($"Player died at position: {e.Position}");
|
||||
});
|
||||
|
||||
// 发送事件(传递实例)
|
||||
eventBus.Send(new PlayerDiedEvent
|
||||
{
|
||||
Position = new Vector3(10, 0, 5)
|
||||
});
|
||||
|
||||
// 发送事件(自动创建实例)
|
||||
eventBus.Send<PlayerDiedEvent>();
|
||||
```
|
||||
|
||||
### 6. [`DefaultUnRegister`](DefaultUnRegister.cs)
|
||||
|
||||
默认注销器实现,封装注销回调。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
Action onUnregister = () => GD.Print("Unregistered");
|
||||
var unregister = new DefaultUnRegister(onUnregister);
|
||||
|
||||
// 执行注销
|
||||
unregister.UnRegister();
|
||||
```
|
||||
|
||||
### 7. [`OrEvent`](OrEvent.cs)
|
||||
|
||||
事件或运算组合器,当任意一个事件触发时触发。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var onAnyInput = new OrEvent()
|
||||
.Or(onKeyPressed)
|
||||
.Or(onMouseClicked)
|
||||
.Or(onTouchDetected);
|
||||
|
||||
// 当上述任意事件触发时,执行回调
|
||||
onAnyInput.Register(() =>
|
||||
{
|
||||
GD.Print("Input detected!");
|
||||
});
|
||||
```
|
||||
|
||||
### 8. [`UnRegisterList`](UnRegisterList.cs)
|
||||
|
||||
批量管理注销对象的列表。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var unregisterList = new UnRegisterList();
|
||||
|
||||
// 添加到列表
|
||||
someEvent.Register(OnEvent).AddToUnregisterList(unregisterList);
|
||||
|
||||
// 批量注销
|
||||
unregisterList.UnRegisterAll();
|
||||
```
|
||||
|
||||
### 9. [`ArchitectureEvents`](ArchitectureEvents.cs)
|
||||
|
||||
定义了架构生命周期相关的事件。
|
||||
|
||||
**包含事件:**
|
||||
|
||||
- `ArchitectureLifecycleReadyEvent` - 架构生命周期准备就绪
|
||||
- `ArchitectureDestroyingEvent` - 架构销毁中
|
||||
- `ArchitectureDestroyedEvent` - 架构已销毁
|
||||
- `ArchitectureFailedInitializationEvent` - 架构初始化失败
|
||||
|
||||
## 在架构中使用事件
|
||||
|
||||
### 定义事件类
|
||||
|
||||
```csharp
|
||||
// 简单事件
|
||||
public struct GameStartedEvent { }
|
||||
|
||||
// 带数据的事件
|
||||
public struct PlayerDiedEvent
|
||||
{
|
||||
public Vector3 Position;
|
||||
public string Cause;
|
||||
}
|
||||
|
||||
// 复杂事件
|
||||
public struct LevelCompletedEvent
|
||||
{
|
||||
public int LevelId;
|
||||
public float CompletionTime;
|
||||
public int Score;
|
||||
public List<string> Achievements;
|
||||
}
|
||||
```
|
||||
|
||||
### Model 中发送事件
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听生命值变化
|
||||
Health.Register(newHealth =>
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
{
|
||||
// 发送玩家死亡事件
|
||||
this.SendEvent(new PlayerDiedEvent
|
||||
{
|
||||
Position = Position,
|
||||
Cause = "Health depleted"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System 中发送事件
|
||||
|
||||
```csharp
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void DealDamage(Character attacker, Character target, int damage)
|
||||
{
|
||||
target.Health -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new DamageDealtEvent
|
||||
{
|
||||
Attacker = attacker.Name,
|
||||
Target = target.Name,
|
||||
Damage = damage
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller 中注册事件
|
||||
|
||||
```csharp
|
||||
public partial class GameController : Node, IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 注册多个事件
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e)
|
||||
{
|
||||
GD.Print("Game started!");
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
GD.Print($"Player died at {e.Position}: {e.Cause}");
|
||||
ShowGameOverScreen();
|
||||
}
|
||||
|
||||
private void OnLevelCompleted(LevelCompletedEvent e)
|
||||
{
|
||||
GD.Print($"Level {e.LevelId} completed! Score: {e.Score}");
|
||||
ShowVictoryScreen(e);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 1. 事件链式组合
|
||||
|
||||
```csharp
|
||||
// 使用 Or 组合多个事件
|
||||
var onAnyDamage = new OrEvent()
|
||||
.Or(onPhysicalDamage)
|
||||
.Or(onMagicDamage)
|
||||
.Or(onPoisonDamage);
|
||||
|
||||
onAnyDamage.Register(() =>
|
||||
{
|
||||
PlayDamageSound();
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 事件过滤
|
||||
|
||||
```csharp
|
||||
// 只处理高伤害事件
|
||||
this.RegisterEvent<DamageDealtEvent>(e =>
|
||||
{
|
||||
if (e.Damage >= 50)
|
||||
{
|
||||
ShowCriticalHitEffect();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 事件转发
|
||||
|
||||
```csharp
|
||||
public class EventBridge : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 将内部事件转发为公共事件
|
||||
this.RegisterEvent<InternalPlayerDiedEvent>(e =>
|
||||
{
|
||||
this.SendEvent(new PublicPlayerDiedEvent
|
||||
{
|
||||
PlayerId = e.Id,
|
||||
Timestamp = DateTime.Now
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 临时事件监听
|
||||
|
||||
```csharp
|
||||
public class TutorialController : Node, IController
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
// 只监听一次
|
||||
IUnRegister unregister = null;
|
||||
unregister = this.RegisterEvent<FirstEnemyKilledEvent>(e =>
|
||||
{
|
||||
ShowTutorialComplete();
|
||||
unregister?.UnRegister(); // 立即注销
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 条件事件
|
||||
|
||||
```csharp
|
||||
public class AchievementSystem : AbstractSystem
|
||||
{
|
||||
private int _killCount = 0;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<EnemyKilledEvent>(e =>
|
||||
{
|
||||
_killCount++;
|
||||
|
||||
// 条件满足时发送成就事件
|
||||
if (_killCount >= 100)
|
||||
{
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "kill_100_enemies"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 生命周期管理
|
||||
|
||||
### 使用 UnRegisterList
|
||||
|
||||
```csharp
|
||||
public class MyController : Node, IController
|
||||
{
|
||||
// 统一管理所有注销对象
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 所有注册都添加到列表
|
||||
this.RegisterEvent<Event1>(OnEvent1)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<Event2>(OnEvent2)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// 一次性注销所有
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 Godot 节点生命周期
|
||||
|
||||
```csharp
|
||||
public override void _Ready()
|
||||
{
|
||||
// 当节点退出场景树时自动注销
|
||||
this.RegisterEvent<GameEvent>(OnGameEvent)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **事件命名规范**
|
||||
- 使用过去式:`PlayerDiedEvent`、`LevelCompletedEvent`
|
||||
- 使用 `Event` 后缀:便于识别
|
||||
- 使用结构体:减少内存分配
|
||||
|
||||
2. **事件数据设计**
|
||||
- 只包含必要信息
|
||||
- 使用值类型(struct)提高性能
|
||||
- 避免传递可变引用
|
||||
|
||||
3. **避免事件循环**
|
||||
- 事件处理器中谨慎发送新事件
|
||||
- 使用命令打破循环依赖
|
||||
|
||||
4. **合理使用事件**
|
||||
- 用于通知状态变化
|
||||
- 用于跨模块通信
|
||||
- 不用于返回数据(使用 Query)
|
||||
|
||||
5. **注销管理**
|
||||
- 始终注销事件监听
|
||||
- 使用 `IUnRegisterList` 批量管理
|
||||
- 利用 Godot 节点生命周期
|
||||
|
||||
6. **性能考虑**
|
||||
- 避免频繁触发的事件(如每帧)
|
||||
- 事件处理器保持轻量
|
||||
- 使用结构体事件减少 GC
|
||||
|
||||
## 事件 vs 其他通信方式
|
||||
|
||||
| 方式 | 适用场景 | 优点 | 缺点 |
|
||||
|----------------------|--------------|-----------|---------|
|
||||
| **Event** | 状态变化通知、跨模块通信 | 松耦合、一对多 | 难以追踪调用链 |
|
||||
| **Command** | 执行操作、修改状态 | 封装逻辑、可撤销 | 单向通信 |
|
||||
| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 |
|
||||
| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 |
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 提供全局事件系统
|
||||
- [`extensions`](./extensions.md) - 提供事件扩展方法
|
||||
- [`property`](./property.md) - 可绑定属性基于事件实现
|
||||
- [`controller`](./controller.md) - 控制器监听事件
|
||||
- [`model`](./model.md) - 模型发送事件
|
||||
- [`system`](./system.md) - 系统发送和监听事件
|
||||
552
docs/core/extensions.md
Normal file
552
docs/core/extensions.md
Normal file
@ -0,0 +1,552 @@
|
||||
# Extensions 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Extensions 包提供了一系列扩展方法,简化了框架各个接口的使用。通过扩展方法,可以用更简洁的语法访问框架功能,提高代码可读性和开发效率。
|
||||
|
||||
## 扩展方法类别
|
||||
|
||||
### 1. ContextAware 扩展 ([`ContextAwareExtensions.cs`](ContextAwareExtensions.cs))
|
||||
|
||||
为 [`IContextAware`](../../GFramework.Core.Abstractions/rule/IContextAware.cs)
|
||||
提供扩展方法,允许直接从实现了 [IContextAware](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs)
|
||||
的对象获取架构组件。
|
||||
|
||||
#### GetSystem 扩展方法
|
||||
|
||||
```csharp
|
||||
public static TSystem? GetSystem<TSystem>(this IContextAware contextAware)
|
||||
where TSystem : class, ISystem
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 在实现了 IContextAware 的类中使用
|
||||
public class PlayerController : IController
|
||||
{
|
||||
public void UpdateUI()
|
||||
{
|
||||
// 直接通过 this 调用
|
||||
var playerSystem = this.GetSystem<PlayerSystem>();
|
||||
var inventorySystem = this.GetSystem<InventorySystem>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GetModel 扩展方法
|
||||
|
||||
```csharp
|
||||
public static TModel? GetModel<TModel>(this IContextAware contextAware)
|
||||
where TModel : class, IModel
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class PlayerController : IController
|
||||
{
|
||||
public void UpdateStats()
|
||||
{
|
||||
// 获取模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
|
||||
// 使用模型数据
|
||||
playerModel.Health += 10;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GetUtility 扩展方法
|
||||
|
||||
```csharp
|
||||
public static TUtility? GetUtility<TUtility>(this IContextAware contextAware)
|
||||
where TUtility : class, IUtility
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameModel : AbstractModel, IContextAware
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 获取工具
|
||||
var timeUtility = this.GetUtility<TimeUtility>();
|
||||
var storageUtility = this.GetUtility<StorageUtility>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### SendCommand 扩展方法
|
||||
|
||||
```csharp
|
||||
// 发送无返回值的命令
|
||||
public static void SendCommand(this IContextAware contextAware, ICommand command)
|
||||
|
||||
// 发送带返回值的命令
|
||||
public static TResult SendCommand<TResult>(this IContextAware contextAware, ICommand<TResult> command)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameController : IController
|
||||
{
|
||||
public void OnStartButtonClicked()
|
||||
{
|
||||
// 发送命令实例
|
||||
this.SendCommand(new StartGameCommand());
|
||||
|
||||
// 发送带返回值的命令
|
||||
var result = this.SendCommand(new CalculateScoreCommand());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### SendQuery 扩展方法
|
||||
|
||||
```csharp
|
||||
public static TResult SendQuery<TResult>(this IContextAware contextAware, IQuery<TResult> query)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class InventoryController : IController
|
||||
{
|
||||
public void ShowInventory()
|
||||
{
|
||||
// 发送查询获取数据
|
||||
var items = this.SendQuery(new GetInventoryItemsQuery());
|
||||
var gold = this.SendQuery(new GetPlayerGoldQuery());
|
||||
|
||||
UpdateInventoryUI(items, gold);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### SendEvent 扩展方法
|
||||
|
||||
```csharp
|
||||
// 发送无参事件
|
||||
public static void SendEvent<T>(this IContextAware contextAware) where T : new()
|
||||
|
||||
// 发送事件实例
|
||||
public static void SendEvent<T>(this IContextAware contextAware, T e) where T : class
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel, IContextAware
|
||||
{
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
Health -= damage;
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
// 方式1:发送无参事件
|
||||
this.SendEvent<PlayerDiedEvent>();
|
||||
|
||||
// 方式2:发送带数据的事件
|
||||
this.SendEvent(new PlayerDiedEvent
|
||||
{
|
||||
Position = Position,
|
||||
Cause = "Damage"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### RegisterEvent 扩展方法
|
||||
|
||||
```csharp
|
||||
public static IUnRegister RegisterEvent<TEvent>(this IContextAware contextAware, Action<TEvent> handler)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class GameController : IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<PlayerLevelUpEvent>(OnPlayerLevelUp)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e) { }
|
||||
private void OnPlayerLevelUp(PlayerLevelUpEvent e) { }
|
||||
}
|
||||
```
|
||||
|
||||
#### UnRegisterEvent 扩展方法
|
||||
|
||||
```csharp
|
||||
public static void UnRegisterEvent<TEvent>(this IContextAware contextAware, Action<TEvent> onEvent)
|
||||
```
|
||||
|
||||
### GetEnvironment 扩展方法
|
||||
|
||||
```csharp
|
||||
public static T? GetEnvironment<T>(this IContextAware contextAware) where T : class
|
||||
public static IEnvironment GetEnvironment(this IContextAware contextAware)
|
||||
```
|
||||
|
||||
### 2. Object 扩展 ([`ObjectExtensions.cs`](ObjectExtensions.cs))
|
||||
|
||||
提供基于运行时类型判断的对象扩展方法,用于简化类型分支、链式调用和架构分派逻辑。
|
||||
|
||||
#### IfType 扩展方法
|
||||
|
||||
```csharp
|
||||
// 最简单的类型判断
|
||||
public static bool IfType<T>(this object obj, Action<T> action)
|
||||
|
||||
// 带条件的类型判断
|
||||
public static bool IfType<T>(
|
||||
this object obj,
|
||||
Func<T, bool> predicate,
|
||||
Action<T> action
|
||||
)
|
||||
|
||||
// 条件判断,带不匹配时的处理
|
||||
public static void IfType<T>(
|
||||
this object obj,
|
||||
Action<T> whenMatch,
|
||||
Action<object>? whenNotMatch = null
|
||||
)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
object obj = new MyRule();
|
||||
|
||||
// 简单类型判断
|
||||
bool executed = obj.IfType<MyRule>(rule =>
|
||||
{
|
||||
rule.Initialize();
|
||||
});
|
||||
|
||||
// 带条件的类型判断
|
||||
obj.IfType<MyRule>(
|
||||
r => r.Enabled, // 条件
|
||||
r => r.Execute() // 执行动作
|
||||
);
|
||||
|
||||
// 带不匹配处理的类型判断
|
||||
obj.IfType<IRule>(
|
||||
rule => rule.Execute(),
|
||||
other => Logger.Warn($"Unsupported type: {other.GetType()}")
|
||||
);
|
||||
```
|
||||
|
||||
#### IfType<T, TResult> 扩展方法
|
||||
|
||||
```csharp
|
||||
public static TResult? IfType<T, TResult>(
|
||||
this object obj,
|
||||
Func<T, TResult> func
|
||||
)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
object obj = new MyRule { Name = "TestRule" };
|
||||
|
||||
string? name = obj.IfType<MyRule, string>(r => r.Name);
|
||||
```
|
||||
|
||||
#### As 和 Do 扩展方法
|
||||
|
||||
```csharp
|
||||
// 安全类型转换
|
||||
public static T? As<T>(this object obj) where T : class
|
||||
|
||||
// 流式调用
|
||||
public static T Do<T>(this T obj, Action<T> action)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 安全类型转换
|
||||
obj.As<MyRule>()
|
||||
?.Execute();
|
||||
|
||||
// 流式调用
|
||||
obj.As<MyRule>()
|
||||
?.Do(r => r.Initialize())
|
||||
?.Do(r => r.Execute());
|
||||
|
||||
// 组合使用
|
||||
obj.As<MyRule>()
|
||||
?.Do(rule =>
|
||||
{
|
||||
if (rule.Enabled)
|
||||
rule.Execute();
|
||||
});
|
||||
```
|
||||
|
||||
#### SwitchType 扩展方法
|
||||
|
||||
```csharp
|
||||
public static void SwitchType(
|
||||
this object obj,
|
||||
params (Type type, Action<object> action)[] handlers
|
||||
)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
obj.SwitchType(
|
||||
(typeof(IRule), o => HandleRule((IRule)o)),
|
||||
(typeof(ISystem), o => HandleSystem((ISystem)o)),
|
||||
(typeof(IModel), o => HandleModel((IModel)o))
|
||||
);
|
||||
```
|
||||
|
||||
### 3. OrEvent 扩展 ([`OrEventExtensions.cs`](OrEventExtensions.cs))
|
||||
|
||||
为 [`IEvent`](../../GFramework.Core.Abstractions/events/IEvent.cs) 提供事件组合功能。
|
||||
|
||||
#### OrEventExtensions
|
||||
|
||||
```csharp
|
||||
public static OrEvent Or(this IEvent self, IEvent e)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 组合多个事件:当任意一个触发时执行
|
||||
var onAnyInput = onKeyPressed.Or(onMouseClicked).Or(onTouchDetected);
|
||||
|
||||
onAnyInput.Register(() =>
|
||||
{
|
||||
GD.Print("Any input detected!");
|
||||
});
|
||||
|
||||
// 链式组合
|
||||
var onAnyDamage = onPhysicalDamage
|
||||
.Or(onMagicDamage)
|
||||
.Or(onPoisonDamage);
|
||||
```
|
||||
|
||||
### 4. UnRegisterList 扩展 ([`UnRegisterListExtension.cs`](UnRegisterListExtension.cs))
|
||||
|
||||
为 [`IUnRegister`](../events/IUnRegister.cs) 和 [`IUnRegisterList`](../events/IUnRegisterList.cs) 提供批量管理功能。
|
||||
|
||||
#### UnRegisterListExtension
|
||||
|
||||
```csharp
|
||||
// 添加到注销列表
|
||||
public static void AddToUnregisterList(this IUnRegister self,
|
||||
IUnRegisterList unRegisterList)
|
||||
|
||||
// 批量注销
|
||||
public static void UnRegisterAll(this IUnRegisterList self)
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class ComplexController : IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 所有注册都添加到列表中
|
||||
this.RegisterEvent<Event1>(OnEvent1)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.RegisterEvent<Event2>(OnEvent2)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.GetModel<Model1>().Property1.Register(OnProperty1Changed)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
this.GetModel<Model2>().Property2.Register(OnProperty2Changed)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// 一次性注销所有
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 完整使用示例
|
||||
|
||||
### Controller 示例
|
||||
|
||||
```csharp
|
||||
public partial class GameplayController : IController
|
||||
{
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// 使用扩展方法获取 Model
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameModel = this.GetModel<GameModel>();
|
||||
|
||||
// 使用扩展方法注册事件
|
||||
this.RegisterEvent<GameStartedEvent>(OnGameStarted)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 监听可绑定属性
|
||||
playerModel.Health.Register(OnHealthChanged)
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public void Process(double delta)
|
||||
{
|
||||
// 发送命令
|
||||
this.SendCommand(new AttackCommand(targetId: 1));
|
||||
|
||||
// 发送查询
|
||||
var hasPotion = this.SendQuery(new HasItemQuery("health_potion"));
|
||||
if (hasPotion)
|
||||
{
|
||||
this.SendCommand<UseHealthPotionCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameStarted(GameStartedEvent e)
|
||||
{
|
||||
Console.WriteLine("Game started!");
|
||||
}
|
||||
|
||||
private void OnHealthChanged(int health)
|
||||
{
|
||||
UpdateHealthBar(health);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command 示例
|
||||
|
||||
```csharp
|
||||
public class ComplexGameCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 获取多个组件
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameSystem = this.GetSystem<GameSystem>();
|
||||
var timeUtility = this.GetUtility<TimeUtility>();
|
||||
|
||||
// 执行业务逻辑
|
||||
var currentTime = timeUtility.GetCurrentTime();
|
||||
gameSystem.ProcessGameLogic(playerModel, currentTime);
|
||||
|
||||
// 发送事件通知
|
||||
this.SendEvent(new GameStateChangedEvent());
|
||||
|
||||
// 可以发送其他命令(谨慎使用)
|
||||
this.SendCommand<SaveGameCommand>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System 示例
|
||||
|
||||
```csharp
|
||||
public class AchievementSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled);
|
||||
this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted);
|
||||
}
|
||||
|
||||
private void OnEnemyKilled(EnemyKilledEvent e)
|
||||
{
|
||||
// 获取模型
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.EnemyKillCount++;
|
||||
|
||||
// 检查成就
|
||||
if (playerModel.EnemyKillCount >= 100)
|
||||
{
|
||||
// 发送成就解锁事件
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "kill_100_enemies"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLevelCompleted(LevelCompletedEvent e)
|
||||
{
|
||||
// 发送查询
|
||||
var completionTime = this.SendQuery(new GetLevelTimeQuery(e.LevelId));
|
||||
|
||||
if (completionTime < 60)
|
||||
{
|
||||
this.SendEvent(new AchievementUnlockedEvent
|
||||
{
|
||||
AchievementId = "speed_runner"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展方法的优势
|
||||
|
||||
1. **简洁的语法**
|
||||
:不需要显式调用 [GetContext()](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L13-L15)
|
||||
2. **类型安全**:编译时检查类型
|
||||
3. **可读性高**:代码意图更清晰
|
||||
4. **智能提示**:IDE 可以提供完整的自动补全
|
||||
5. **链式调用**:支持流式编程风格
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **确保引用命名空间:**
|
||||
```csharp
|
||||
using GFramework.Core.extensions;
|
||||
```
|
||||
|
||||
2. **理解扩展方法本质:**
|
||||
- 扩展方法是静态方法的语法糖
|
||||
- 不会改变原始类型的结构
|
||||
- 仅在编译时解析
|
||||
|
||||
3. **性能考虑:**
|
||||
- 扩展方法本身无性能开销
|
||||
- 实际调用的是底层方法
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 扩展方法最终调用架构方法
|
||||
- [`command`](./command.md) - 命令发送扩展
|
||||
- [`query`](./query.md) - 查询发送扩展
|
||||
- [`events`](./events.md) - 事件注册和 Or 组合扩展
|
||||
- [`model`](./model.md) - 模型获取扩展
|
||||
- [`system`](./system.md) - 系统获取扩展
|
||||
- [`utility`](./utility.md) - 工具获取扩展
|
||||
689
docs/core/ioc.md
Normal file
689
docs/core/ioc.md
Normal file
@ -0,0 +1,689 @@
|
||||
# IoC 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
IoC(Inversion of Control,控制反转)包提供了一个轻量级的依赖注入容器,用于管理框架中各种组件的注册和获取。通过 IoC
|
||||
容器,可以实现组件间的解耦,便于测试和维护。
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`IocContainer`](IocContainer.cs)
|
||||
|
||||
IoC 容器类,负责管理对象的注册和获取。
|
||||
|
||||
**主要功能:**
|
||||
|
||||
- 注册实例到容器
|
||||
- 从容器中获取实例
|
||||
- 类型安全的依赖管理
|
||||
- 线程安全操作
|
||||
- 容器冻结保护
|
||||
|
||||
## 核心方法
|
||||
|
||||
### 1. Register<T> 和 Register(Type, object)
|
||||
|
||||
注册一个实例到容器中。
|
||||
|
||||
```csharp
|
||||
public void Register<T>(T instance)
|
||||
public void Register(Type type, object instance)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `instance`: 要注册的实例对象
|
||||
- `type`: 要注册的类型(重载方法)
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册各种类型的实例
|
||||
container.Register<IPlayerModel>(new PlayerModel());
|
||||
container.Register<IGameSystem>(new GameSystem());
|
||||
container.Register<IStorageUtility>(new StorageUtility());
|
||||
```
|
||||
|
||||
### 2. RegisterSingleton<T>
|
||||
|
||||
注册单例实例到容器中。一个类型只允许一个实例。
|
||||
|
||||
```csharp
|
||||
public void RegisterSingleton<T>(T instance)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `instance`: 要注册的单例实例
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册单例
|
||||
container.RegisterSingleton<IPlayerModel>(new PlayerModel());
|
||||
```
|
||||
|
||||
### 3. RegisterPlurality
|
||||
|
||||
注册多个实例,将实例注册到其实现的所有接口和具体类型上。
|
||||
|
||||
```csharp
|
||||
public void RegisterPlurality(object instance)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `instance`: 要注册的实例
|
||||
|
||||
### 4. RegisterSystem
|
||||
|
||||
注册系统实例,将其绑定到其所有实现的接口上。
|
||||
|
||||
```csharp
|
||||
public void RegisterSystem(ISystem system)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `system`: 系统实例对象
|
||||
|
||||
### 5. Get<T> 和 GetAll<T>
|
||||
|
||||
从容器中获取指定类型的实例。
|
||||
|
||||
```csharp
|
||||
public T? Get<T>() where T : class
|
||||
public IReadOnlyList<T> GetAll<T>() where T : class
|
||||
```
|
||||
|
||||
**返回值:**
|
||||
|
||||
- `Get<T>`: 返回指定类型的实例,如果未找到则返回 `null`
|
||||
- `GetAll<T>`: 返回指定类型的所有实例列表,如果未找到则返回空数组
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 获取已注册的实例
|
||||
var playerModel = container.Get<IPlayerModel>();
|
||||
var gameSystems = container.GetAll<IGameSystem>();
|
||||
|
||||
// 如果类型未注册,Get 返回 null,GetAll 返回空数组
|
||||
var unknownService = container.Get<IUnknownService>(); // null
|
||||
```
|
||||
|
||||
### 6. GetRequired<T>
|
||||
|
||||
获取指定类型的必需实例,如果没有注册或注册了多个实例会抛出异常。
|
||||
|
||||
```csharp
|
||||
public T GetRequired<T>() where T : class
|
||||
```
|
||||
|
||||
**返回值:**
|
||||
|
||||
- 返回找到的唯一实例
|
||||
|
||||
### 7. GetAllSorted<T>
|
||||
|
||||
获取并排序(系统调度专用)。
|
||||
|
||||
```csharp
|
||||
public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `comparison`: 比较器委托,定义排序规则
|
||||
|
||||
**返回值:**
|
||||
|
||||
- 按指定方式排序后的实例列表
|
||||
|
||||
## 在框架中的使用
|
||||
|
||||
### Architecture 中的应用
|
||||
|
||||
IoC 容器是 [`Architecture`](../architecture/Architecture.cs) 类的核心组件,用于管理所有的 System、Model 和 Utility。
|
||||
|
||||
```csharp
|
||||
public abstract class Architecture : IArchitecture
|
||||
{
|
||||
// 内置 IoC 容器
|
||||
private readonly IocContainer _mContainer = new();
|
||||
|
||||
// 注册系统
|
||||
public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
|
||||
{
|
||||
system.SetArchitecture(this);
|
||||
_mContainer.Register(system); // 注册到容器
|
||||
// ...
|
||||
}
|
||||
|
||||
// 获取系统
|
||||
public TSystem GetSystem<TSystem>() where TSystem : class, ISystem
|
||||
=> _mContainer.Get<TSystem>(); // 从容器获取
|
||||
|
||||
// Model 和 Utility 同理
|
||||
}
|
||||
```
|
||||
|
||||
### 注册组件到容器
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 这些方法内部都使用 IoC 容器
|
||||
|
||||
// 注册 Model(存储游戏数据)
|
||||
RegisterModel<IPlayerModel>(new PlayerModel());
|
||||
RegisterModel<IInventoryModel>(new InventoryModel());
|
||||
|
||||
// 注册 System(业务逻辑)
|
||||
RegisterSystem<IGameplaySystem>(new GameplaySystem());
|
||||
RegisterSystem<ISaveSystem>(new SaveSystem());
|
||||
|
||||
// 注册 Utility(工具类)
|
||||
RegisterUtility<ITimeUtility>(new TimeUtility());
|
||||
RegisterUtility<IStorageUtility>(new StorageUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 从容器获取组件
|
||||
|
||||
```csharp
|
||||
// 通过扩展方法间接使用 IoC 容器
|
||||
public class PlayerController : IController
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
// GetModel 内部调用 Architecture.GetModel
|
||||
// Architecture.GetModel 内部调用 IocContainer.Get
|
||||
var playerModel = this.GetModel<IPlayerModel>();
|
||||
|
||||
var gameplaySystem = this.GetSystem<IGameplaySystem>();
|
||||
var timeUtility = this.GetUtility<ITimeUtility>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 内部实现
|
||||
|
||||
```csharp
|
||||
public class IocContainer
|
||||
{
|
||||
// 使用字典存储类型到实例集合的映射
|
||||
private readonly Dictionary<Type, HashSet<object>> _typeIndex = new();
|
||||
private readonly HashSet<object> _objects = [];
|
||||
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
|
||||
private volatile bool _frozen = false;
|
||||
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
// 获取写锁以确保线程安全
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
RegisterInternal(typeof(T), instance!);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public T? Get<T>() where T : class
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (_typeIndex.TryGetValue(typeof(T), out var set) && set.Count > 0)
|
||||
{
|
||||
var result = set.First() as T;
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterInternal(Type type, object instance)
|
||||
{
|
||||
if (_frozen) throw new InvalidOperationException("IocContainer is frozen");
|
||||
|
||||
_objects.Add(instance);
|
||||
|
||||
if (!_typeIndex.TryGetValue(type, out var set))
|
||||
{
|
||||
set = [];
|
||||
_typeIndex[type] = set;
|
||||
}
|
||||
|
||||
set.Add(instance);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 线程安全机制
|
||||
|
||||
容器使用 [ReaderWriterLockSlim](xref:System.Threading.ReaderWriterLockSlim) 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。
|
||||
|
||||
### 注册流程
|
||||
|
||||
```
|
||||
用户代码
|
||||
↓
|
||||
RegisterSystem<T>(system)
|
||||
↓
|
||||
IocContainer.Register<T>(system)
|
||||
↓
|
||||
加写锁 -> Dictionary[typeof(T)] 添加实例到HashSet
|
||||
```
|
||||
|
||||
### 获取流程
|
||||
|
||||
```
|
||||
用户代码
|
||||
↓
|
||||
this.GetSystem<T>()
|
||||
↓
|
||||
Architecture.GetSystem<T>()
|
||||
↓
|
||||
IocContainer.Get<T>()
|
||||
↓
|
||||
加读锁 -> Dictionary.TryGetValue(typeof(T)) 获取HashSet
|
||||
↓
|
||||
返回HashSet中第一个实例或 null
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础使用
|
||||
|
||||
```csharp
|
||||
// 1. 创建容器
|
||||
var container = new IocContainer();
|
||||
|
||||
// 2. 注册服务
|
||||
var playerService = new PlayerService();
|
||||
container.Register<IPlayerService>(playerService);
|
||||
|
||||
// 3. 获取服务
|
||||
var service = container.Get<IPlayerService>();
|
||||
service.DoSomething();
|
||||
```
|
||||
|
||||
### 接口和实现分离
|
||||
|
||||
```csharp
|
||||
// 定义接口
|
||||
public interface IDataService
|
||||
{
|
||||
void SaveData(string data);
|
||||
string LoadData();
|
||||
}
|
||||
|
||||
// 实现类
|
||||
public class LocalDataService : IDataService
|
||||
{
|
||||
public void SaveData(string data) { /* 本地存储 */ }
|
||||
public string LoadData() { /* 本地加载 */ return ""; }
|
||||
}
|
||||
|
||||
public class CloudDataService : IDataService
|
||||
{
|
||||
public void SaveData(string data) { /* 云端存储 */ }
|
||||
public string LoadData() { /* 云端加载 */ return ""; }
|
||||
}
|
||||
|
||||
// 注册(可以根据配置选择不同实现)
|
||||
var container = new IocContainer();
|
||||
|
||||
#if CLOUD_SAVE
|
||||
container.Register<IDataService>(new CloudDataService());
|
||||
#else
|
||||
container.Register<IDataService>(new LocalDataService());
|
||||
#endif
|
||||
|
||||
// 使用(不需要关心具体实现)
|
||||
var dataService = container.Get<IDataService>();
|
||||
dataService.SaveData("game data");
|
||||
```
|
||||
|
||||
### 注册多个实现
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册多个相同接口的不同实现
|
||||
container.Register<IDataService>(new LocalDataService());
|
||||
container.Register<IDataService>(new CloudDataService());
|
||||
|
||||
// 获取单个实例(返回第一个)
|
||||
var singleService = container.Get<IDataService>(); // 返回第一个注册的实例
|
||||
|
||||
// 获取所有实例
|
||||
var allServices = container.GetAll<IDataService>(); // 返回两个实例的列表
|
||||
```
|
||||
|
||||
## 其他实用方法
|
||||
|
||||
### `Contains<T>()`
|
||||
|
||||
检查容器中是否包含指定类型的实例。
|
||||
|
||||
```csharp
|
||||
public bool Contains<T>() where T : class
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- 无泛型参数
|
||||
|
||||
**返回值:**
|
||||
|
||||
- 如果容器中包含指定类型的实例则返回 `true`,否则返回 `false`
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 检查服务是否已注册
|
||||
if (container.Contains<IPlayerService>())
|
||||
{
|
||||
Console.WriteLine("Player service is available");
|
||||
}
|
||||
|
||||
// 根据检查结果决定是否注册
|
||||
if (!container.Contains<ISettingsService>())
|
||||
{
|
||||
container.Register<ISettingsService>(new SettingsService());
|
||||
}
|
||||
```
|
||||
|
||||
**应用场景:**
|
||||
|
||||
- 条件注册服务
|
||||
- 检查依赖是否可用
|
||||
- 动态功能开关
|
||||
|
||||
### `ContainsInstance(object instance)`
|
||||
|
||||
判断容器中是否包含某个具体的实例对象。
|
||||
|
||||
```csharp
|
||||
public bool ContainsInstance(object instance)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `instance`:待查询的实例对象
|
||||
|
||||
**返回值:**
|
||||
|
||||
- 若容器中包含该实例则返回 `true`,否则返回 `false`
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
var service = new MyService();
|
||||
container.Register<IMyService>(service);
|
||||
|
||||
// 检查特定实例是否在容器中
|
||||
if (container.ContainsInstance(service))
|
||||
{
|
||||
Console.WriteLine("This instance is registered in the container");
|
||||
}
|
||||
|
||||
// 检查另一个实例
|
||||
var anotherService = new MyService();
|
||||
if (!container.ContainsInstance(anotherService))
|
||||
{
|
||||
Console.WriteLine("This instance is not in the container");
|
||||
}
|
||||
```
|
||||
|
||||
**应用场景:**
|
||||
|
||||
- 避免重复注册同一实例
|
||||
- 检查对象是否已被管理
|
||||
- 调试和日志记录
|
||||
|
||||
### `Clear()`
|
||||
|
||||
清空容器中的所有实例。
|
||||
|
||||
```csharp
|
||||
public void Clear()
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册多个服务
|
||||
container.Register<IService1>(new Service1());
|
||||
container.Register<IService2>(new Service2());
|
||||
container.Register<IService3>(new Service3());
|
||||
|
||||
// 清空容器
|
||||
container.Clear();
|
||||
|
||||
// 检查是否清空成功
|
||||
Console.WriteLine($"Contains IService1: {container.Contains<IService1>()}"); // False
|
||||
Console.WriteLine($"Contains IService2: {container.Contains<IService2>()}"); // False
|
||||
```
|
||||
|
||||
**应用场景:**
|
||||
|
||||
- 重置容器状态
|
||||
- 内存清理
|
||||
- 测试环境准备
|
||||
|
||||
**注意事项:**
|
||||
|
||||
- 容器冻结后也可以调用 `Clear()` 方法
|
||||
- 清空后,所有已注册的实例都将丢失
|
||||
- 不会自动清理已注册对象的其他引用
|
||||
|
||||
## 设计特点
|
||||
|
||||
### 1. 简单轻量
|
||||
|
||||
- 支持多种注册方式:普通注册、单例注册、多实例注册
|
||||
- 基于字典和哈希集实现,性能高效
|
||||
- 无复杂的依赖解析逻辑
|
||||
|
||||
### 2. 手动注册
|
||||
|
||||
- 需要显式注册每个组件
|
||||
- 不支持自动依赖注入
|
||||
- 完全可控的组件生命周期
|
||||
|
||||
### 3. 多实例支持
|
||||
|
||||
- 每个类型可以注册多个实例
|
||||
- 提供 [GetAll](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L244-L261) 方法获取所有实例
|
||||
- 提供 [Get](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L208-L225) 方法获取单个实例
|
||||
|
||||
### 4. 类型安全
|
||||
|
||||
- 基于泛型,编译时类型检查
|
||||
- 避免字符串键导致的错误
|
||||
- IDE 友好,支持自动补全
|
||||
|
||||
### 5. 线程安全
|
||||
|
||||
- 使用读写锁确保多线程环境下的安全操作
|
||||
- 读操作可以并发执行
|
||||
- 写操作独占锁,防止并发修改冲突
|
||||
|
||||
### 6. 容器冻结
|
||||
|
||||
- 提供 [Freeze](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L359-L372) 方法,防止进一步修改容器内容
|
||||
- 防止在初始化后意外修改注册内容
|
||||
|
||||
## 与其他 IoC 容器的区别
|
||||
|
||||
### 本框架的 IocContainer
|
||||
|
||||
```csharp
|
||||
// 简单直接
|
||||
var container = new IocContainer();
|
||||
container.Register(new MyService());
|
||||
var service = container.Get<MyService>();
|
||||
```
|
||||
|
||||
**特点:**
|
||||
|
||||
- ✅ 简单易用
|
||||
- ✅ 性能高
|
||||
- ✅ 线程安全
|
||||
- ✅ 支持多实例
|
||||
- ❌ 不支持构造函数注入
|
||||
- ❌ 不支持自动解析依赖
|
||||
- ❌ 不支持生命周期管理(Transient/Scoped/Singleton)
|
||||
|
||||
### 完整的 IoC 框架(如 Autofac、Zenject)
|
||||
|
||||
```csharp
|
||||
// 复杂但功能强大
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<MyService>().As<IMyService>().SingleInstance();
|
||||
builder.RegisterType<MyController>().WithParameter("config", config);
|
||||
var container = builder.Build();
|
||||
|
||||
// 自动解析依赖
|
||||
var controller = container.Resolve<MyController>();
|
||||
```
|
||||
|
||||
**特点:**
|
||||
|
||||
- ✅ 自动依赖注入
|
||||
- ✅ 生命周期管理
|
||||
- ✅ 复杂场景支持
|
||||
- ❌ 学习成本高
|
||||
- ❌ 性能开销大
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 在架构初始化时注册
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 按顺序注册组件
|
||||
// 1. 工具类(无依赖)
|
||||
RegisterUtility(new TimeUtility());
|
||||
RegisterUtility(new StorageUtility());
|
||||
|
||||
// 2. 模型(可能依赖工具)
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new GameModel());
|
||||
|
||||
// 3. 系统(可能依赖模型和工具)
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterSystem(new SaveSystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用接口类型注册
|
||||
|
||||
```csharp
|
||||
// ❌ 不推荐:直接使用实现类
|
||||
RegisterSystem(new ConcreteSystem());
|
||||
var system = GetSystem<ConcreteSystem>();
|
||||
|
||||
// ✅ 推荐:使用接口
|
||||
RegisterSystem<IGameSystem>(new ConcreteSystem());
|
||||
var system = GetSystem<IGameSystem>();
|
||||
```
|
||||
|
||||
### 3. 避免运行时频繁注册
|
||||
|
||||
```csharp
|
||||
// ❌ 不好:游戏运行时频繁注册
|
||||
void Update()
|
||||
{
|
||||
RegisterService(new TempService()); // 每帧创建
|
||||
}
|
||||
|
||||
// ✅ 好:在初始化时一次性注册
|
||||
protected override void Init()
|
||||
{
|
||||
RegisterService(new PersistentService());
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 检查 null 返回值
|
||||
|
||||
```csharp
|
||||
// 获取可能不存在的服务
|
||||
var service = container.Get<IOptionalService>();
|
||||
if (service != null)
|
||||
{
|
||||
service.DoSomething();
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Service not registered!");
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 合理使用容器冻结
|
||||
|
||||
```csharp
|
||||
// 在架构初始化完成后冻结容器,防止意外修改
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册所有组件
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new GameSystem());
|
||||
// ...
|
||||
|
||||
// 冻结容器
|
||||
Container.Freeze(); // 此后无法再注册新组件
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全操作**:容器内部使用读写锁确保线程安全,无需额外同步
|
||||
|
||||
2. **容器冻结**:一旦调用 [Freeze](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L359-L372)
|
||||
方法,将不能再注册新实例
|
||||
|
||||
3. **单例注册限制
|
||||
**:[RegisterSingleton](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L84-L106)
|
||||
方法确保一个类型只能有一个实例,重复注册会抛出异常
|
||||
|
||||
4. **内存管理**:容器持有的实例不会自动释放,需要注意内存泄漏问题
|
||||
|
||||
5. **注册顺序**:组件的依赖关系需要手动保证,先注册被依赖的组件
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 使用 IoC 容器管理所有组件
|
||||
- [`model`](./model.md) - Model 通过 IoC 容器注册和获取
|
||||
- [`system`](./system.md) - System 通过 IoC 容器注册和获取
|
||||
- [`utility`](./utility.md) - Utility 通过 IoC 容器注册和获取
|
||||
364
docs/core/logging.md
Normal file
364
docs/core/logging.md
Normal file
@ -0,0 +1,364 @@
|
||||
# Logging 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Logging 包提供了灵活的日志系统,支持多级别日志记录。默认日志级别为 `Info`,确保框架的关键操作都能被记录下来。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [ILogger](ILogger.cs)
|
||||
|
||||
日志记录器接口,定义了日志记录的基本功能。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
// 日志级别检查
|
||||
bool IsTraceEnabled();
|
||||
bool IsDebugEnabled();
|
||||
bool IsInfoEnabled();
|
||||
bool IsWarnEnabled();
|
||||
bool IsErrorEnabled();
|
||||
bool IsFatalEnabled();
|
||||
|
||||
// 记录日志
|
||||
void Trace(string msg);
|
||||
void Trace(string format, object arg);
|
||||
void Trace(string format, object arg1, object arg2);
|
||||
void Trace(string format, params object[] arguments);
|
||||
void Trace(string msg, Exception t);
|
||||
|
||||
void Debug(string msg);
|
||||
void Debug(string format, object arg);
|
||||
void Debug(string format, object arg1, object arg2);
|
||||
void Debug(string format, params object[] arguments);
|
||||
void Debug(string msg, Exception t);
|
||||
|
||||
void Info(string msg);
|
||||
void Info(string format, object arg);
|
||||
void Info(string format, object arg1, object arg2);
|
||||
void Info(string format, params object[] arguments);
|
||||
void Info(string msg, Exception t);
|
||||
|
||||
void Warn(string msg);
|
||||
void Warn(string format, object arg);
|
||||
void Warn(string format, object arg1, object arg2);
|
||||
void Warn(string format, params object[] arguments);
|
||||
void Warn(string msg, Exception t);
|
||||
|
||||
void Error(string msg);
|
||||
void Error(string format, object arg);
|
||||
void Error(string format, object arg1, object arg2);
|
||||
void Error(string format, params object[] arguments);
|
||||
void Error(string msg, Exception t);
|
||||
|
||||
void Fatal(string msg);
|
||||
void Fatal(string format, object arg);
|
||||
void Fatal(string format, object arg1, object arg2);
|
||||
void Fatal(string format, params object[] arguments);
|
||||
void Fatal(string msg, Exception t);
|
||||
|
||||
// 获取日志器名称
|
||||
string Name();
|
||||
```
|
||||
|
||||
### [ILoggerFactory](ILoggerFactory.cs)
|
||||
|
||||
日志工厂接口,用于创建日志记录器实例。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info);
|
||||
```
|
||||
|
||||
### [ILoggerFactoryProvider](ILoggerFactoryProvider.cs)
|
||||
|
||||
日志工厂提供程序接口,用于获取日志工厂。
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
ILoggerFactory GetLoggerFactory();
|
||||
ILogger CreateLogger(string name);
|
||||
```
|
||||
|
||||
### [LogLevel](LogLevel.cs)
|
||||
|
||||
日志级别枚举。
|
||||
|
||||
```csharp
|
||||
public enum LogLevel
|
||||
{
|
||||
Trace = 0, // 最详细的跟踪信息
|
||||
Debug = 1, // 调试信息
|
||||
Info = 2, // 一般信息(默认级别)
|
||||
Warning = 3, // 警告信息
|
||||
Error = 4, // 错误信息
|
||||
Fatal = 5 // 致命错误
|
||||
}
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [AbstractLogger](AbstractLogger.cs)
|
||||
|
||||
抽象日志基类,封装了日志级别判断、格式化与异常处理逻辑。平台日志器只需实现 `Write` 方法即可。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class CustomLogger : AbstractLogger
|
||||
{
|
||||
public CustomLogger(string? name = null, LogLevel minLevel = LogLevel.Info)
|
||||
: base(name, minLevel)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
// 自定义日志输出逻辑
|
||||
var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
|
||||
if (exception != null)
|
||||
logMessage += $"\n{exception}";
|
||||
|
||||
Console.WriteLine(logMessage);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### [ConsoleLogger](ConsoleLogger.cs)
|
||||
|
||||
控制台日志记录器实现,支持彩色输出。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建控制台日志记录器
|
||||
var logger = new ConsoleLogger("MyLogger", LogLevel.Debug);
|
||||
|
||||
// 记录不同级别的日志
|
||||
logger.Info("应用程序启动");
|
||||
logger.Debug("调试信息");
|
||||
logger.Warn("警告信息");
|
||||
logger.Error("错误信息");
|
||||
logger.Fatal("致命错误");
|
||||
```
|
||||
|
||||
**输出格式:**
|
||||
|
||||
```
|
||||
[2025-01-09 01:40:00.000] INFO [MyLogger] 应用程序启动
|
||||
[2025-01-09 01:40:01.000] DEBUG [MyLogger] 调试信息
|
||||
[2025-01-09 01:40:02.000] WARN [MyLogger] 警告信息
|
||||
```
|
||||
|
||||
**日志级别颜色:**
|
||||
|
||||
- **Trace**: 深灰色
|
||||
- **Debug**: 青色
|
||||
- **Info**: 白色
|
||||
- **Warning**: 黄色
|
||||
- **Error**: 红色
|
||||
- **Fatal**: 洋红色
|
||||
|
||||
**构造函数参数:**
|
||||
|
||||
- `name`:日志器名称,默认为 "ROOT"
|
||||
- `minLevel`:最低日志级别,默认为 LogLevel.Info
|
||||
- `writer`:TextWriter 输出流,默认为 Console.Out
|
||||
- `useColors`:是否使用颜色,默认为 true(仅在输出到控制台时生效)
|
||||
|
||||
### [ConsoleLoggerFactory](ConsoleLoggerFactory.cs)
|
||||
|
||||
控制台日志工厂,用于创建控制台日志记录器实例。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var factory = new ConsoleLoggerFactory();
|
||||
var logger = factory.GetLogger("MyModule", LogLevel.Debug);
|
||||
logger.Info("日志记录器创建成功");
|
||||
```
|
||||
|
||||
### [ConsoleLoggerFactoryProvider](ConsoleLoggerFactoryProvider.cs)
|
||||
|
||||
控制台日志工厂提供程序实现。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var provider = new ConsoleLoggerFactoryProvider();
|
||||
provider.MinLevel = LogLevel.Debug; // 设置最低日志级别
|
||||
var logger = provider.CreateLogger("MyApp");
|
||||
logger.Info("应用程序启动");
|
||||
```
|
||||
|
||||
### [LoggerFactoryResolver](LoggerFactoryResolver.cs)
|
||||
|
||||
日志工厂提供程序解析器,用于管理和提供日志工厂提供程序实例。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 设置日志工厂提供程序
|
||||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
|
||||
|
||||
// 设置最小日志级别
|
||||
LoggerFactoryResolver.MinLevel = LogLevel.Debug;
|
||||
|
||||
// 获取日志记录器
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("MyApp");
|
||||
logger.Info("应用程序启动");
|
||||
```
|
||||
|
||||
## 在架构中使用日志
|
||||
|
||||
### 1. 在 Architecture 中使用
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("GameArchitecture");
|
||||
logger.Info("游戏架构初始化开始");
|
||||
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new GameSystem());
|
||||
|
||||
logger.Info("游戏架构初始化完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在 System 中使用
|
||||
|
||||
```csharp
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("CombatSystem");
|
||||
logger.Info("战斗系统初始化完成");
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("CombatSystem");
|
||||
logger.Info("战斗系统已销毁");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在 Model 中使用
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("PlayerModel");
|
||||
logger.Info("玩家模型初始化完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 自定义日志级别
|
||||
|
||||
```csharp
|
||||
public class DebugLogger : AbstractLogger
|
||||
{
|
||||
public DebugLogger() : base("Debug", LogLevel.Debug)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
// 只输出调试及更高级别的日志
|
||||
if (level >= LogLevel.Debug)
|
||||
{
|
||||
Console.WriteLine($"[{level}] {message}");
|
||||
if (exception != null)
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 日志级别说明
|
||||
|
||||
| 级别 | 说明 | 使用场景 |
|
||||
|-------------|----------|-------------------|
|
||||
| **Trace** | 最详细的跟踪信息 | 调试复杂的执行流程,记录函数调用等 |
|
||||
| **Debug** | 调试信息 | 开发阶段,记录变量值、流程分支等 |
|
||||
| **Info** | 一般信息 | 记录重要的业务流程和系统状态 |
|
||||
| **Warning** | 警告信息 | 可能的问题但不中断程序执行 |
|
||||
| **Error** | 错误信息 | 影响功能但不致命的问题 |
|
||||
| **Fatal** | 致命错误 | 导致程序无法继续运行的严重错误 |
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用合适的日志级别**:
|
||||
- 使用 `Info` 记录重要业务流程
|
||||
- 使用 `Debug` 记录调试信息
|
||||
- 使用 `Warning` 记录异常情况
|
||||
- 使用 `Error` 记录错误但不影响程序运行
|
||||
- 使用 `Fatal` 记录严重错误
|
||||
|
||||
2. **提供上下文信息**:
|
||||
```csharp
|
||||
logger.Info($"用户登录成功: UserId={userId}, UserName={userName}");
|
||||
```
|
||||
|
||||
3. **异常日志记录**:
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
// 业务逻辑
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error("数据库操作失败", ex);
|
||||
}
|
||||
```
|
||||
|
||||
4. **分类使用日志**:
|
||||
```csharp
|
||||
var dbLogger = LoggerFactoryResolver.Provider.CreateLogger("Database");
|
||||
var netLogger = LoggerFactoryResolver.Provider.CreateLogger("Network");
|
||||
|
||||
dbLogger.Info("查询用户数据");
|
||||
netLogger.Debug("发送HTTP请求");
|
||||
```
|
||||
|
||||
5. **在框架组件中合理使用日志**:
|
||||
```csharp
|
||||
// 在系统初始化时记录
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger("System");
|
||||
logger.Info("系统初始化完成");
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **日志级别检查**:
|
||||
- 每个日志方法都会自动检查日志级别
|
||||
- 如果当前级别低于最小级别,不会输出日志
|
||||
|
||||
2. **格式化参数**:
|
||||
- 支持字符串格式化参数
|
||||
- 支持异常信息传递
|
||||
|
||||
3. **ConsoleLogger 的额外参数**:
|
||||
- ConsoleLogger 现在支持自定义TextWriter输出流
|
||||
- 支持禁用颜色输出的功能(useColors参数)
|
||||
|
||||
## 相关包
|
||||
|
||||
- [architecture](./architecture.md) - 架构核心,使用日志系统记录生命周期事件
|
||||
- [property](./property.md) - 可绑定属性基于事件系统实现
|
||||
- [extensions](./extensions.md) - 提供便捷的扩展方法
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
184
docs/core/model.md
Normal file
184
docs/core/model.md
Normal file
@ -0,0 +1,184 @@
|
||||
# Model 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Model 包定义了数据模型层的接口和基类。Model 是 MVC 架构中的 M 层,负责管理应用程序的数据和状态。Model
|
||||
层应该只包含数据和简单的数据逻辑,不包含复杂的业务逻辑。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IModel`](IModel.cs)
|
||||
|
||||
模型接口,定义了模型的基本行为和功能。
|
||||
|
||||
**继承的能力接口:**
|
||||
|
||||
- [`IContextAware`](../rule/IContextAware.cs) - 上下文感知接口
|
||||
- [`ILogAware`](../rule/ILogAware.cs) - 日志感知接口
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```csharp
|
||||
void Init(); // 初始化模型
|
||||
void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
|
||||
```
|
||||
|
||||
### [`ICanGetModel`](ICanGetModel.cs)
|
||||
|
||||
标记接口,表示实现者可以获取模型。继承自 [`IBelongToArchitecture`](../rule/IBelongToArchitecture.cs)。
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractModel`](AbstractModel.cs)
|
||||
|
||||
抽象模型基类,实现IModel接口,提供模型的基础实现。该类继承自[
|
||||
`ContextAwareBase`](file:///d:/Project/Rider/GFramework/GFramework.Core/rule/ContextAwareBase.cs#L11-L37),提供了上下文感知能力。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// 使用 BindableProperty 定义可监听的属性
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听生命值变化
|
||||
Health.Register(newHealth =>
|
||||
{
|
||||
if (newHealth <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.Initializing:
|
||||
// 架构初始化阶段的处理
|
||||
break;
|
||||
case ArchitecturePhase.Ready:
|
||||
// 架构就绪阶段的处理
|
||||
break;
|
||||
// ... 其他阶段处理
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Model 的职责
|
||||
|
||||
### ✅ 应该做的事
|
||||
|
||||
1. **存储数据和状态**
|
||||
2. **提供数据访问接口**
|
||||
3. **监听自身属性变化并做出响应**
|
||||
4. **发送数据变化事件**
|
||||
5. **处理架构生命周期事件**
|
||||
|
||||
### ❌ 不应该做的事
|
||||
|
||||
1. **不包含复杂业务逻辑** - 业务逻辑应该在 System 中
|
||||
2. **不直接依赖其他 Model** - 通过 Command 协调
|
||||
3. **不包含 UI 逻辑** - UI 逻辑应该在 Controller 中
|
||||
|
||||
## 常见 Model 示例
|
||||
|
||||
### 玩家数据模型
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<string> PlayerId { get; } = new("");
|
||||
public BindableProperty<string> PlayerName { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
public BindableProperty<int> Gold { get; } = new(0);
|
||||
|
||||
public Vector3 Position { get; set; }
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
Health.Register(hp =>
|
||||
{
|
||||
if (hp <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.Ready:
|
||||
// 模型准备好后的处理
|
||||
_log?.Log("PlayerModel is ready.");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 游戏状态模型
|
||||
|
||||
```csharp
|
||||
public class GameModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<GameState> State { get; } = new(GameState.Menu);
|
||||
public BindableProperty<int> Score { get; } = new(0);
|
||||
public BindableProperty<int> HighScore { get; } = new(0);
|
||||
public BindableProperty<int> CurrentLevel { get; } = new(1);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
Score.Register(newScore =>
|
||||
{
|
||||
if (newScore > HighScore.Value)
|
||||
{
|
||||
HighScore.Value = newScore;
|
||||
this.SendEvent(new NewHighScoreEvent { Score = newScore });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.ShuttingDown:
|
||||
// 游戏模型清理工作
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用 BindableProperty 存储需要监听的数据**
|
||||
2. **Model 之间不要直接调用,通过 Command/System 协调**
|
||||
3. **复杂计算和业务逻辑放在 System 中**
|
||||
4. **使用事件通知数据的重要变化**
|
||||
5. **保持 Model 简单纯粹,只做数据管理**
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 提供 Model 的注册和获取
|
||||
- [`property`](./property.md) - BindableProperty 用于定义可监听属性
|
||||
- [`events`](./events.md) - Model 发送事件通知变化
|
||||
- [`utility`](./utility.md) - Model 可以使用工具类
|
||||
- [`extensions`](./extensions.md) - 提供 GetModel 等扩展方法
|
||||
951
docs/core/pool.md
Normal file
951
docs/core/pool.md
Normal file
@ -0,0 +1,951 @@
|
||||
# 对象池系统 (Object Pool System)
|
||||
|
||||
## 概述
|
||||
|
||||
GFramework 的对象池系统是一个高效的内存管理机制,旨在减少垃圾回收(GC)压力,通过复用对象实例来提高应用程序性能。该系统实现了对象的创建、获取、释放和销毁的完整生命周期管理。
|
||||
|
||||
**核心优势:**
|
||||
|
||||
- **减少 GC 压力**:复用对象实例,避免频繁的内存分配和回收
|
||||
- **提高性能**:避免重复创建开销大的对象
|
||||
- **灵活管理**:支持多个独立的对象池,按需分类管理
|
||||
- **自动生命周期**:与 System 生命周期集成,自动管理对象销毁
|
||||
- **类型安全**:基于泛型实现,编译时类型检查
|
||||
|
||||
## 核心组件
|
||||
|
||||
### IPoolableObject 接口
|
||||
|
||||
定义了可池化对象的行为规范,所有需要池化的对象都必须实现此接口。
|
||||
|
||||
```csharp
|
||||
public interface IPoolableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 当对象从池中获取时调用,用于初始化或重置对象状态
|
||||
/// </summary>
|
||||
void OnAcquire();
|
||||
|
||||
/// <summary>
|
||||
/// 当对象被放回池中时调用,用于清理对象状态
|
||||
/// </summary>
|
||||
void OnRelease();
|
||||
|
||||
/// <summary>
|
||||
/// 当对象池被销毁时调用,用于执行最终清理
|
||||
/// </summary>
|
||||
void OnPoolDestroy();
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期:**
|
||||
|
||||
```
|
||||
创建 → Acquire(从池取出)→ 使用 → Release(放回池)→ 可再次 Acquire
|
||||
↓
|
||||
Pool Destroy → OnPoolDestroy → 销毁
|
||||
```
|
||||
|
||||
### IObjectPoolSystem 接口
|
||||
|
||||
定义了对象池系统的基本操作接口。
|
||||
|
||||
```csharp
|
||||
public interface IObjectPoolSystem<TKey, TObject>
|
||||
where TObject : IPoolableObject
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// 从指定键的对象池中获取一个对象
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键值</param>
|
||||
/// <returns>获取到的对象实例</returns>
|
||||
TObject Acquire(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// 将对象释放回指定键的对象池中
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键值</param>
|
||||
/// <param name="obj">需要释放的对象</param>
|
||||
void Release(TKey key, TObject obj);
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有对象池,销毁所有池中的对象
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
```
|
||||
|
||||
### AbstractObjectPoolSystem 抽象类
|
||||
|
||||
实现了 `IObjectPoolSystem` 接口的具体逻辑,提供了对象池管理的完整实现。
|
||||
|
||||
**核心特性:**
|
||||
|
||||
- 使用字典存储多个对象池,以键区分不同的对象池
|
||||
- 使用栈(Stack)存储池中的对象,实现 LIFO(后进先出)管理
|
||||
- 提供获取和释放对象的方法
|
||||
- 通过抽象方法 `Create` 让子类决定如何创建对象
|
||||
- 在系统销毁时自动清理所有对象池
|
||||
|
||||
**内部实现:**
|
||||
|
||||
```csharp
|
||||
public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
: AbstractSystem, IObjectPoolSystem<TKey, TObject>
|
||||
where TObject : IPoolableObject
|
||||
where TKey : notnull
|
||||
{
|
||||
// 存储对象池的字典,键为池标识,值为对应类型的对象栈
|
||||
protected readonly Dictionary<TKey, Stack<TObject>> Pools = new();
|
||||
|
||||
// 获取对象
|
||||
public TObject Acquire(TKey key)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var pool))
|
||||
{
|
||||
pool = new Stack<TObject>();
|
||||
Pools[key] = pool;
|
||||
}
|
||||
|
||||
var obj = pool.Count > 0
|
||||
? pool.Pop() // 从池中取出
|
||||
: Create(key); // 创建新对象
|
||||
|
||||
obj.OnAcquire(); // 调用对象的获取钩子
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 释放对象
|
||||
public void Release(TKey key, TObject obj)
|
||||
{
|
||||
obj.OnRelease(); // 调用对象的释放钩子
|
||||
|
||||
if (!Pools.TryGetValue(key, out var pool))
|
||||
{
|
||||
pool = new Stack<TObject>();
|
||||
Pools[key] = pool;
|
||||
}
|
||||
|
||||
pool.Push(obj); // 放回池中
|
||||
}
|
||||
|
||||
// 清空所有对象池
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var obj in Pools.Values.SelectMany(pool => pool))
|
||||
{
|
||||
obj.OnPoolDestroy(); // 调用对象的销毁钩子
|
||||
}
|
||||
|
||||
Pools.Clear();
|
||||
}
|
||||
|
||||
// 子类实现:创建新对象
|
||||
protected abstract TObject Create(TKey key);
|
||||
|
||||
// 系统销毁时自动清空对象池
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义池化对象
|
||||
|
||||
首先,创建一个实现 `IPoolableObject` 接口的类。
|
||||
|
||||
```csharp
|
||||
public class Bullet : IPoolableObject
|
||||
{
|
||||
public int Damage { get; private set; }
|
||||
public float Speed { get; private set; }
|
||||
public Vector3 Position { get; private set; }
|
||||
public Vector3 Direction { get; private set; }
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public void OnAcquire()
|
||||
{
|
||||
// 从池中获取时调用,初始化对象状态
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
// 放回池中时调用,清理对象状态
|
||||
IsActive = false;
|
||||
Damage = 0;
|
||||
Speed = 0;
|
||||
Position = Vector3.Zero;
|
||||
Direction = Vector3.Zero;
|
||||
}
|
||||
|
||||
public void OnPoolDestroy()
|
||||
{
|
||||
// 对象池销毁时调用,执行最终清理
|
||||
// 可以在这里释放非托管资源
|
||||
}
|
||||
|
||||
// 设置子弹属性的方法
|
||||
public void Setup(int damage, float speed, Vector3 position, Vector3 direction)
|
||||
{
|
||||
Damage = damage;
|
||||
Speed = speed;
|
||||
Position = position;
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
// 更新子弹逻辑
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
Position += Direction * Speed * deltaTime;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 实现对象池系统
|
||||
|
||||
继承 `AbstractObjectPoolSystem<TKey, TObject>` 并实现 `Create` 方法。
|
||||
|
||||
```csharp
|
||||
public class BulletPoolSystem : AbstractObjectPoolSystem<string, Bullet>
|
||||
{
|
||||
protected override Bullet Create(string key)
|
||||
{
|
||||
// 根据键值创建不同类型的子弹
|
||||
return key switch
|
||||
{
|
||||
"standard" => new Bullet(),
|
||||
"heavy" => new Bullet(),
|
||||
"explosive" => new Bullet(),
|
||||
_ => new Bullet()
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 可以预先创建一些对象放入池中
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var bullet = Create("standard");
|
||||
bullet.OnAcquire();
|
||||
bullet.OnRelease();
|
||||
Release("standard", bullet);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在架构中注册对象池系统
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册其他组件
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new CombatSystem());
|
||||
|
||||
// 注册对象池系统
|
||||
RegisterSystem(new BulletPoolSystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 使用对象池
|
||||
|
||||
```csharp
|
||||
public class ShootingSystem : AbstractSystem
|
||||
{
|
||||
private BulletPoolSystem _bulletPool;
|
||||
private List<Bullet> _activeBullets = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_bulletPool = this.GetSystem<BulletPoolSystem>();
|
||||
this.RegisterEvent<ShootEvent>(OnShoot);
|
||||
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||
}
|
||||
|
||||
private void OnShoot(ShootEvent e)
|
||||
{
|
||||
// 从对象池获取子弹
|
||||
var bullet = _bulletPool.Acquire("standard");
|
||||
|
||||
// 设置子弹属性
|
||||
bullet.Setup(
|
||||
damage: e.Damage,
|
||||
speed: 10.0f,
|
||||
position: e.StartPosition,
|
||||
direction: e.Direction
|
||||
);
|
||||
|
||||
// 添加到活跃列表
|
||||
_activeBullets.Add(bullet);
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 更新所有活跃子弹
|
||||
for (int i = _activeBullets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var bullet = _activeBullets[i];
|
||||
bullet.Update(e.DeltaTime);
|
||||
|
||||
// 检查子弹是否需要销毁(例如:超出范围或击中目标)
|
||||
if (ShouldDestroyBullet(bullet))
|
||||
{
|
||||
// 放回对象池
|
||||
_bulletPool.Release("standard", bullet);
|
||||
_activeBullets.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldDestroyBullet(Bullet bullet)
|
||||
{
|
||||
// 简单示例:子弹超出一定范围则销毁
|
||||
return bullet.Position.Length() > 1000.0f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 1. 多键对象池管理
|
||||
|
||||
```csharp
|
||||
public class ParticlePoolSystem : AbstractObjectPoolSystem<string, Particle>
|
||||
{
|
||||
protected override Particle Create(string key)
|
||||
{
|
||||
// 根据键值创建不同类型的粒子效果
|
||||
return key switch
|
||||
{
|
||||
"explosion" => new Particle(explosionPrefab),
|
||||
"smoke" => new Particle(smokePrefab),
|
||||
"spark" => new Particle(sparkPrefab),
|
||||
_ => throw new ArgumentException($"Unknown particle type: {key}")
|
||||
};
|
||||
}
|
||||
|
||||
// 提供便捷方法
|
||||
public Particle SpawnExplosion(Vector3 position)
|
||||
{
|
||||
var particle = Acquire("explosion");
|
||||
particle.Position = position;
|
||||
particle.Play();
|
||||
return particle;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
public class EffectSystem : AbstractSystem
|
||||
{
|
||||
private ParticlePoolSystem _particlePool;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_particlePool = this.GetSystem<ParticlePoolSystem>();
|
||||
}
|
||||
|
||||
public void PlayExplosion(Vector3 position)
|
||||
{
|
||||
_particlePool.SpawnExplosion(position);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 动态对象池管理
|
||||
|
||||
```csharp
|
||||
public class EnemyPoolSystem : AbstractObjectPoolSystem<string, Enemy>
|
||||
{
|
||||
protected override Enemy Create(string key)
|
||||
{
|
||||
// 根据敌人类型创建不同的敌人
|
||||
var enemyPrefab = LoadEnemyPrefab(key);
|
||||
return new Enemy(enemyPrefab);
|
||||
}
|
||||
|
||||
// 动态注册新的敌人类型池
|
||||
public void RegisterEnemyType(string enemyType)
|
||||
{
|
||||
if (!Pools.ContainsKey(enemyType))
|
||||
{
|
||||
Pools[enemyType] = new Stack<Enemy>();
|
||||
|
||||
// 预热:预先创建几个敌人放入池中
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var enemy = Create(enemyType);
|
||||
enemy.OnAcquire();
|
||||
enemy.OnRelease();
|
||||
Release(enemyType, enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
public class EnemySpawnerSystem : AbstractSystem
|
||||
{
|
||||
private EnemyPoolSystem _enemyPool;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_enemyPool = this.GetSystem<EnemyPoolSystem>();
|
||||
|
||||
// 注册不同类型的敌人
|
||||
_enemyPool.RegisterEnemyType("goblin");
|
||||
_enemyPool.RegisterEnemyType("orc");
|
||||
_enemyPool.RegisterEnemyType("dragon");
|
||||
}
|
||||
|
||||
public void SpawnEnemy(string enemyType, Vector3 position)
|
||||
{
|
||||
var enemy = _enemyPool.Acquire(enemyType);
|
||||
enemy.Position = position;
|
||||
enemy.Activate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 对象池大小限制
|
||||
|
||||
```csharp
|
||||
public class LimitedBulletPoolSystem : AbstractObjectPoolSystem<string, Bullet>
|
||||
{
|
||||
private const int MaxPoolSize = 50;
|
||||
|
||||
protected override Bullet Create(string key)
|
||||
{
|
||||
return new Bullet();
|
||||
}
|
||||
|
||||
public new void Release(string key, Bullet obj)
|
||||
{
|
||||
// 检查对象池大小
|
||||
if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize)
|
||||
{
|
||||
// 池已满,不回收对象,让它被 GC 回收
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用基类的 Release 方法
|
||||
base.Release(key, obj);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 对象池统计和调试
|
||||
|
||||
```csharp
|
||||
public class DebuggablePoolSystem : AbstractObjectPoolSystem<string, PoolableObject>
|
||||
{
|
||||
public Dictionary<string, int> PoolSizes => Pools.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.Count
|
||||
);
|
||||
|
||||
public int TotalPooledObjects => Pools.Values.Sum(stack => stack.Count);
|
||||
|
||||
public void LogPoolStatus()
|
||||
{
|
||||
foreach (var (key, stack) in Pools)
|
||||
{
|
||||
Console.WriteLine($"Pool [{key}]: {stack.Count} objects");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Total pooled objects: {TotalPooledObjects}");
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
LogPoolStatus();
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 游戏对象池
|
||||
|
||||
**适用对象:**
|
||||
|
||||
- 子弹、箭矢、投射物
|
||||
- 敌人、NPC
|
||||
- 爆炸效果、粒子系统
|
||||
- UI 元素(提示、对话框)
|
||||
|
||||
**示例:子弹池**
|
||||
|
||||
```csharp
|
||||
// 定义子弹
|
||||
public class Bullet : IPoolableObject
|
||||
{
|
||||
public Vector3 Position { get; private set; }
|
||||
public Vector3 Velocity { get; private set; }
|
||||
public float Lifetime { get; private set; }
|
||||
|
||||
public void OnAcquire()
|
||||
{
|
||||
Lifetime = 5.0f; // 5秒后自动销毁
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
Position = Vector3.Zero;
|
||||
Velocity = Vector3.Zero;
|
||||
}
|
||||
|
||||
public void OnPoolDestroy() { }
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
Position += Velocity * deltaTime;
|
||||
Lifetime -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 子弹对象池
|
||||
public class BulletPoolSystem : AbstractObjectPoolSystem<string, Bullet>
|
||||
{
|
||||
protected override Bullet Create(string key) => new Bullet();
|
||||
}
|
||||
|
||||
// 射击系统
|
||||
public class ShootingSystem : AbstractSystem
|
||||
{
|
||||
private BulletPoolSystem _bulletPool;
|
||||
private List<Bullet> _activeBullets = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_bulletPool = this.GetSystem<BulletPoolSystem>();
|
||||
this.RegisterEvent<ShootEvent>(OnShoot);
|
||||
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||
}
|
||||
|
||||
private void OnShoot(ShootEvent e)
|
||||
{
|
||||
var bullet = _bulletPool.Acquire("normal");
|
||||
bullet.Position = e.StartPosition;
|
||||
bullet.Velocity = e.Direction * e.Speed;
|
||||
_activeBullets.Add(bullet);
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
for (int i = _activeBullets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var bullet = _activeBullets[i];
|
||||
bullet.Update(e.DeltaTime);
|
||||
|
||||
if (bullet.Lifetime <= 0)
|
||||
{
|
||||
_bulletPool.Release("normal", bullet);
|
||||
_activeBullets.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. UI 元素池
|
||||
|
||||
**适用对象:**
|
||||
|
||||
- 对话框
|
||||
- 提示框
|
||||
- 菜单项
|
||||
- 列表项
|
||||
|
||||
**示例:提示框池**
|
||||
|
||||
```csharp
|
||||
public class Tooltip : IPoolableObject
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public void OnAcquire()
|
||||
{
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
IsActive = false;
|
||||
Text = "";
|
||||
}
|
||||
|
||||
public void OnPoolDestroy() { }
|
||||
|
||||
public void Show(string text, Vector3 position)
|
||||
{
|
||||
Text = text;
|
||||
// 更新 UI 位置和内容
|
||||
}
|
||||
}
|
||||
|
||||
public class TooltipPoolSystem : AbstractObjectPoolSystem<string, Tooltip>
|
||||
{
|
||||
protected override Tooltip Create(string key) => new Tooltip();
|
||||
}
|
||||
|
||||
public class UISystem : AbstractSystem
|
||||
{
|
||||
private TooltipPoolSystem _tooltipPool;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_tooltipPool = this.GetSystem<TooltipPoolSystem>();
|
||||
}
|
||||
|
||||
public void ShowTooltip(string text, Vector3 position)
|
||||
{
|
||||
var tooltip = _tooltipPool.Acquire("default");
|
||||
tooltip.Show(text, position);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 网络消息对象池
|
||||
|
||||
**适用对象:**
|
||||
|
||||
- 网络包
|
||||
- 协议消息
|
||||
- 数据包
|
||||
|
||||
**示例:网络包池**
|
||||
|
||||
```csharp
|
||||
public class NetworkPacket : IPoolableObject
|
||||
{
|
||||
public byte[] Data { get; private set; }
|
||||
public int Length { get; private set; }
|
||||
|
||||
public void OnAcquire()
|
||||
{
|
||||
Data = Array.Empty<byte>();
|
||||
Length = 0;
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
// 清理敏感数据
|
||||
if (Data != null)
|
||||
{
|
||||
Array.Clear(Data, 0, Data.Length);
|
||||
}
|
||||
Length = 0;
|
||||
}
|
||||
|
||||
public void OnPoolDestroy() { }
|
||||
|
||||
public void SetData(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
Length = data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public class PacketPoolSystem : AbstractObjectPoolSystem<string, NetworkPacket>
|
||||
{
|
||||
protected override NetworkPacket Create(string key) => new NetworkPacket();
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 对象生命周期管理
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:确保所有对象都放回池中
|
||||
public class BulletSystem : AbstractSystem
|
||||
{
|
||||
private List<Bullet> _activeBullets = new();
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
// 系统销毁时,确保所有活跃子弹都放回池中
|
||||
foreach (var bullet in _activeBullets)
|
||||
{
|
||||
_bulletPool.Release("standard", bullet);
|
||||
}
|
||||
|
||||
_activeBullets.Clear();
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不好的做法:忘记放回对象,导致泄漏
|
||||
public class BadBulletSystem : AbstractSystem
|
||||
{
|
||||
private List<Bullet> _activeBullets = new();
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 子弹销毁时忘记放回池中
|
||||
if (bullet.Lifetime <= 0)
|
||||
{
|
||||
_activeBullets.RemoveAt(i);
|
||||
// 忘记调用 _bulletPool.Release(...)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 对象状态重置
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:在 OnRelease 中彻底重置对象状态
|
||||
public class Bullet : IPoolableObject
|
||||
{
|
||||
public int Damage { get; set; }
|
||||
public float Speed { get; set; }
|
||||
public List<string> Tags { get; set; }
|
||||
public Dictionary<string, object> Data { get; set; }
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
// 重置所有属性
|
||||
Damage = 0;
|
||||
Speed = 0;
|
||||
Tags?.Clear();
|
||||
Data?.Clear();
|
||||
|
||||
// 也可以设置为新实例(如果性能允许)
|
||||
Tags = new List<string>();
|
||||
Data = new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不好的做法:不完全重置状态
|
||||
public class BadBullet : IPoolableObject
|
||||
{
|
||||
public List<string> Tags = new List<string>();
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
// 只清空列表,但列表实例本身保留
|
||||
// 这可能导致问题:如果其他代码持有列表引用
|
||||
Tags.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 对象池预热
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:预先创建一些对象放入池中
|
||||
public class BulletPoolSystem : AbstractObjectPoolSystem<string, Bullet>
|
||||
{
|
||||
protected override Bullet Create(string key) => new Bullet();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 为常用的子弹类型预热对象池
|
||||
var commonTypes = new[] { "standard", "heavy", "sniper" };
|
||||
|
||||
foreach (var type in commonTypes)
|
||||
{
|
||||
// 预先创建 5 个对象
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var bullet = Create(type);
|
||||
bullet.OnAcquire();
|
||||
bullet.OnRelease();
|
||||
Release(type, bullet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 对象池大小管理
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:限制对象池大小,避免内存浪费
|
||||
public class BoundedPoolSystem : AbstractObjectPoolSystem<string, PooledObject>
|
||||
{
|
||||
private const int MaxPoolSize = 100;
|
||||
|
||||
public new void Release(string key, PooledObject obj)
|
||||
{
|
||||
if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize)
|
||||
{
|
||||
// 池已满,不回收对象
|
||||
return;
|
||||
}
|
||||
|
||||
base.Release(key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 好的做法:动态调整对象池大小
|
||||
public class AdaptivePoolSystem : AbstractObjectPoolSystem<string, PooledObject>
|
||||
{
|
||||
private Dictionary<string, int> _peakUsage = new();
|
||||
|
||||
public new void Release(string key, PooledObject obj)
|
||||
{
|
||||
// 记录峰值使用量
|
||||
if (Pools.TryGetValue(key, out var pool))
|
||||
{
|
||||
_peakUsage[key] = Math.Max(_peakUsage.GetValueOrDefault(key, 0), pool.Count);
|
||||
}
|
||||
|
||||
// 根据使用情况动态调整
|
||||
int maxAllowed = _peakUsage.GetValueOrDefault(key, 10) * 2;
|
||||
if (pool.Count >= maxAllowed)
|
||||
{
|
||||
return; // 不回收
|
||||
}
|
||||
|
||||
base.Release(key, obj);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 调试和监控
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:添加对象池统计功能
|
||||
public class MonitoredPoolSystem : AbstractObjectPoolSystem<string, PooledObject>
|
||||
{
|
||||
private Dictionary<string, int> _acquireCount = new();
|
||||
private Dictionary<string, int> _releaseCount = new();
|
||||
|
||||
public new PooledObject Acquire(string key)
|
||||
{
|
||||
_acquireCount[key] = _acquireCount.GetValueOrDefault(key, 0) + 1;
|
||||
return base.Acquire(key);
|
||||
}
|
||||
|
||||
public new void Release(string key, PooledObject obj)
|
||||
{
|
||||
_releaseCount[key] = _releaseCount.GetValueOrDefault(key, 0) + 1;
|
||||
base.Release(key, obj);
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
{
|
||||
foreach (var key in Pools.Keys)
|
||||
{
|
||||
int acquired = _acquireCount.GetValueOrDefault(key, 0);
|
||||
int released = _releaseCount.GetValueOrDefault(key, 0);
|
||||
int leaked = acquired - released;
|
||||
|
||||
Console.WriteLine($"Pool [{key}]:");
|
||||
Console.WriteLine($" Acquired: {acquired}");
|
||||
Console.WriteLine($" Released: {released}");
|
||||
Console.WriteLine($" Leaked: {leaked}");
|
||||
Console.WriteLine($" Pool size: {Pools[key].Count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 减少对象创建
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:对象池预热,避免运行时创建
|
||||
public class PreheatedPoolSystem : AbstractObjectPoolSystem<string, Bullet>
|
||||
{
|
||||
protected override Bullet Create(string key) => new Bullet();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 预热常用对象池
|
||||
PreheatPool("standard", 20);
|
||||
PreheatPool("heavy", 10);
|
||||
}
|
||||
|
||||
private void PreheatPool(string key, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var bullet = Create(key);
|
||||
bullet.OnAcquire();
|
||||
bullet.OnRelease();
|
||||
Release(key, bullet);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用值类型作为键
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:使用枚举或整数作为键,避免字符串比较
|
||||
public enum BulletType
|
||||
{
|
||||
Standard,
|
||||
Heavy,
|
||||
Explosive
|
||||
}
|
||||
|
||||
public class FastBulletPoolSystem : AbstractObjectPoolSystem<BulletType, Bullet>
|
||||
{
|
||||
protected override Bullet Create(BulletType key) => key switch
|
||||
{
|
||||
BulletType.Standard => new Bullet(),
|
||||
BulletType.Heavy => new Bullet(),
|
||||
BulletType.Explosive => new Bullet(),
|
||||
_ => throw new ArgumentException()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 批量操作
|
||||
|
||||
```csharp
|
||||
// ✅ 好的做法:批量获取和释放对象
|
||||
public class BatchPoolSystem : AbstractObjectPoolSystem<string, PooledObject>
|
||||
{
|
||||
public List<PooledObject> AcquireBatch(string key, int count)
|
||||
{
|
||||
var objects = new List<PooledObject>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
objects.Add(Acquire(key));
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
public void ReleaseBatch(string key, IEnumerable<PooledObject> objects)
|
||||
{
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
Release(key, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **对象状态管理**:确保 `OnRelease` 方法彻底重置对象状态,避免状态污染
|
||||
2. **对象引用**:不要在放回对象池后继续持有对象引用
|
||||
3. **线程安全**:对象池本身不是线程安全的,如需多线程访问,需要自行加锁
|
||||
4. **内存泄漏**:确保所有获取的对象最终都放回池中
|
||||
5. **对象池大小**:合理设置对象池大小,避免内存浪费或频繁创建
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`system`](./system.md) - 对象池系统继承自 AbstractSystem
|
||||
- [`architecture`](./architecture.md) - 在架构中注册对象池系统
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
341
docs/core/property.md
Normal file
341
docs/core/property.md
Normal file
@ -0,0 +1,341 @@
|
||||
# Property 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`IReadonlyBindableProperty<T>`](IReadonlyBindableProperty.cs)
|
||||
|
||||
只读可绑定属性接口,提供属性值的读取和变更监听功能。
|
||||
|
||||
**核心成员:**
|
||||
|
||||
```csharp
|
||||
// 获取属性值
|
||||
T Value { get; }
|
||||
|
||||
// 注册监听(不立即触发回调)
|
||||
IUnRegister Register(Action<T> onValueChanged);
|
||||
|
||||
// 注册监听并立即触发回调传递当前值
|
||||
IUnRegister RegisterWithInitValue(Action<T> action);
|
||||
|
||||
// 取消监听
|
||||
void UnRegister(Action<T> onValueChanged);
|
||||
```
|
||||
|
||||
### [`IBindableProperty<T>`](IBindableProperty.cs)
|
||||
|
||||
可绑定属性接口,继承自只读接口,增加了修改能力。
|
||||
|
||||
**核心成员:**
|
||||
|
||||
```csharp
|
||||
// 可读写的属性值
|
||||
new T Value { get; set; }
|
||||
|
||||
// 设置值但不触发事件
|
||||
void SetValueWithoutEvent(T newValue);
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`BindableProperty<T>`](BindableProperty.cs)
|
||||
|
||||
可绑定属性的完整实现。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
// 创建可绑定属性
|
||||
var health = new BindableProperty<int>(100);
|
||||
|
||||
// 监听值变化(不会立即触发)
|
||||
var unregister = health.Register(newValue =>
|
||||
{
|
||||
GD.Print($"Health changed to: {newValue}");
|
||||
});
|
||||
|
||||
// 设置值(会触发监听器)
|
||||
health.Value = 50; // 输出: Health changed to: 50
|
||||
|
||||
// 取消监听
|
||||
unregister.UnRegister();
|
||||
|
||||
// 设置值但不触发事件
|
||||
health.SetValueWithoutEvent(75);
|
||||
```
|
||||
|
||||
**高级功能:**
|
||||
|
||||
```csharp
|
||||
// 1. 注册并立即获得当前值
|
||||
health.RegisterWithInitValue(value =>
|
||||
{
|
||||
GD.Print($"Current health: {value}"); // 立即输出当前值
|
||||
// 后续值变化时也会调用
|
||||
});
|
||||
|
||||
// 2. 自定义比较器(静态方法)
|
||||
BindableProperty<int>.Comparer = (a, b) => Math.Abs(a - b) < 1;
|
||||
|
||||
// 3. 使用实例方法设置比较器
|
||||
var position = new BindableProperty<Vector3>(Vector3.Zero)
|
||||
.WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等
|
||||
```
|
||||
|
||||
### [`BindablePropertyUnRegister<T>`](BindablePropertyUnRegister.cs)
|
||||
|
||||
可绑定属性的注销器,负责清理监听。
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```csharp
|
||||
var unregister = health.Register(OnHealthChanged);
|
||||
// 当需要取消监听时
|
||||
unregister.UnRegister();
|
||||
```
|
||||
|
||||
## 在 Model 中使用
|
||||
|
||||
### 定义可绑定属性
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// 可读写属性
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
// 只读属性(外部只能读取和监听)
|
||||
public IReadonlyBindableProperty<int> ReadonlyHealth => Health;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 内部监听属性变化
|
||||
Health.Register(hp =>
|
||||
{
|
||||
if (hp <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 在 Controller 中监听
|
||||
|
||||
### UI 数据绑定
|
||||
|
||||
```csharp
|
||||
public partial class PlayerUI : Control, IController
|
||||
{
|
||||
[Export] private Label _healthLabel;
|
||||
[Export] private Label _nameLabel;
|
||||
|
||||
private IUnRegisterList _unregisterList = new UnRegisterList();
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 绑定生命值到UI(立即显示当前值)
|
||||
playerModel.Health
|
||||
.RegisterWithInitValue(health =>
|
||||
{
|
||||
_healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}";
|
||||
})
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
|
||||
// 绑定名称
|
||||
playerModel.Name
|
||||
.RegisterWithInitValue(name =>
|
||||
{
|
||||
_nameLabel.Text = name;
|
||||
})
|
||||
.AddToUnregisterList(_unregisterList);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_unregisterList.UnRegisterAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见使用模式
|
||||
|
||||
### 1. 双向绑定
|
||||
|
||||
```csharp
|
||||
// Model
|
||||
public class SettingsModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<float> MasterVolume { get; } = new(1.0f);
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
|
||||
// UI Controller
|
||||
public partial class VolumeSlider : HSlider, IController
|
||||
{
|
||||
private BindableProperty<float> _volumeProperty;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_volumeProperty = this.GetModel<SettingsModel>().MasterVolume;
|
||||
|
||||
// Model -> UI
|
||||
_volumeProperty.RegisterWithInitValue(vol => Value = vol)
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
|
||||
// UI -> Model
|
||||
ValueChanged += newValue => _volumeProperty.Value = (float)newValue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 计算属性
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
public BindableProperty<float> HealthPercent { get; } = new(1.0f);
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 自动计算百分比
|
||||
Action updatePercent = () =>
|
||||
{
|
||||
HealthPercent.Value = (float)Health.Value / MaxHealth.Value;
|
||||
};
|
||||
|
||||
Health.Register(_ => updatePercent());
|
||||
MaxHealth.Register(_ => updatePercent());
|
||||
|
||||
updatePercent(); // 初始计算
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 属性验证
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
private BindableProperty<int> _health = new(100);
|
||||
|
||||
public BindableProperty<int> Health
|
||||
{
|
||||
get => _health;
|
||||
set
|
||||
{
|
||||
// 限制范围
|
||||
var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value);
|
||||
_health.Value = clampedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public BindableProperty<int> MaxHealth { get; } = new(100);
|
||||
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 条件监听
|
||||
|
||||
```csharp
|
||||
public class CombatController : Node, IController
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 只在生命值低于30%时显示警告
|
||||
playerModel.Health.Register(hp =>
|
||||
{
|
||||
if (hp < playerModel.MaxHealth.Value * 0.3f)
|
||||
{
|
||||
ShowLowHealthWarning();
|
||||
}
|
||||
else
|
||||
{
|
||||
HideLowHealthWarning();
|
||||
}
|
||||
}).UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 避免频繁触发
|
||||
|
||||
```csharp
|
||||
// 使用 SetValueWithoutEvent 批量修改
|
||||
public void LoadPlayerData(SaveData data)
|
||||
{
|
||||
// 临时关闭事件
|
||||
Health.SetValueWithoutEvent(data.Health);
|
||||
Mana.SetValueWithoutEvent(data.Mana);
|
||||
Gold.SetValueWithoutEvent(data.Gold);
|
||||
|
||||
// 最后统一触发一次更新事件
|
||||
this.SendEvent(new PlayerDataLoadedEvent());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自定义比较器
|
||||
|
||||
```csharp
|
||||
// 避免浮点数精度问题导致的频繁触发
|
||||
var position = new BindableProperty<Vector3>()
|
||||
.WithComparer((a, b) => a.DistanceTo(b) < 0.001f);
|
||||
```
|
||||
|
||||
## 实现原理
|
||||
|
||||
### 值变化检测
|
||||
|
||||
```csharp
|
||||
// 使用 EqualityComparer<T>.Default 进行比较
|
||||
if (!EqualityComparer<T>.Default.Equals(value, MValue))
|
||||
{
|
||||
MValue = value;
|
||||
_mOnValueChanged?.Invoke(value);
|
||||
}
|
||||
```
|
||||
|
||||
### 事件触发机制
|
||||
|
||||
```csharp
|
||||
// 当值变化时触发所有注册的回调
|
||||
_mOnValueChanged?.Invoke(value);
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层
|
||||
2. **使用只读接口暴露** - 防止外部随意修改
|
||||
3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree
|
||||
4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值
|
||||
5. **避免循环依赖** - 属性监听器中修改其他属性要小心
|
||||
6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`model`](./model.md) - Model 中大量使用 BindableProperty
|
||||
- [`events`](./events.md) - BindableProperty 基于事件系统实现
|
||||
- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法
|
||||
|
||||
---
|
||||
|
||||
**许可证**: Apache 2.0
|
||||
447
docs/core/query.md
Normal file
447
docs/core/query.md
Normal file
@ -0,0 +1,447 @@
|
||||
# Query 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Query 包实现了 CQRS(命令查询职责分离)模式中的查询部分。Query 用于封装数据查询逻辑,与 Command 不同的是,Query
|
||||
有返回值且不应该修改系统状态。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### IQuery<TResult>
|
||||
|
||||
查询接口,定义了查询的基本契约。
|
||||
|
||||
**核心成员:**
|
||||
|
||||
```csharp
|
||||
TResult Do(); // 执行查询并返回结果
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractQuery<TInput, TResult>`](AbstractQuery.cs)
|
||||
|
||||
抽象查询基类,提供了查询的基础实现。它接受一个泛型输入参数 TInput,该参数必须实现 IQueryInput 接口。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwareBase, IQuery<TResult>
|
||||
where TInput : IQueryInput
|
||||
{
|
||||
public TResult Do() => OnDo(input); // 执行查询,传入输入参数
|
||||
protected abstract TResult OnDo(TInput input); // 子类实现查询逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### [`EmptyQueryInput`](EmptyQueryInput.cs)
|
||||
|
||||
空查询输入类,用于表示不需要任何输入参数的查询操作。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public sealed class EmptyQueryInput : IQueryInput
|
||||
{
|
||||
// 作为占位符使用,适用于那些不需要额外输入参数的查询场景
|
||||
}
|
||||
```
|
||||
|
||||
### [`QueryBus`](QueryBus.cs)
|
||||
|
||||
查询总线实现,负责执行查询并返回结果。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public sealed class QueryBus : IQueryBus
|
||||
{
|
||||
public TResult Send<TResult>(IQuery<TResult> query)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
return query.Do();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义查询
|
||||
|
||||
``csharp
|
||||
// 定义查询输入参数
|
||||
public record GetPlayerGoldQueryInput : IQueryInput;
|
||||
|
||||
// 查询玩家金币数量
|
||||
public class GetPlayerGoldQuery : AbstractQuery<GetPlayerGoldQueryInput, int>
|
||||
{
|
||||
public GetPlayerGoldQuery() : base(new EmptyQueryInput())
|
||||
{
|
||||
}
|
||||
|
||||
protected override int OnDo(GetPlayerGoldQueryInput input)
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Gold.Value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 查询玩家是否死亡
|
||||
public record IsPlayerDeadQueryInput : IQueryInput;
|
||||
|
||||
public class IsPlayerDeadQuery : AbstractQuery<IsPlayerDeadQueryInput, bool>
|
||||
{
|
||||
public IsPlayerDeadQuery() : base(new EmptyQueryInput())
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnDo(IsPlayerDeadQueryInput input)
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Health.Value <= 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 查询背包中指定物品的数量
|
||||
public record GetItemCountQueryInput(string ItemId) : IQueryInput;
|
||||
|
||||
public class GetItemCountQuery : AbstractQuery<GetItemCountQueryInput, int>
|
||||
{
|
||||
public GetItemCountQuery(string itemId) : base(new GetItemCountQueryInput(itemId))
|
||||
{
|
||||
}
|
||||
|
||||
protected override int OnDo(GetItemCountQueryInput input)
|
||||
{
|
||||
var inventory = this.GetModel<InventoryModel>();
|
||||
return inventory.GetItemCount(input.ItemId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 2. 发送查询(在 Controller 中)
|
||||
|
||||
``csharp
|
||||
public partial class ShopUI : Control, IController
|
||||
{
|
||||
[Export] private Button _buyButton;
|
||||
[Export] private int _itemPrice = 100;
|
||||
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_buyButton.Pressed += OnBuyButtonPressed;
|
||||
}
|
||||
|
||||
private void OnBuyButtonPressed()
|
||||
{
|
||||
// 查询玩家金币
|
||||
int playerGold = this.SendQuery(new GetPlayerGoldQuery());
|
||||
|
||||
if (playerGold >= _itemPrice)
|
||||
{
|
||||
// 发送购买命令
|
||||
this.SendCommand(new BuyItemCommand { ItemId = "sword_01" });
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("金币不足!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在 System 中使用
|
||||
|
||||
``csharp
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
|
||||
}
|
||||
|
||||
private void OnEnemyAttack(EnemyAttackEvent e)
|
||||
{
|
||||
// 查询玩家是否已经死亡
|
||||
bool isDead = this.SendQuery(new IsPlayerDeadQuery());
|
||||
|
||||
if (!isDead)
|
||||
{
|
||||
// 执行伤害逻辑
|
||||
this.SendCommand(new TakeDamageCommand { Damage = e.Damage });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 1. 带参数的复杂查询
|
||||
|
||||
``csharp
|
||||
// 查询指定范围内的敌人列表
|
||||
public class GetEnemiesInRangeQuery : AbstractQuery<List<Enemy>>
|
||||
{
|
||||
public Vector3 Center { get; set; }
|
||||
public float Radius { get; set; }
|
||||
|
||||
protected override List<Enemy> OnDo()
|
||||
{
|
||||
var enemySystem = this.GetSystem<EnemySpawnSystem>();
|
||||
return enemySystem.GetEnemiesInRange(Center, Radius);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
var enemies = this.SendQuery(new GetEnemiesInRangeQuery
|
||||
{
|
||||
Center = playerPosition,
|
||||
Radius = 10.0f
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 组合查询
|
||||
|
||||
``csharp
|
||||
// 查询玩家是否可以使用技能
|
||||
public class CanUseSkillQuery : AbstractQuery<bool>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override bool OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var skillModel = this.GetModel<SkillModel>();
|
||||
|
||||
// 查询技能消耗
|
||||
var skillCost = this.SendQuery(new GetSkillCostQuery { SkillId = SkillId });
|
||||
|
||||
// 检查是否满足条件
|
||||
return playerModel.Mana.Value >= skillCost.ManaCost
|
||||
&& !this.SendQuery(new IsSkillOnCooldownQuery { SkillId = SkillId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class GetSkillCostQuery : AbstractQuery<SkillCost>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override SkillCost OnDo()
|
||||
{
|
||||
return this.GetModel<SkillModel>().GetSkillCost(SkillId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class IsSkillOnCooldownQuery : AbstractQuery<bool>
|
||||
{
|
||||
public string SkillId { get; set; }
|
||||
|
||||
protected override bool OnDo()
|
||||
{
|
||||
return this.GetModel<SkillModel>().IsOnCooldown(SkillId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 3. 聚合数据查询
|
||||
|
||||
``csharp
|
||||
// 查询玩家战斗力
|
||||
public class GetPlayerPowerQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var equipmentModel = this.GetModel<EquipmentModel>();
|
||||
|
||||
int basePower = playerModel.Level.Value * 10;
|
||||
int equipmentPower = equipmentModel.GetTotalPower();
|
||||
int buffPower = this.SendQuery(new GetBuffPowerQuery());
|
||||
|
||||
return basePower + equipmentPower + buffPower;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询玩家详细信息(用于UI显示)
|
||||
public class GetPlayerInfoQuery : AbstractQuery<PlayerInfo>
|
||||
{
|
||||
protected override PlayerInfo OnDo()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
return new PlayerInfo
|
||||
{
|
||||
Name = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Health = playerModel.Health.Value,
|
||||
MaxHealth = playerModel.MaxHealth.Value,
|
||||
Gold = this.SendQuery(new GetPlayerGoldQuery()),
|
||||
Power = this.SendQuery(new GetPlayerPowerQuery())
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 跨 System 查询
|
||||
|
||||
``csharp
|
||||
// 在 AI System 中查询玩家状态
|
||||
public class EnemyAISystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit() { }
|
||||
|
||||
public void UpdateEnemyBehavior(Enemy enemy)
|
||||
{
|
||||
// 查询玩家位置
|
||||
var playerPos = this.SendQuery(new GetPlayerPositionQuery());
|
||||
|
||||
// 查询玩家是否在攻击范围内
|
||||
bool inRange = this.SendQuery(new IsPlayerInRangeQuery
|
||||
{
|
||||
Position = enemy.Position,
|
||||
Range = enemy.AttackRange
|
||||
});
|
||||
|
||||
if (inRange)
|
||||
{
|
||||
// 查询是否可以攻击
|
||||
bool canAttack = this.SendQuery(new CanEnemyAttackQuery
|
||||
{
|
||||
EnemyId = enemy.Id
|
||||
});
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
this.SendCommand(new EnemyAttackCommand { EnemyId = enemy.Id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Command vs Query
|
||||
|
||||
### Command(命令)
|
||||
|
||||
- **用途**:修改系统状态
|
||||
- **返回值**:无返回值(void)
|
||||
- **示例**:购买物品、造成伤害、升级角色
|
||||
|
||||
### Query(查询)
|
||||
|
||||
- **用途**:读取数据,不修改状态
|
||||
- **返回值**:有返回值
|
||||
- **示例**:获取金币数量、检查技能冷却、查询玩家位置
|
||||
|
||||
``csharp
|
||||
// ❌ 错误:在 Query 中修改状态
|
||||
public class BadQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>();
|
||||
model.Gold.Value += 100; // 不应该在 Query 中修改数据!
|
||||
return model.Gold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:Query 只读取数据
|
||||
public class GoodQuery : AbstractQuery<int>
|
||||
{
|
||||
protected override int OnDo()
|
||||
{
|
||||
return this.GetModel<PlayerModel>().Gold.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修改数据应该使用 Command
|
||||
public class AddGoldCommand : AbstractCommand
|
||||
{
|
||||
public int Amount { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>();
|
||||
model.Gold.Value += Amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **查询只读取,不修改** - 保持 Query 的纯粹性
|
||||
2. **小而专注** - 每个 Query 只负责一个具体的查询任务
|
||||
3. **可组合** - 复杂查询可以通过组合简单查询实现
|
||||
4. **避免过度查询** - 如果需要频繁查询,考虑使用 BindableProperty
|
||||
5. **命名清晰** - Query 名称应该清楚表达查询意图(Get、Is、Can、Has等前缀)
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 缓存查询结果
|
||||
|
||||
``csharp
|
||||
// 在 Model 中缓存复杂计算
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
private int? _cachedPower;
|
||||
|
||||
public int GetPower()
|
||||
{
|
||||
if (_cachedPower == null)
|
||||
{
|
||||
_cachedPower = CalculatePower();
|
||||
}
|
||||
return _cachedPower.Value;
|
||||
}
|
||||
|
||||
private int CalculatePower()
|
||||
{
|
||||
// 复杂计算...
|
||||
return 100;
|
||||
}
|
||||
|
||||
public void InvalidatePowerCache()
|
||||
{
|
||||
_cachedPower = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 2. 批量查询
|
||||
|
||||
``csharp
|
||||
// 一次查询多个数据,而不是多次单独查询
|
||||
public class GetMultipleItemCountsQuery : AbstractQuery<Dictionary<string, int>>
|
||||
{
|
||||
public List<string> ItemIds { get; set; }
|
||||
|
||||
protected override Dictionary<string, int> OnDo()
|
||||
{
|
||||
var inventory = this.GetModel<InventoryModel>();
|
||||
return ItemIds.ToDictionary(id => id, id => inventory.GetItemCount(id));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`command`](./command.md) - CQRS 的命令部分
|
||||
- [`model`](./model.md) - Query 主要从 Model 获取数据
|
||||
- [`system`](./system.md) - System 中可以发送 Query
|
||||
- [`controller`](./controller.md) - Controller 中可以发送 Query
|
||||
- [`extensions`](./extensions.md) - 提供 SendQuery 扩展方法
|
||||
316
docs/core/rule.md
Normal file
316
docs/core/rule.md
Normal file
@ -0,0 +1,316 @@
|
||||
# Rule 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Rule 包定义了框架的核心规则接口,这些接口规定了框架各个组件之间的关系和约束。所有框架组件都需要遵循这些规则接口。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### IContextAware
|
||||
|
||||
标记接口,表示该类型可以感知架构上下文。
|
||||
|
||||
**接口定义:**
|
||||
|
||||
```csharp
|
||||
public interface IContextAware
|
||||
{
|
||||
void SetContext(IArchitectureContext context);
|
||||
IArchitectureContext GetContext();
|
||||
}
|
||||
```
|
||||
|
||||
**实现此接口的类型:**
|
||||
|
||||
- System
|
||||
- Query
|
||||
- Model
|
||||
- Command
|
||||
- 以及其他需要感知架构上下文的组件
|
||||
|
||||
**作用:**
|
||||
所有实现此接口的类型都能够获取其所属的架构上下文实例,从而访问架构提供的各种能力。
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`ContextAwareBase`](ContextAwareBase.cs)
|
||||
|
||||
上下文感知基类,实现了 IContextAware 接口,为需要感知架构上下文的类提供基础实现。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public abstract class ContextAwareBase : IContextAware
|
||||
{
|
||||
protected IArchitectureContext? Context { get; set; }
|
||||
|
||||
void IContextAware.SetContext(IArchitectureContext context)
|
||||
{
|
||||
Context = context;
|
||||
OnContextReady(); // 上下文准备好后调用此方法
|
||||
}
|
||||
|
||||
IArchitectureContext IContextAware.GetContext()
|
||||
{
|
||||
Context ??= GameContext.GetFirstArchitectureContext();
|
||||
return Context;
|
||||
}
|
||||
|
||||
protected virtual void OnContextReady() // 子类可以重写此方法进行初始化
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接口关系图
|
||||
|
||||
```
|
||||
IContextAware (上下文感知接口)
|
||||
↓ 被继承于
|
||||
├── AbstractSystem (抽象系统基类)
|
||||
├── AbstractQuery<TInput, TResult> (抽象查询基类)
|
||||
├── AbstractModel (抽象模型基类)
|
||||
└── AbstractCommand (抽象命令基类)
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. Component 继承 ContextAwareBase
|
||||
|
||||
```csharp
|
||||
// 组件通过继承 ContextAwareBase 获得架构上下文访问能力
|
||||
public partial class PlayerController : Node, IController
|
||||
{
|
||||
// 不再需要手动实现 IContextAware,基类已处理
|
||||
// 可以直接使用扩展方法
|
||||
public override void _Ready()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
this.SendCommand(new InitPlayerCommand());
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
GD.Print("Player died!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Command 继承 AbstractCommand (IContextAware)
|
||||
|
||||
```csharp
|
||||
// Command 继承 AbstractCommand,自动成为 IContextAware
|
||||
public class BuyItemCommand : AbstractCommand
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 框架或上下文系统会自动注入 IArchitectureContext
|
||||
// 所以这里可以直接使用 this.GetModel
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var shopModel = this.GetModel<ShopModel>();
|
||||
|
||||
int price = shopModel.GetItemPrice(ItemId);
|
||||
if (playerModel.Gold.Value >= price)
|
||||
{
|
||||
playerModel.Gold.Value -= price;
|
||||
this.SendCommand(new AddItemCommand { ItemId = ItemId });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 自定义组件遵循规则
|
||||
|
||||
```csharp
|
||||
// 自定义管理器遵循框架规则,继承 ContextAwareBase
|
||||
public class SaveManager : ContextAwareBase
|
||||
{
|
||||
// 不再需要手动构造函数传参,上下文由框架注入
|
||||
// protected override void OnContextReady() 可用于初始化
|
||||
|
||||
public void SaveGame()
|
||||
{
|
||||
// 因为继承了 ContextAwareBase,可以使用扩展方法
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var saveData = new SaveData
|
||||
{
|
||||
PlayerName = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Gold = playerModel.Gold.Value
|
||||
};
|
||||
|
||||
// 保存逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 规则约束
|
||||
|
||||
### 1. 组件注册规则
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// Model/System/Utility 自动获得架构引用
|
||||
this.RegisterModel<PlayerModel>(new PlayerModel());
|
||||
this.RegisterSystem<CombatSystem>(new CombatSystem());
|
||||
this.RegisterUtility<StorageUtility>(new StorageUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Command/Query 自动注入规则
|
||||
|
||||
```csharp
|
||||
// Controller 中发送 Command
|
||||
public class ShopUI : Control, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
private void OnBuyButtonPressed()
|
||||
{
|
||||
var command = new BuyItemCommand { ItemId = "sword_01" };
|
||||
|
||||
// SendCommand 内部会自动调用 command.SetArchitecture(this.GetArchitecture())
|
||||
this.SendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
// Command 执行时已经有了架构引用
|
||||
public class BuyItemCommand : AbstractCommand
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
// 此时 GetArchitecture() 已经可用
|
||||
var model = this.GetModel<ShopModel>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设计模式
|
||||
|
||||
### 1. 依赖注入模式
|
||||
|
||||
Rule 接口体现了依赖注入(DI)的思想:
|
||||
|
||||
```csharp
|
||||
// 接口定义了"需要什么"
|
||||
public interface IContextAware
|
||||
{
|
||||
void SetContext(IArchitectureContext context);
|
||||
IArchitectureContext GetContext();
|
||||
}
|
||||
|
||||
// 框架负责"提供什么"
|
||||
public static class CanSendExtensions
|
||||
{
|
||||
public static void SendCommand<T>(this ICanSendCommand self, T command)
|
||||
where T : ICommand
|
||||
{
|
||||
// 自动注入架构上下文依赖
|
||||
command.SetContext(self.GetContext());
|
||||
command.Execute();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 接口隔离原则(ISP)
|
||||
|
||||
Rule 接口遵循接口隔离原则,每个接口职责单一:
|
||||
|
||||
```csharp
|
||||
// ❌ 不好的设计:一个大接口包含所有能力
|
||||
public interface IBigInterface
|
||||
{
|
||||
void SetContext(IArchitectureContext context);
|
||||
IArchitectureContext GetContext();
|
||||
T GetModel<T>() where T : class, IModel;
|
||||
T GetSystem<T>() where T : class, ISystem;
|
||||
void SendCommand<T>(T command) where T : ICommand;
|
||||
// ... 更多方法
|
||||
}
|
||||
|
||||
// ✅ 好的设计:小接口组合
|
||||
public interface IContextAware { ... } // 只负责上下文的设置与获取
|
||||
public interface ICanGetModel { ... } // 只负责获取 Model
|
||||
public interface ICanSendCommand { ... } // 只负责发送 Command
|
||||
```
|
||||
|
||||
### 3. 组合优于继承
|
||||
|
||||
通过接口组合实现不同能力:
|
||||
|
||||
```csharp
|
||||
// Controller 需要获取 Model 和发送 Command
|
||||
public interface IController : ICanGetModel, ICanGetSystem, ICanSendCommand,
|
||||
ICanSendQuery, ICanRegisterEvent
|
||||
{
|
||||
}
|
||||
|
||||
// Command 需要上下文感知和获取 Model/System
|
||||
public interface ICommand : IContextAware, ICanGetModel, ICanGetSystem,
|
||||
ICanSendEvent, ICanSendQuery
|
||||
{
|
||||
}
|
||||
|
||||
// System 只需要获取其他组件
|
||||
public interface ISystem : IContextAware, ICanGetModel, ICanGetUtility, ICanGetSystem,
|
||||
ICanRegisterEvent, ICanSendEvent, ICanSendQuery
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展规则
|
||||
|
||||
### 自定义规则接口
|
||||
|
||||
```csharp
|
||||
// 定义新的规则接口
|
||||
public interface ICanAccessDatabase : IBelongToArchitecture
|
||||
{
|
||||
}
|
||||
|
||||
// 提供扩展方法
|
||||
public static class CanAccessDatabaseExtensions
|
||||
{
|
||||
public static DatabaseUtility GetDatabase(this ICanAccessDatabase self)
|
||||
{
|
||||
return self.GetArchitecture().GetUtility<DatabaseUtility>();
|
||||
}
|
||||
}
|
||||
|
||||
// 让特定组件实现这个接口
|
||||
public class DatabaseCommand : AbstractCommand, ICanAccessDatabase
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var db = this.GetDatabase();
|
||||
// 使用数据库...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **遵循既有规则** - 优先使用框架提供的规则接口
|
||||
2. **不要绕过规则** - 不要直接存储架构引用,使用接口获取
|
||||
3. **接口组合** - 通过实现多个小接口获得所需能力
|
||||
4. **扩展规则** - 需要新能力时,定义新的规则接口
|
||||
5. **保持一致** - 自定义组件也应遵循相同的规则体系
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`architecture`](./architecture.md) - 定义 IArchitectureContext 接口
|
||||
- [`command`](./command.md) - Command 继承 AbstractCommand (IContextAware)
|
||||
- [`query`](./query.md) - Query 继承 AbstractQuery (IContextAware)
|
||||
- [`controller`](./controller.md) - Controller 实现 ICanSendCommand 等接口
|
||||
- [`system`](./system.md) - System 继承 AbstractSystem (IContextAware)
|
||||
- [`model`](./model.md) - Model 继承 AbstractModel (IContextAware)
|
||||
- [`extensions`](./extensions.md) - 基于规则接口提供扩展方法
|
||||
544
docs/core/system.md
Normal file
544
docs/core/system.md
Normal file
@ -0,0 +1,544 @@
|
||||
# System 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
System 包定义了业务逻辑层(Business Logic Layer)。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### [`ICanGetSystem`](ICanGetSystem.cs)
|
||||
|
||||
标记接口,表示该类型可以获取其他 System。
|
||||
|
||||
**继承关系:**
|
||||
|
||||
```csharp
|
||||
public interface ICanGetSystem : IBelongToArchitecture
|
||||
```
|
||||
|
||||
### [`ISystem`](ISystem.cs)
|
||||
|
||||
System 接口,定义了系统的基本行为。
|
||||
|
||||
**核心成员:**
|
||||
|
||||
```csharp
|
||||
void Init(); // 系统初始化方法
|
||||
void Destroy(); // 系统销毁方法
|
||||
void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
|
||||
```
|
||||
|
||||
**继承的能力:**
|
||||
|
||||
- `ICanSetArchitecture` - 可设置架构
|
||||
- `ICanGetModel` - 可获取 Model
|
||||
- `ICanGetUtility` - 可获取 Utility
|
||||
- `ICanGetSystem` - 可获取其他 System
|
||||
- `ICanRegisterEvent` - 可注册事件
|
||||
- `ICanSendEvent` - 可发送事件
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractSystem`](AbstractSystem.cs)
|
||||
|
||||
抽象 System 基类,提供了 System 的基础实现。继承自 ContextAwareBase,具有上下文感知能力。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public abstract class AbstractSystem : ContextAwareBase, ISystem
|
||||
{
|
||||
void ISystem.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit()
|
||||
void ISystem.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy()
|
||||
protected abstract void OnInit(); // 子类实现初始化逻辑
|
||||
protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑
|
||||
public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义 System
|
||||
|
||||
```csharp
|
||||
// 战斗系统
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 注册事件监听
|
||||
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
|
||||
this.RegisterEvent<PlayerAttackEvent>(OnPlayerAttack);
|
||||
}
|
||||
|
||||
private void OnEnemyAttack(EnemyAttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 计算伤害
|
||||
int damage = CalculateDamage(e.AttackPower, playerModel.Defense.Value);
|
||||
|
||||
// 应用伤害
|
||||
playerModel.Health.Value -= damage;
|
||||
|
||||
// 发送伤害事件
|
||||
this.SendEvent(new PlayerTookDamageEvent { Damage = damage });
|
||||
}
|
||||
|
||||
private void OnPlayerAttack(PlayerAttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var enemyModel = this.GetModel<EnemyModel>();
|
||||
|
||||
int damage = CalculateDamage(playerModel.AttackPower.Value, e.Enemy.Defense);
|
||||
e.Enemy.Health -= damage;
|
||||
|
||||
this.SendEvent(new EnemyTookDamageEvent
|
||||
{
|
||||
EnemyId = e.Enemy.Id,
|
||||
Damage = damage
|
||||
});
|
||||
}
|
||||
|
||||
private int CalculateDamage(int attackPower, int defense)
|
||||
{
|
||||
return Math.Max(1, attackPower - defense / 2);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
// 清理资源
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 注册 System
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
this.RegisterModel<PlayerModel>(new PlayerModel());
|
||||
this.RegisterModel<EnemyModel>(new EnemyModel());
|
||||
|
||||
// 注册 System(系统注册后会自动调用 Init)
|
||||
this.RegisterSystem<CombatSystem>(new CombatSystem());
|
||||
this.RegisterSystem<InventorySystem>(new InventorySystem());
|
||||
this.RegisterSystem<QuestSystem>(new QuestSystem());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在其他组件中获取 System
|
||||
|
||||
```csharp
|
||||
// 在 Controller 中
|
||||
public partial class GameController : Node, IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// 获取 System
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
var questSystem = this.GetSystem<QuestSystem>();
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Command 中
|
||||
public class StartBattleCommand : AbstractCommand
|
||||
{
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
// 使用 System...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见使用模式
|
||||
|
||||
### 1. 事件驱动的 System
|
||||
|
||||
```csharp
|
||||
public class InventorySystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
// 监听物品相关事件
|
||||
this.RegisterEvent<ItemAddedEvent>(OnItemAdded);
|
||||
this.RegisterEvent<ItemRemovedEvent>(OnItemRemoved);
|
||||
this.RegisterEvent<ItemUsedEvent>(OnItemUsed);
|
||||
}
|
||||
|
||||
private void OnItemAdded(ItemAddedEvent e)
|
||||
{
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
|
||||
// 添加物品
|
||||
inventoryModel.AddItem(e.ItemId, e.Count);
|
||||
|
||||
// 检查成就
|
||||
CheckAchievements(e.ItemId);
|
||||
|
||||
// 发送通知
|
||||
this.SendEvent(new ShowNotificationEvent
|
||||
{
|
||||
Message = $"获得物品: {e.ItemId} x{e.Count}"
|
||||
});
|
||||
}
|
||||
|
||||
private void OnItemUsed(ItemUsedEvent e)
|
||||
{
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
if (inventoryModel.HasItem(e.ItemId))
|
||||
{
|
||||
// 应用物品效果
|
||||
ApplyItemEffect(e.ItemId, playerModel);
|
||||
|
||||
// 移除物品
|
||||
inventoryModel.RemoveItem(e.ItemId, 1);
|
||||
|
||||
this.SendEvent(new ItemEffectAppliedEvent { ItemId = e.ItemId });
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyItemEffect(string itemId, PlayerModel player)
|
||||
{
|
||||
// 物品效果逻辑...
|
||||
if (itemId == "health_potion")
|
||||
{
|
||||
player.Health.Value = Math.Min(
|
||||
player.Health.Value + 50,
|
||||
player.MaxHealth.Value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAchievements(string itemId)
|
||||
{
|
||||
// 成就检查逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 定时更新的 System
|
||||
|
||||
```csharp
|
||||
public class BuffSystem : AbstractSystem
|
||||
{
|
||||
private List<BuffData> _activeBuffs = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<BuffAppliedEvent>(OnBuffApplied);
|
||||
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||
}
|
||||
|
||||
private void OnBuffApplied(BuffAppliedEvent e)
|
||||
{
|
||||
_activeBuffs.Add(new BuffData
|
||||
{
|
||||
BuffId = e.BuffId,
|
||||
Duration = e.Duration,
|
||||
RemainingTime = e.Duration
|
||||
});
|
||||
|
||||
ApplyBuffEffect(e.BuffId, true);
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 更新所有 Buff
|
||||
for (int i = _activeBuffs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var buff = _activeBuffs[i];
|
||||
buff.RemainingTime -= e.DeltaTime;
|
||||
|
||||
if (buff.RemainingTime <= 0)
|
||||
{
|
||||
// Buff 过期
|
||||
ApplyBuffEffect(buff.BuffId, false);
|
||||
_activeBuffs.RemoveAt(i);
|
||||
|
||||
this.SendEvent(new BuffExpiredEvent { BuffId = buff.BuffId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBuffEffect(string buffId, bool apply)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
// 应用或移除 Buff 效果...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 跨 System 协作
|
||||
|
||||
```csharp
|
||||
public class QuestSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled);
|
||||
this.RegisterEvent<ItemCollectedEvent>(OnItemCollected);
|
||||
}
|
||||
|
||||
private void OnEnemyKilled(EnemyKilledEvent e)
|
||||
{
|
||||
var questModel = this.GetModel<QuestModel>();
|
||||
var activeQuests = questModel.GetActiveQuests();
|
||||
|
||||
foreach (var quest in activeQuests)
|
||||
{
|
||||
if (quest.Type == QuestType.KillEnemy && quest.TargetId == e.EnemyType)
|
||||
{
|
||||
quest.Progress++;
|
||||
|
||||
if (quest.Progress >= quest.RequiredAmount)
|
||||
{
|
||||
// 任务完成
|
||||
CompleteQuest(quest.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteQuest(string questId)
|
||||
{
|
||||
var questModel = this.GetModel<QuestModel>();
|
||||
var quest = questModel.GetQuest(questId);
|
||||
|
||||
// 标记任务完成
|
||||
questModel.CompleteQuest(questId);
|
||||
|
||||
// 发放奖励(通过其他 System)
|
||||
this.SendEvent(new QuestCompletedEvent
|
||||
{
|
||||
QuestId = questId,
|
||||
Rewards = quest.Rewards
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class RewardSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<QuestCompletedEvent>(OnQuestCompleted);
|
||||
}
|
||||
|
||||
private void OnQuestCompleted(QuestCompletedEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 发放奖励
|
||||
foreach (var reward in e.Rewards)
|
||||
{
|
||||
switch (reward.Type)
|
||||
{
|
||||
case RewardType.Gold:
|
||||
playerModel.Gold.Value += reward.Amount;
|
||||
break;
|
||||
case RewardType.Experience:
|
||||
playerModel.Experience.Value += reward.Amount;
|
||||
break;
|
||||
case RewardType.Item:
|
||||
this.SendEvent(new ItemAddedEvent
|
||||
{
|
||||
ItemId = reward.ItemId,
|
||||
Count = reward.Amount
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.SendEvent(new RewardsGrantedEvent { Rewards = e.Rewards });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 管理复杂状态机
|
||||
|
||||
```csharp
|
||||
public class GameStateSystem : AbstractSystem
|
||||
{
|
||||
private GameState _currentState = GameState.MainMenu;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<GameStateChangeRequestEvent>(OnStateChangeRequest);
|
||||
}
|
||||
|
||||
private void OnStateChangeRequest(GameStateChangeRequestEvent e)
|
||||
{
|
||||
if (CanTransition(_currentState, e.TargetState))
|
||||
{
|
||||
ExitState(_currentState);
|
||||
_currentState = e.TargetState;
|
||||
EnterState(_currentState);
|
||||
|
||||
this.SendEvent(new GameStateChangedEvent
|
||||
{
|
||||
PreviousState = _currentState,
|
||||
NewState = e.TargetState
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanTransition(GameState from, GameState to)
|
||||
{
|
||||
// 状态转换规则
|
||||
return (from, to) switch
|
||||
{
|
||||
(GameState.MainMenu, GameState.Playing) => true,
|
||||
(GameState.Playing, GameState.Paused) => true,
|
||||
(GameState.Paused, GameState.Playing) => true,
|
||||
(GameState.Playing, GameState.GameOver) => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private void EnterState(GameState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case GameState.Playing:
|
||||
// 开始游戏
|
||||
this.SendCommand(new StartGameCommand());
|
||||
break;
|
||||
case GameState.Paused:
|
||||
// 暂停游戏
|
||||
this.SendEvent(new GamePausedEvent());
|
||||
break;
|
||||
case GameState.GameOver:
|
||||
// 游戏结束
|
||||
this.SendCommand(new GameOverCommand());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExitState(GameState state)
|
||||
{
|
||||
// 清理当前状态
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## System vs Model
|
||||
|
||||
### Model(数据层)
|
||||
|
||||
- **职责**:存储数据和状态
|
||||
- **特点**:被动,等待修改
|
||||
- **示例**:PlayerModel、InventoryModel
|
||||
|
||||
### System(逻辑层)
|
||||
|
||||
- **职责**:处理业务逻辑,协调 Model
|
||||
- **特点**:主动,响应事件
|
||||
- **示例**:CombatSystem、QuestSystem
|
||||
|
||||
```csharp
|
||||
// ✅ 正确的职责划分
|
||||
|
||||
// Model: 存储数据
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> Mana { get; } = new(50);
|
||||
|
||||
protected override void OnInit() { }
|
||||
}
|
||||
|
||||
// System: 处理逻辑
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<AttackEvent>(OnAttack);
|
||||
}
|
||||
|
||||
private void OnAttack(AttackEvent e)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// System 负责计算和决策
|
||||
int damage = CalculateDamage(e);
|
||||
playerModel.Health.Value -= damage;
|
||||
|
||||
if (playerModel.Health.Value <= 0)
|
||||
{
|
||||
this.SendEvent(new PlayerDiedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **单一职责** - 每个 System 专注于一个业务领域
|
||||
2. **事件驱动** - 通过事件与其他组件通信
|
||||
3. **无状态或少状态** - 优先将状态存储在 Model 中
|
||||
4. **可组合** - System 之间通过事件松耦合协作
|
||||
5. **初始化注册** - 在 `OnInit` 中注册所有事件监听
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 避免频繁的 GetModel/GetSystem
|
||||
|
||||
```csharp
|
||||
// ❌ 不好:每次都获取
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>(); // 频繁调用
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ 好:缓存引用
|
||||
private PlayerModel _playerModel;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_playerModel = this.GetModel<PlayerModel>(); // 只获取一次
|
||||
}
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 直接使用缓存的引用
|
||||
_playerModel.Health.Value += 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 批量处理
|
||||
|
||||
```csharp
|
||||
public class ParticleSystem : AbstractSystem
|
||||
{
|
||||
private List<Particle> _particles = new();
|
||||
|
||||
private void OnUpdate(GameUpdateEvent e)
|
||||
{
|
||||
// 批量更新,而不是每个粒子发一个事件
|
||||
for (int i = _particles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
UpdateParticle(_particles[i], e.DeltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`model`](./model.md) - System 操作 Model 的数据
|
||||
- [`events`](./events.md) - System 通过事件通信
|
||||
- [`command`](./command.md) - System 中可以发送 Command
|
||||
- [`query`](./query.md) - System 中可以发送 Query
|
||||
- [`utility`](./utility.md) - System 可以使用 Utility
|
||||
- [`architecture`](./architecture.md) - 在架构中注册 System
|
||||
613
docs/core/utility.md
Normal file
613
docs/core/utility.md
Normal file
@ -0,0 +1,613 @@
|
||||
# Utility 包使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
Utility 包定义了工具类层。Utility 提供无状态的辅助功能,如数学计算、文件操作、序列化等通用工具方法。与 System 不同,Utility
|
||||
不依赖架构状态,是纯粹的工具函数集合。
|
||||
|
||||
## 核心接口
|
||||
|
||||
### IUtility
|
||||
|
||||
Utility 标记接口,所有工具类都应实现此接口。
|
||||
|
||||
**接口定义:**
|
||||
|
||||
```csharp
|
||||
public interface IUtility
|
||||
{
|
||||
// 标记接口,无方法定义
|
||||
}
|
||||
```
|
||||
|
||||
### IContextUtility
|
||||
|
||||
上下文工具接口,扩展了IUtility接口,为需要感知架构上下文的工具类提供基础能力。
|
||||
|
||||
**接口定义:**
|
||||
|
||||
```csharp
|
||||
public interface IContextUtility : IUtility
|
||||
{
|
||||
void Init(); // 初始化上下文工具
|
||||
}
|
||||
```
|
||||
|
||||
## 核心类
|
||||
|
||||
### [`AbstractContextUtility`](AbstractContextUtility.cs)
|
||||
|
||||
抽象上下文工具类,提供上下文相关的通用功能实现。继承自 ContextAwareBase 并实现 IContextUtility 接口。
|
||||
|
||||
**使用方式:**
|
||||
|
||||
```csharp
|
||||
public abstract class AbstractContextUtility : ContextAwareBase, IContextUtility
|
||||
{
|
||||
protected ILogger Logger = null!;
|
||||
|
||||
void IContextUtility.Init()
|
||||
{
|
||||
var name = GetType().Name;
|
||||
Logger = LoggerFactoryResolver.Provider.CreateLogger(name);
|
||||
Logger.Debug($"Initializing Context Utility: {name}");
|
||||
|
||||
OnInit(); // 子类实现初始化逻辑
|
||||
|
||||
Logger.Info($"Context Utility initialized: {name}");
|
||||
}
|
||||
|
||||
protected abstract void OnInit(); // 子类实现具体的初始化逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 基本使用
|
||||
|
||||
### 1. 定义 Utility
|
||||
|
||||
```csharp
|
||||
// 存储工具类,继承自AbstractContextUtility
|
||||
public class StorageUtility : AbstractContextUtility
|
||||
{
|
||||
private const string SavePath = "user://save_data.json";
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
Logger.Info("StorageUtility initialized");
|
||||
}
|
||||
|
||||
public void Save<T>(T data)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(data);
|
||||
// 实际保存逻辑
|
||||
File.WriteAllText(SavePath, json);
|
||||
}
|
||||
|
||||
public T Load<T>()
|
||||
{
|
||||
if (!File.Exists(SavePath))
|
||||
return default(T);
|
||||
|
||||
string json = File.ReadAllText(SavePath);
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
File.Delete(SavePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数学工具类,作为普通Utility
|
||||
public class MathUtility : IUtility
|
||||
{
|
||||
public float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * Math.Clamp(t, 0f, 1f);
|
||||
}
|
||||
|
||||
public bool IsInRange(float value, float min, float max)
|
||||
{
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
}
|
||||
|
||||
// 时间工具类
|
||||
public class TimeUtility : IUtility
|
||||
{
|
||||
public string FormatTime(float seconds)
|
||||
{
|
||||
int minutes = (int)(seconds / 60);
|
||||
int secs = (int)(seconds % 60);
|
||||
return $"{minutes:D2}:{secs:D2}";
|
||||
}
|
||||
|
||||
public long GetCurrentTimestamp()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
public bool IsExpired(long timestamp, int durationSeconds)
|
||||
{
|
||||
return GetCurrentTimestamp() > timestamp + durationSeconds;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 注册 Utility
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Utility(不需要初始化)
|
||||
this.RegisterUtility<StorageUtility>(new StorageUtility());
|
||||
this.RegisterUtility<MathUtility>(new MathUtility());
|
||||
this.RegisterUtility<TimeUtility>(new TimeUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用 Utility
|
||||
|
||||
```csharp
|
||||
// 在 System 中使用
|
||||
public class SaveSystem : AbstractSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<SaveGameEvent>(OnSaveGame);
|
||||
this.RegisterEvent<LoadGameEvent>(OnLoadGame);
|
||||
}
|
||||
|
||||
private void OnSaveGame(SaveGameEvent e)
|
||||
{
|
||||
var storage = this.GetUtility<StorageUtility>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
var saveData = new SaveData
|
||||
{
|
||||
PlayerName = playerModel.Name.Value,
|
||||
Level = playerModel.Level.Value,
|
||||
Gold = playerModel.Gold.Value,
|
||||
Timestamp = this.GetUtility<TimeUtility>().GetCurrentTimestamp()
|
||||
};
|
||||
|
||||
storage.Save(saveData);
|
||||
this.SendEvent(new GameSavedEvent());
|
||||
}
|
||||
|
||||
private void OnLoadGame(LoadGameEvent e)
|
||||
{
|
||||
var storage = this.GetUtility<StorageUtility>();
|
||||
var saveData = storage.Load<SaveData>();
|
||||
|
||||
if (saveData != null)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Name.Value = saveData.PlayerName;
|
||||
playerModel.Level.Value = saveData.Level;
|
||||
playerModel.Gold.Value = saveData.Gold;
|
||||
|
||||
this.SendEvent(new GameLoadedEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Command 中使用
|
||||
public class MovePlayerCommand : AbstractCommand
|
||||
{
|
||||
public Vector3 TargetPosition { get; set; }
|
||||
public float Speed { get; set; }
|
||||
|
||||
protected override void OnExecute()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var mathUtil = this.GetUtility<MathUtility>();
|
||||
|
||||
// 使用工具类计算
|
||||
Vector3 currentPos = playerModel.Position.Value;
|
||||
Vector3 direction = (TargetPosition - currentPos).Normalized();
|
||||
Vector3 newPos = currentPos + direction * Speed;
|
||||
|
||||
playerModel.Position.Value = newPos;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见 Utility 类型
|
||||
|
||||
### 1. 序列化/反序列化工具
|
||||
|
||||
```csharp
|
||||
public class JsonUtility : IUtility
|
||||
{
|
||||
public string Serialize<T>(T obj)
|
||||
{
|
||||
return Json.Stringify(obj);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json) where T : new()
|
||||
{
|
||||
return Json.Parse<T>(json);
|
||||
}
|
||||
|
||||
public bool TryDeserialize<T>(string json, out T result) where T : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Json.Parse<T>(json);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 随机数工具
|
||||
|
||||
```csharp
|
||||
public class RandomUtility : IUtility
|
||||
{
|
||||
private Random _random = new Random();
|
||||
|
||||
public int Range(int min, int max)
|
||||
{
|
||||
return _random.Next(min, max + 1);
|
||||
}
|
||||
|
||||
public float Range(float min, float max)
|
||||
{
|
||||
return min + (float)_random.NextDouble() * (max - min);
|
||||
}
|
||||
|
||||
public T Choose<T>(params T[] items)
|
||||
{
|
||||
return items[Range(0, items.Length - 1)];
|
||||
}
|
||||
|
||||
public List<T> Shuffle<T>(List<T> list)
|
||||
{
|
||||
var shuffled = new List<T>(list);
|
||||
for (int i = shuffled.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Range(0, i);
|
||||
(shuffled[i], shuffled[j]) = (shuffled[j], shuffled[i]);
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
public bool Probability(float chance)
|
||||
{
|
||||
return _random.NextDouble() < chance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 字符串工具
|
||||
|
||||
```csharp
|
||||
public class StringUtility : IUtility
|
||||
{
|
||||
public string Truncate(string text, int maxLength, string suffix = "...")
|
||||
{
|
||||
if (text.Length <= maxLength)
|
||||
return text;
|
||||
return text.Substring(0, maxLength - suffix.Length) + suffix;
|
||||
}
|
||||
|
||||
public string FormatNumber(int number)
|
||||
{
|
||||
if (number >= 1000000)
|
||||
return $"{number / 1000000.0:F1}M";
|
||||
if (number >= 1000)
|
||||
return $"{number / 1000.0:F1}K";
|
||||
return number.ToString();
|
||||
}
|
||||
|
||||
public string ToTitleCase(string text)
|
||||
{
|
||||
return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text.ToLower());
|
||||
}
|
||||
|
||||
public bool IsValidEmail(string email)
|
||||
{
|
||||
return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 加密工具
|
||||
|
||||
```csharp
|
||||
public class EncryptionUtility : IUtility
|
||||
{
|
||||
private const string EncryptionKey = "YourSecretKey123";
|
||||
|
||||
public string Encrypt(string plainText)
|
||||
{
|
||||
byte[] data = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] encrypted = EncryptBytes(data);
|
||||
return Convert.ToBase64String(encrypted);
|
||||
}
|
||||
|
||||
public string Decrypt(string encryptedText)
|
||||
{
|
||||
byte[] data = Convert.FromBase64String(encryptedText);
|
||||
byte[] decrypted = DecryptBytes(data);
|
||||
return System.Text.Encoding.UTF8.GetString(decrypted);
|
||||
}
|
||||
|
||||
private byte[] EncryptBytes(byte[] data)
|
||||
{
|
||||
// 简单的 XOR 加密示例(实际项目应使用更安全的算法)
|
||||
byte[] key = System.Text.Encoding.UTF8.GetBytes(EncryptionKey);
|
||||
byte[] result = new byte[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
result[i] = (byte)(data[i] ^ key[i % key.Length]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] DecryptBytes(byte[] data)
|
||||
{
|
||||
return EncryptBytes(data); // XOR 解密与加密相同
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 对象池工具
|
||||
|
||||
```csharp
|
||||
public class ObjectPoolUtility : IUtility
|
||||
{
|
||||
private Dictionary<Type, Queue<object>> _pools = new();
|
||||
|
||||
public T Get<T>() where T : new()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (_pools.ContainsKey(type) && _pools[type].Count > 0)
|
||||
{
|
||||
return (T)_pools[type].Dequeue();
|
||||
}
|
||||
return new T();
|
||||
}
|
||||
|
||||
public void Return<T>(T obj)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (!_pools.ContainsKey(type))
|
||||
{
|
||||
_pools[type] = new Queue<object>();
|
||||
}
|
||||
_pools[type].Enqueue(obj);
|
||||
}
|
||||
|
||||
public void Clear<T>()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (_pools.ContainsKey(type))
|
||||
{
|
||||
_pools[type].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
_pools.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 日志工具
|
||||
|
||||
```csharp
|
||||
public class LogUtility : IUtility
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level = LogLevel.Info)
|
||||
{
|
||||
string prefix = level switch
|
||||
{
|
||||
LogLevel.Debug => "[DEBUG]",
|
||||
LogLevel.Info => "[INFO]",
|
||||
LogLevel.Warning => "[WARN]",
|
||||
LogLevel.Error => "[ERROR]",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
string timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||
GD.Print($"{timestamp} {prefix} {message}");
|
||||
}
|
||||
|
||||
public void Debug(string message) => Log(message, LogLevel.Debug);
|
||||
public void Info(string message) => Log(message, LogLevel.Info);
|
||||
public void Warning(string message) => Log(message, LogLevel.Warning);
|
||||
public void Error(string message) => Log(message, LogLevel.Error);
|
||||
}
|
||||
```
|
||||
|
||||
## Utility vs System
|
||||
|
||||
### Utility(工具层)
|
||||
|
||||
- **无状态** - 不存储业务数据
|
||||
- **纯函数** - 相同输入产生相同输出
|
||||
- **独立性** - 不依赖架构状态
|
||||
- **可复用** - 可在多个项目中使用
|
||||
|
||||
### System(逻辑层)
|
||||
|
||||
- **有状态** - 可能存储临时状态
|
||||
- **业务逻辑** - 处理特定业务流程
|
||||
- **架构依赖** - 需要访问 Model
|
||||
- **项目特定** - 针对特定项目设计
|
||||
|
||||
```csharp
|
||||
// ✅ Utility: 无状态的工具方法
|
||||
public class MathUtility : IUtility
|
||||
{
|
||||
public float CalculateDamage(float attackPower, float defense)
|
||||
{
|
||||
return Math.Max(1, attackPower - defense * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ System: 有状态的业务逻辑
|
||||
public class CombatSystem : AbstractSystem
|
||||
{
|
||||
private List<CombatInstance> _activeCombats = new();
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
this.RegisterEvent<AttackEvent>(OnAttack);
|
||||
}
|
||||
|
||||
private void OnAttack(AttackEvent e)
|
||||
{
|
||||
var mathUtil = this.GetUtility<MathUtility>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 使用 Utility 计算,但在 System 中处理业务逻辑
|
||||
float damage = mathUtil.CalculateDamage(e.AttackPower, playerModel.Defense.Value);
|
||||
playerModel.Health.Value -= (int)damage;
|
||||
|
||||
_activeCombats.Add(new CombatInstance { Damage = damage });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **保持无状态** - Utility 不应存储业务状态
|
||||
2. **纯函数优先** - 相同输入应产生相同输出
|
||||
3. **单一职责** - 每个 Utility 专注于一类功能
|
||||
4. **避免依赖** - 不依赖 Model、System 等架构组件
|
||||
5. **可测试** - 易于单元测试的纯函数
|
||||
|
||||
## 错误示例
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:Utility 中存储状态
|
||||
public class BadUtility : IUtility
|
||||
{
|
||||
private int _counter = 0; // 不应该有状态
|
||||
|
||||
public int GetNextId()
|
||||
{
|
||||
return ++_counter; // 依赖内部状态
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 错误:Utility 中访问 Model
|
||||
public class BadUtility2 : IUtility
|
||||
{
|
||||
public void DoSomething()
|
||||
{
|
||||
// Utility 不应该访问架构组件
|
||||
var model = this.GetModel<PlayerModel>(); // 编译错误!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:如果需要状态,应该使用 System
|
||||
public class IdGeneratorSystem : AbstractSystem
|
||||
{
|
||||
private int _counter = 0;
|
||||
|
||||
protected override void OnInit() { }
|
||||
|
||||
public int GetNextId()
|
||||
{
|
||||
return ++_counter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 缓存计算结果
|
||||
|
||||
```csharp
|
||||
public class PathfindingUtility : IUtility
|
||||
{
|
||||
private Dictionary<(Vector3, Vector3), List<Vector3>> _pathCache = new();
|
||||
|
||||
public List<Vector3> FindPath(Vector3 start, Vector3 end, bool useCache = true)
|
||||
{
|
||||
var key = (start, end);
|
||||
|
||||
if (useCache && _pathCache.ContainsKey(key))
|
||||
{
|
||||
return _pathCache[key];
|
||||
}
|
||||
|
||||
var path = CalculatePath(start, end);
|
||||
|
||||
if (useCache)
|
||||
{
|
||||
_pathCache[key] = path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<Vector3> CalculatePath(Vector3 start, Vector3 end)
|
||||
{
|
||||
// A* 算法等复杂计算...
|
||||
return new List<Vector3>();
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
_pathCache.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 对象复用
|
||||
|
||||
```csharp
|
||||
public class CollectionUtility : IUtility
|
||||
{
|
||||
private List<Vector3> _tempList = new();
|
||||
|
||||
public List<Vector3> GetPointsInRadius(Vector3 center, float radius, List<Vector3> points)
|
||||
{
|
||||
_tempList.Clear();
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
if (point.DistanceTo(center) <= radius)
|
||||
{
|
||||
_tempList.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Vector3>(_tempList); // 返回副本
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 相关包
|
||||
|
||||
- [`system`](./system.md) - System 中使用 Utility
|
||||
- [`command`](./command.md) - Command 中可以使用 Utility
|
||||
- [`architecture`](./architecture.md) - 在架构中注册 Utility
|
||||
- [`ioc`](./ioc.md) - Utility 通过 IoC 容器管理
|
||||
- [`extensions`](./extensions.md) - 提供 GetUtility 扩展方法
|
||||
204
docs/game/setting.md
Normal file
204
docs/game/setting.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/)
|
||||
335
docs/godot/extensions.md
Normal file
335
docs/godot/extensions.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) - 通用设置框架
|
||||
579
docs/godot/setting.md
Normal file
579
docs/godot/setting.md
Normal file
@ -0,0 +1,579 @@
|
||||
# Godot 设置模块 (Godot Settings Module)
|
||||
|
||||
## 概述
|
||||
|
||||
Godot 设置模块是 GFramework.Godot 的核心组件之一,专门为 Godot 引擎提供游戏设置系统的实现。该模块将通用的设置框架与 Godot
|
||||
引擎的特定功能相结合,提供了音频设置和图形设置的完整解决方案。
|
||||
|
||||
## 核心类
|
||||
|
||||
### 音频设置系统
|
||||
|
||||
#### AudioBusMap
|
||||
|
||||
音频总线映射配置类,用于定义音频系统中不同类型音频的总线名称。
|
||||
|
||||
**属性:**
|
||||
|
||||
- `Master` - 主音频总线名称(默认:"Master")
|
||||
- `Bgm` - 背景音乐音频总线名称(默认:"BGM")
|
||||
- `Sfx` - 音效音频总线名称(默认:"SFX")
|
||||
|
||||
#### GodotAudioApplier
|
||||
|
||||
音频设置应用器,负责将音频设置应用到 Godot 引擎的音频总线系统。
|
||||
|
||||
**功能:**
|
||||
|
||||
- 应用音量设置到指定音频总线
|
||||
- 处理音量格式转换(线性值到分贝)
|
||||
- 音频总线存在性检查和警告
|
||||
|
||||
#### GodotAudioSettings
|
||||
|
||||
Godot 音频设置实现类,接收 AudioSettings 配置并实现 IApplyAbleSettings 接口,负责将音频配置应用到 Godot 音频系统。
|
||||
|
||||
**实现关系:**
|
||||
|
||||
```
|
||||
AudioSettings (配置数据)
|
||||
↓ [组合]
|
||||
GodotAudioSettings (Godot 特定实现) → IApplyAbleSettings (可应用设置接口)
|
||||
```
|
||||
|
||||
**功能:**
|
||||
|
||||
- 接收 AudioSettings 配置对象和 AudioBusMap 总线映射
|
||||
- 实现 Apply() 方法,将音量设置应用到指定音频总线
|
||||
- 支持自定义音频总线映射
|
||||
- 自动处理音量格式转换(线性值到分贝)
|
||||
|
||||
### 图形设置系统
|
||||
|
||||
#### GodotGraphicsSettings
|
||||
|
||||
Godot 图形设置实现类,继承自 GraphicsSettings 并实现 IApplyAbleSettings。
|
||||
|
||||
**功能:**
|
||||
|
||||
- 分辨率设置和窗口尺寸调整
|
||||
- 全屏模式切换
|
||||
- 窗口位置自动居中
|
||||
- 多显示器支持
|
||||
|
||||
## 架构设计
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[AudioSettings] --> B[GodotAudioSettings]
|
||||
C[GraphicsSettings] --> D[GodotGraphicsSettings]
|
||||
E[IApplyAbleSettings] --> B
|
||||
E --> D
|
||||
|
||||
G[AudioBusMap] --> B
|
||||
|
||||
B --> I[AudioServer API]
|
||||
D --> J[DisplayServer API]
|
||||
|
||||
K[SettingsSystem] --> L[Apply Method]
|
||||
L --> B
|
||||
L --> D
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 音频设置配置
|
||||
|
||||
#### 基本音频设置
|
||||
|
||||
```csharp
|
||||
// 创建音频配置数据
|
||||
var settings = new AudioSettings
|
||||
{
|
||||
MasterVolume = 0.8f, // 80% 主音量
|
||||
BgmVolume = 0.6f, // 60% 背景音乐音量
|
||||
SfxVolume = 0.9f // 90% 音效音量
|
||||
};
|
||||
|
||||
// 创建 Godot 音频设置应用器
|
||||
var audioSettings = new GodotAudioSettings(settings, new AudioBusMap());
|
||||
|
||||
// 应用设置
|
||||
await audioSettings.Apply();
|
||||
```
|
||||
|
||||
#### 自定义音频总线映射
|
||||
|
||||
```csharp
|
||||
// 自定义音频总线映射
|
||||
var customBusMap = new AudioBusMap
|
||||
{
|
||||
Master = "Master_Bus",
|
||||
Bgm = "Background_Music",
|
||||
Sfx = "Sound_Effects"
|
||||
};
|
||||
|
||||
// 创建音频配置
|
||||
var settings = new AudioSettings
|
||||
{
|
||||
MasterVolume = 0.7f,
|
||||
BgmVolume = 0.5f,
|
||||
SfxVolume = 0.8f
|
||||
};
|
||||
|
||||
// 使用自定义总线映射应用设置
|
||||
var audioSettings = new GodotAudioSettings(settings, customBusMap);
|
||||
await audioSettings.Apply();
|
||||
```
|
||||
|
||||
#### 通过设置系统使用
|
||||
|
||||
```csharp
|
||||
// 注册音频设置到设置模型
|
||||
var settingsModel = this.GetModel<ISettingsModel>();
|
||||
var audioSettingsData = settingsModel.Get<AudioSettings>();
|
||||
audioSettingsData.MasterVolume = 0.8f;
|
||||
audioSettingsData.BgmVolume = 0.6f;
|
||||
audioSettingsData.SfxVolume = 0.9f;
|
||||
|
||||
// 创建 Godot 音频设置应用器
|
||||
var godotAudioSettings = new GodotAudioSettings(audioSettingsData, new AudioBusMap());
|
||||
await godotAudioSettings.Apply();
|
||||
```
|
||||
|
||||
### 图形设置配置
|
||||
|
||||
#### 基本图形设置
|
||||
|
||||
```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` 属性,创建后不可修改
|
||||
- 提供合理的默认值
|
||||
- 支持对象初始化语法
|
||||
|
||||
### GodotAudioSettings
|
||||
|
||||
```csharp
|
||||
public class GodotAudioSettings(AudioSettings settings, AudioBusMap busMap) : IApplyAbleSettings
|
||||
{
|
||||
public Task Apply();
|
||||
}
|
||||
```
|
||||
|
||||
**构造函数参数:**
|
||||
|
||||
- `settings` - AudioSettings 配置对象,包含音量设置
|
||||
- `busMap` - AudioBusMap 对象,定义音频总线映射
|
||||
|
||||
**Apply 方法实现:**
|
||||
|
||||
```csharp
|
||||
public Task Apply()
|
||||
{
|
||||
SetBus(busMap.Master, settings.MasterVolume);
|
||||
SetBus(busMap.Bgm, settings.BgmVolume);
|
||||
SetBus(busMap.Sfx, settings.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 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 AudioSettings { MasterVolume = linearVolume };
|
||||
var audioSettings = new GodotAudioSettings(settings, new AudioBusMap());
|
||||
audioSettings.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用自定义总线映射的平滑过渡
|
||||
public class CustomAudioManager : Node
|
||||
{
|
||||
private Tween _volumeTween;
|
||||
private AudioBusMap _customBusMap;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_customBusMap = new AudioBusMap
|
||||
{
|
||||
Master = "Master_Bus",
|
||||
Bgm = "Background_Music",
|
||||
Sfx = "Sound_Effects"
|
||||
};
|
||||
}
|
||||
|
||||
public async Task SmoothVolumeTransition(float targetMasterVolume, float duration = 1.0f)
|
||||
{
|
||||
var settings = new AudioSettings { MasterVolume = targetMasterVolume };
|
||||
var currentVolume = AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex(_customBusMap.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 audioSettingsData = new AudioSettings { MasterVolume = linearVolume };
|
||||
var audioSettings = new GodotAudioSettings(audioSettingsData, _customBusMap);
|
||||
audioSettings.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 窗口管理
|
||||
427
docs/godot/signal.md
Normal file
427
docs/godot/signal.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 信号系统基础
|
||||
281
docs/godot/storage.md
Normal file
281
docs/godot/storage.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