From 026d9964fe19dbfd39fe7f0fb62aad5153197e41 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:59:00 +0800 Subject: [PATCH] =?UTF-8?q?docs(game):=20=E6=9B=B4=E6=96=B0=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E6=A8=A1=E5=9D=97=E6=96=87=E6=A1=A3=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=95=B4=E4=BD=93=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重写了 GFramework.Game 模块的完整文档 - 添加了详细的目录结构和使用示例 - 扩展了架构模块系统的说明和代码示例 - 增加了资产管理、存储系统和序列化系统的详细文档 - 提供了模块配置和高级用法的指导 - 完善了存储系统的分层存储和缓存实现 - 添加了序列化系统的自定义转换器示例 - 更新了核心特性和设计理念的描述 - 优化了文档的整体组织结构和可读性 - 刷新了 VitePress 依赖缓存以同步文档变更 --- GFramework.Core.Tests/TEST_COVERAGE_PLAN.md | 581 -------- GFramework.Core/README.md | 507 ------- GFramework.Game/README.md | 1406 ------------------ GFramework.Godot/README.md | 908 ------------ GFramework.SourceGenerators/README.md | 997 ------------- docs/.vitepress/cache/deps/_metadata.json | 12 +- docs/core/overview.md | 567 ++++++-- docs/game/overview.md | 1450 +++++++++++++++++-- docs/godot/overview.md | 958 ++++++++++-- docs/source-generators/overview.md | 1103 +++++++++++--- 10 files changed, 3610 insertions(+), 4879 deletions(-) delete mode 100644 GFramework.Core.Tests/TEST_COVERAGE_PLAN.md delete mode 100644 GFramework.Core/README.md delete mode 100644 GFramework.Game/README.md delete mode 100644 GFramework.Godot/README.md delete mode 100644 GFramework.SourceGenerators/README.md diff --git a/GFramework.Core.Tests/TEST_COVERAGE_PLAN.md b/GFramework.Core.Tests/TEST_COVERAGE_PLAN.md deleted file mode 100644 index fb6d590..0000000 --- a/GFramework.Core.Tests/TEST_COVERAGE_PLAN.md +++ /dev/null @@ -1,581 +0,0 @@ -# GFramework.Core 模块测试覆盖详细清单 - -> **生成日期**: 2026-01-18 -> **最后更新**: 2026-01-18 -> **当前版本**: Core测试覆盖率 ~79.2% (文件级别) -> **目标**: 提升Core模块测试覆盖率至 95%+ 并补充缺失的单元测试 - ---- - -## 📊 总体统计 - -| 类别 | 源文件数 | 有测试文件数 | 缺失测试文件数 | 测试覆盖率 | -|--------|--------|--------|---------|-----------| -| 架构系统 | 6 | 4 | 1 | 83% | -| 事件系统 | 8 | 5 | 0 | 100% | -| 命令系统 | 4 | 1 | 3 | 25% | -| 查询系统 | 5 | 1 | 3 | 20% | -| 日志系统 | 5 | 2 | 0 | 100% | -| 扩展方法 | 4 | 2 | 0 | 100% | -| 状态系统 | 4 | 2 | 0 | 100% | -| IOC容器 | 1 | 1 | 0 | 100% | -| 模型系统 | 1 | 0 | 0 | 100% | -| 系统基类 | 1 | 0 | 0 | 100% | -| 对象池 | 1 | 1 | 0 | 100% | -| 属性系统 | 2 | 1 | 0 | 100% | -| 规则系统 | 1 | 0 | 0 | 100% | -| 工具类 | 1 | 0 | 1 | 0% | -| 环境系统 | 2 | 1 | 0 | 100% | -| 常量 | 2 | 0 | 2 | 0% | -| 协程系统 | 11 | 4 | 0 | 100% | -| **总计** | **59** | **24** | **10** | **83.1%** | - -> **注**: 标记为0个测试文件的模块通过间接测试(集成测试)实现了功能覆盖 -> **重要发现**: 命令系统和查询系统的异步功能完全缺失测试! - ---- - -## 🎯 测试补充优先级概览 - -| 优先级 | 任务数 | 预计测试数 | 描述 | -|---------|-------|-------------|-------------| -| 🔴 高优先级 | 5 | 34-44 | 异步核心功能和工具基类 | -| 🟡 中优先级 | 2 | 6-10 | 常量验证测试 | -| ✅ 已完成 | 1 | 61 | 协程系统完整测试 | -| **总计** | **7** | **101-115** | - | - ---- - -## 📋 详细源文件与测试文件对应关系 - -### Architecture 模块 (6个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|------------------------------|-----------------------------------------------------|---------| -| Architecture.cs | SyncArchitectureTests.cs, AsyncArchitectureTests.cs | ✅ 98个测试 | -| ArchitectureConfiguration.cs | ArchitectureConfigurationTests.cs | ✅ 12个测试 | -| ArchitectureConstants.cs | **缺失** | ❌ 需补充 | -| ArchitectureContext.cs | ArchitectureContextTests.cs | ✅ 22个测试 | -| ArchitectureServices.cs | ArchitectureServicesTests.cs | ✅ 15个测试 | -| GameContext.cs | GameContextTests.cs | ✅ 已有测试 | - -**测试用例总数**: 147个 - ---- - -### Command 模块 (4个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|-----------------------------|-------------------------|------------| -| **AbstractAsyncCommand.cs** | **缺失** | ❌ 需创建测试文件 | -| AbstractCommand.cs | CommandBusTests.cs (间接) | ✅ 已覆盖 | -| **CommandBus.cs** | CommandBusTests.cs | ⚠️ 需补充异步测试 | -| EmptyCommandInput.cs | CommandBusTests.cs (间接) | ✅ 已覆盖 | - -**测试用例总数**: 4个(需补充异步测试) - -**需要补充**: - -1. ❌ AbstractAsyncCommandTests.cs - 新建(高优先级) -2. ❌ CommandBusTests.cs - 补充 SendAsync 方法测试(高优先级) - ---- - -### Query 模块 (5个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|---------------------------|-----------------------|-----------| -| **AbstractAsyncQuery.cs** | **缺失** | ❌ 需创建测试文件 | -| AbstractQuery.cs | QueryBusTests.cs (间接) | ✅ 已覆盖 | -| **AsyncQueryBus.cs** | **缺失** | ❌ 需创建测试文件 | -| EmptyQueryInput.cs | QueryBusTests.cs (间接) | ✅ 已覆盖 | -| QueryBus.cs | QueryBusTests.cs | ✅ 3个测试 | - -**测试用例总数**: 3个(需补充异步测试) - -**需要补充**: - -1. ❌ AbstractAsyncQueryTests.cs - 新建(高优先级) -2. ❌ AsyncQueryBusTests.cs - 新建(高优先级) - ---- - -### Constants 模块 (2个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|--------------------------|--------|-------| -| ArchitectureConstants.cs | **缺失** | ❌ 需补充 | -| GFrameworkConstants.cs | **缺失** | ❌ 需补充 | - -**测试用例总数**: 0个 - -**需要补充**: - -1. ❌ ArchitectureConstantsTests.cs - 新建(中优先级) -2. ❌ GFrameworkConstantsTests.cs - 新建(中优先级) - ---- - -### Utility 模块 (1个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|---------------------------|--------|-----------| -| AbstractContextUtility.cs | **缺失** | ❌ 需创建测试文件 | - -**测试用例总数**: 0个 - -**需要补充**: - -1. ❌ AbstractContextUtilityTests.cs - 新建(高优先级) - ---- - -### 协程系统 模块 (11个源文件) - -| 源文件 | 对应测试文件 | 测试覆盖 | -|-----------------------|---------------------------------|---------| -| CoroutineState.cs | CoroutineStateTests.cs | ✅ 2个测试 | -| ITimeSource.cs | CoroutineSchedulerTests.cs (间接) | ✅ 已覆盖 | -| IYieldInstruction.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| CoroutineHandle.cs | CoroutineHandleTests.cs | ✅ 15个测试 | -| CoroutineHelper.cs | CoroutineHelperTests.cs | ✅ 19个测试 | -| CoroutineMetadata.cs | CoroutineSchedulerTests.cs (间接) | ✅ 已覆盖 | -| CoroutineScheduler.cs | CoroutineSchedulerTests.cs | ✅ 25个测试 | -| CoroutineSlot.cs | CoroutineSchedulerTests.cs (间接) | ✅ 已覆盖 | -| Delay.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| WaitForCoroutine.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| WaitForFrames.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| WaitOneFrame.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| WaitUntil.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | -| WaitWhile.cs | YieldInstructionTests.cs (间接) | ✅ 已覆盖 | - -**测试用例总数**: 61个 - -**需要补充**: - -无需补充,协程系统测试覆盖率已达 100% - ---- - -### 其他模块 (Events, Logging, IoC, etc.) - -所有其他模块的测试覆盖率均达到 100%(包括间接测试覆盖),详见下文详细列表。 - ---- - -## 🔴 高优先级 - 异步核心功能(5个任务) - -### 任务1: CommandBusTests.cs - 补充异步测试 - -**源文件路径**: `GFramework.Core/command/CommandBus.cs` - -**优先级**: 🔴 高 - -**原因**: CommandBus 已实现 SendAsync 方法但没有任何测试 - -**需要补充的测试内容**: - -- ✅ SendAsync(IAsyncCommand) 方法 - 执行无返回值的异步命令 -- ✅ SendAsync(IAsyncCommand) 方法 - 处理 null 异步命令 -- ✅ SendAsync(IAsyncCommand) 方法 - 执行有返回值的异步命令 -- ✅ SendAsync(IAsyncCommand) 方法 - 处理 null 异步命令 - -**预计测试数**: 4 个 - -**测试文件**: `GFramework.Core.Tests/command/CommandBusTests.cs` - -**操作**: 在现有测试文件中补充异步测试方法 - -**状态**: ❌ 待补充 - ---- - -### 任务2: AbstractAsyncCommandTests.cs - -**源文件路径**: `GFramework.Core/command/AbstractAsyncCommand.cs` - -**优先级**: 🔴 高 - -**原因**: 异步命令基类没有任何单元测试,是核心功能 - -**需要测试的内容**: - -- ✅ 异步命令无返回值版本的基础实现 -- ✅ 异步命令有返回值版本的基础实现 -- ✅ ExecuteAsync 方法调用 -- ✅ ExecuteAsync 方法的异常处理 -- ✅ 上下文感知功能(SetContext, GetContext) -- ✅ 日志功能(Logger属性) -- ✅ 子类继承行为验证(两个版本) -- ✅ 命令执行前日志记录 -- ✅ 命令执行后日志记录 -- ✅ 错误情况下的日志记录 - -**预计测试数**: 10-12 个 - -**创建路径**: `GFramework.Core.Tests/command/AbstractAsyncCommandTests.cs` - -**状态**: ❌ 待创建 - ---- - -### 任务3: AsyncQueryBusTests.cs - -**源文件路径**: `GFramework.Core/query/AsyncQueryBus.cs` - -**优先级**: 🔴 高 - -**原因**: 异步查询总线是核心组件,需要完整的单元测试 - -**需要测试的内容**: - -- ✅ SendAsync 方法 - 正常查询发送 -- ✅ SendAsync 方法 - 空查询异常 -- ✅ 异步查询结果正确性 -- ✅ 不同返回类型的异步查询支持 -- ✅ 异步查询的异常处理 -- ✅ 异步查询的上下文传递 - -**预计测试数**: 6-8 个 - -**创建路径**: `GFramework.Core.Tests/query/AsyncQueryBusTests.cs` - -**状态**: ❌ 待创建 - ---- - -### 任务4: AbstractAsyncQueryTests.cs - -**源文件路径**: `GFramework.Core/query/AbstractAsyncQuery.cs` - -**优先级**: 🔴 高 - -**原因**: 异步查询基类没有任何单元测试,是核心功能 - -**需要测试的内容**: - -- ✅ 异步查询的基础实现 -- ✅ DoAsync 方法调用 -- ✅ DoAsync 方法的异常处理 -- ✅ 上下文感知功能(SetContext, GetContext) -- ✅ 日志功能(Logger属性) -- ✅ 子类继承行为验证 -- ✅ 查询执行前日志记录 -- ✅ 查询执行后日志记录 -- ✅ 返回值类型验证 -- ✅ 错误情况下的日志记录 - -**预计测试数**: 8-10 个 - -**创建路径**: `GFramework.Core.Tests/query/AbstractAsyncQueryTests.cs` - -**状态**: ❌ 待创建 - ---- - -### 任务5: AbstractContextUtilityTests.cs - -**源文件路径**: `GFramework.Core/utility/AbstractContextUtility.cs` - -**优先级**: 🔴 高 - -**原因**: 工具基类需要直接的单元测试以确保其基础功能正确性 - -**需要测试的内容**: - -- ✅ 抽象工具类实现 -- ✅ IContextUtility 接口实现 -- ✅ Init 方法调用 -- ✅ 日志初始化 -- ✅ 上下文感知功能(SetContext, GetContext) -- ✅ 子类继承行为 -- ✅ 工具初始化日志记录 -- ✅ 工具生命周期完整性 - -**预计测试数**: 6-8 个 - -**创建路径**: `GFramework.Core.Tests/utility/AbstractContextUtilityTests.cs` - -**状态**: ❌ 待创建 - ---- - -## 🟡 中优先级 - 常量验证(2个任务) - -### 任务6: ArchitectureConstantsTests.cs - -**源文件路径**: `GFramework.Core/architecture/ArchitectureConstants.cs` - -**优先级**: 🟡 中 - -**原因**: 验证架构相关的常量定义是否正确 - -**需要测试的内容**: - -- ✅ 常量值的正确性 -- ✅ 常量类型验证 -- ✅ 常量可访问性 -- ✅ 常量命名规范 -- ✅ 架构阶段定义常量 - -**预计测试数**: 3-5 个 - -**创建路径**: `GFramework.Core.Tests/architecture/ArchitectureConstantsTests.cs` - -**状态**: ❌ 待创建 - ---- - -### 任务7: GFrameworkConstantsTests.cs - -**源文件路径**: `GFramework.Core/constants/GFrameworkConstants.cs` - -**优先级**: 🟡 中 - -**原因**: 验证框架级别的常量定义 - -**需要测试的内容**: - -- ✅ 版本号常量格式正确性 -- ✅ 其他框架常量 -- ✅ 常量值正确性 -- ✅ 常量类型验证 -- ✅ 常量可访问性 - -**预计测试数**: 3-5 个 - -**创建路径**: `GFramework.Core.Tests/constants/GFrameworkConstantsTests.cs` - -**状态**: ❌ 待创建 - ---- - -## 📊 测试执行计划 - -### 第一批:异步核心功能(4个任务,预计 1.5小时) - -| 序号 | 测试任务 | 操作 | 预计测试数 | 优先级 | 预计时间 | -|----|------------------------------|----|-------|------|------| -| 1 | CommandBusTests.cs - 补充异步测试 | 补充 | 4 | 🔴 高 | 20分钟 | -| 2 | AbstractAsyncCommandTests.cs | 新建 | 10-12 | 🔴 高 | 30分钟 | -| 3 | AsyncQueryBusTests.cs | 新建 | 6-8 | 🔴 高 | 25分钟 | -| 4 | AbstractAsyncQueryTests.cs | 新建 | 8-10 | 🔴 高 | 25分钟 | - -**小计**: 28-34 个测试,约 1.5小时 - ---- - -### 第二批:工具基类(1个任务,预计 15分钟) - -| 序号 | 测试文件 | 操作 | 预计测试数 | 优先级 | 预计时间 | -|----|--------------------------------|----|-------|------|------| -| 5 | AbstractContextUtilityTests.cs | 新建 | 6-8 | 🔴 高 | 15分钟 | - -**小计**: 6-8 个测试 - ---- - -### 第三批:常量验证(2个任务,预计 20分钟) - -| 序号 | 测试文件 | 操作 | 预计测试数 | 优先级 | 预计时间 | -|----|-------------------------------|----|-------|------|------| -| 6 | ArchitectureConstantsTests.cs | 新建 | 3-5 | 🟡 中 | 10分钟 | -| 7 | GFrameworkConstantsTests.cs | 新建 | 3-5 | 🟡 中 | 10分钟 | - -**小计**: 6-10 个测试 - ---- - -## 📊 最终统计 - -| 批次 | 任务数 | 操作 | 预计测试数 | 预计时间 | -|----------|-------|-------------|-----------|---------| -| 第一批(异步) | 4 | 3新建+1补充 | 28-34 | 1.5小时 | -| 第二批(高优先) | 1 | 新建 | 6-8 | 15分钟 | -| 第三批(中优先) | 2 | 新建 | 6-10 | 20分钟 | -| **总计** | **7** | **6新建+1补充** | **40-54** | **2小时** | - ---- - -## 🎯 目标达成路径 - -### 当前状态(2026-01-18) - -- **现有测试数**: 496 个 -- **文件覆盖率**: 79.2% (38/48个文件有测试覆盖) -- **缺失测试**: 40-54 个 -- **已完成文件**: 38/48 -- **关键发现**: 异步命令和查询功能完全缺失测试 - -### 协程模块新增后状态(2026-01-21) - -- **现有测试数**: 496 + 61 = 557 个 -- **文件覆盖率**: 83.1% (49/59个文件有测试覆盖) -- **协程模块测试**: 61 个(已完成) -- **协程模块文件**: 11 个 -- **关键发现**: 协程系统测试覆盖率已达 100% - -### 补充测试完成后预计 - -- **预计测试数**: 557 + 40-54 = 597-611 个 -- **预计文件覆盖率**: ~95% (56/59) -- **代码行覆盖率**: 预计 90%+ (需通过覆盖率工具精确测量) - ---- - -## 📝 注意事项 - -### 注释规范 - -- ✅ 生成的测试类需要有注释说明这个测试类具体有哪些测试 -- ✅ 测试方法需要有注释说明具体测试的是什么 -- ✅ 对于复杂逻辑的测试方法,需要有标准的行注释说明逻辑,不要使用行尾注释 -- ✅ 对于类与方法的测试,需要标准的C#文档注释 - -### 测试隔离性 - -1. ✅ 每个测试文件使用独立的测试辅助类(TestXxxV2, TestXxxV3等) -2. ✅ 避免与现有测试类(TestSystem, TestModel)命名冲突 -3. ✅ 使用 `[SetUp]` 和 `[TearDown]` 确保测试隔离 -4. ✅ 必要时使用 `[NonParallelizable]` 特性 -5. ✅ 异步测试需要正确使用 `async/await` 模式 - -### 测试命名规范 - -- 测试类:`{Component}Tests` -- 测试方法:`{Scenario}_Should_{ExpectedOutcome}` -- 测试辅助类:`Test{Component}V{Version}` -- 异步测试方法建议包含 `Async` 关键字 - -### 构建和验证流程 - -1. 编写测试代码 -2. 运行 `dotnet build` 验证编译 -3. 运行 `dotnet test` 执行测试 -4. 检查测试通过率 -5. 修复失败或隔离性问题 - -### 异步测试最佳实践 - -1. **正确使用 async/await** - - 测试方法标记为 `async Task` - - 所有异步操作使用 `await` - - 不要使用 `.Result` 或 `.Wait()` 导致死锁 - -2. **异常测试** - - 使用 `Assert.ThrowsAsync` 测试异步异常 - - 确保异常在正确的位置抛出 - -3. **测试辅助类** - - 创建模拟的异步命令/查询类 - - 验证异步操作是否正确执行 - - 测试并发场景(如需要) - -### 代码覆盖率工具建议 - -建议添加 Coverlet 代码覆盖率工具以获得精确的覆盖率数据: - -```xml - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - -``` - -运行覆盖率命令: - -```bash -dotnet test --collect:"XPlat Code Coverage" -``` - ---- - -## 🔄 更新日志 - -| 日期 | 操作 | 说明 | -|------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 2026-01-16 | 初始创建 | 生成原始测试覆盖清单(包含错误) | -| 2026-01-18 | 全面更新(第1版) | 重新检查框架和测试,修正以下问题:
1. 删除不存在的ContextAwareStateMachineTests.cs
2. 更新实际测试数量为496个
3. 添加新增源文件
4. 修正文件覆盖率从41%提升至91.5%
5. 调整优先级,从26个减少到3个缺失测试文件 | -| 2026-01-18 | 全面更新(第2版) | 补充异步命令和异步查询测试计划:
1. 发现CommandBus已有SendAsync实现但无测试
2. 发现AbstractAsyncCommand、AsyncQueryBus、AbstractAsyncQuery无测试
3. 新增4个高优先级异步测试任务
4. 更新文件覆盖率从91.5%调整为79.2%(补充异步后)
5. 总测试数从40-54调整为目标 | -| 2026-01-21 | 协程模块测试完成 | 新增协程系统模块测试:
1. 新增协程源文件11个(GFramework.Core和GFramework.Core.Abstractions)
2. 创建协程测试文件4个,测试用例61个
3. 协程系统测试覆盖率达到100%
4. 更新文件覆盖率从79.2%提升至83.1%
5. 总测试数从496增加至557个 | - ---- - -## 📌 待确认事项 - -- [x] 确认优先级划分是否合理 -- [x] 确认执行计划是否可行 -- [x] 确认测试用例数量估算是否准确 -- [x] 确认测试隔离策略是否完整 -- [ ] 添加代码覆盖率工具配置 -- [ ] 确定是否需要补充间接测试为直接测试 - ---- - -## 🎉 成就解锁 - -### 已完成的测试覆盖 - -✅ **架构系统核心功能** - 147个测试覆盖 -✅ **事件系统完整功能** - 37个测试覆盖 -✅ **日志系统完整功能** - 69个测试覆盖 -✅ **IoC容器** - 21个测试覆盖 -✅ **状态机系统** - 33个测试覆盖 -✅ **对象池系统** - 6个测试覆盖 -✅ **属性系统** - 8个测试覆盖 -✅ **扩展方法** - 17个测试覆盖 -✅ **同步命令查询系统** - 通过集成测试覆盖 -✅ **模型系统** - 通过架构集成测试覆盖 -✅ **系统基类** - 通过架构集成测试覆盖 -✅ **协程系统完整功能** - 61个测试覆盖 - -### 待补充的异步功能 - -❌ **异步命令系统** - AbstractAsyncCommand、CommandBus.SendAsync -❌ **异步查询系统** - AsyncQueryBus、AbstractAsyncQuery -❌ **工具基类** - AbstractContextUtility -❌ **常量验证** - ArchitectureConstants、GFrameworkConstants - -### 测试质量指标 - -- **测试用例总数**: 557个 -- **文件级别覆盖率**: 83.1% -- **支持测试的.NET版本**: .NET 8.0, .NET 10.0 -- **测试框架**: NUnit 3.x -- **测试隔离性**: 良好 -- **测试组织结构**: 清晰(按模块分类) - ---- - -## 🚀 实施进度 - -### 协程系统测试(已完成) - -- [x] 协程系统测试模块 (61个测试) - - [x] CoroutineStateTests.cs (2个测试) - - [x] CoroutineHandleTests.cs (15个测试) - - [x] CoroutineHelperTests.cs (19个测试) - - [x] YieldInstructionTests.cs (25个测试) - - [x] CoroutineSchedulerTests.cs (25个测试,包含TestTimeSource辅助类) - -### 第一批:异步核心功能 - -- [ ] 任务1: CommandBusTests.cs - 补充异步测试 (4个测试) -- [ ] 任务2: AbstractAsyncCommandTests.cs (10-12个测试) -- [ ] 任务3: AsyncQueryBusTests.cs (6-8个测试) -- [ ] 任务4: AbstractAsyncQueryTests.cs (8-10个测试) - -### 第二批:工具基类 - -- [ ] 任务5: AbstractContextUtilityTests.cs (6-8个测试) - -### 第三批:常量验证 - -- [ ] 任务6: ArchitectureConstantsTests.cs (3-5个测试) -- [ ] 任务7: GFrameworkConstantsTests.cs (3-5个测试) - ---- - -**文档维护**: 请在完成每个测试任务后更新本文档的状态和实施进度 diff --git a/GFramework.Core/README.md b/GFramework.Core/README.md deleted file mode 100644 index c50b887..0000000 --- a/GFramework.Core/README.md +++ /dev/null @@ -1,507 +0,0 @@ -# GFramework.Core 核心框架 - -> 一个基于 CQRS、MVC 和事件驱动的轻量级游戏开发架构框架 - -## 目录 - -- [框架概述](#框架概述) -- [核心概念](#核心概念) -- [架构图](#架构图) -- [快速开始](#快速开始) -- [包说明](#包说明) -- [组件联动](#组件联动) -- [最佳实践](#最佳实践) -- [设计理念](#设计理念) - -## 框架概述 - -本框架是一个与平台无关的轻量级架构,它结合了多种经典设计模式: - -- **MVC 架构模式** - 清晰的层次划分 -- **CQRS 模式** - 命令查询职责分离 -- **IoC/DI** - 依赖注入和控制反转 -- **事件驱动** - 松耦合的组件通信 -- **响应式编程** - 可绑定属性和数据流 -- **阶段式生命周期管理** - 精细化的架构状态控制 - -**重要说明**:GFramework.Core 是与平台无关的核心模块,不包含任何 Godot 特定代码。Godot 集成功能在 GFramework.Godot 包中实现。 - -### 核心特性 - -- **清晰的分层架构** - Model、View、Controller、System、Utility 各司其职 -- **类型安全** - 基于泛型的组件获取和事件系统 -- **松耦合** - 通过事件和接口实现组件解耦 -- **易于测试** - 依赖注入和纯函数设计 -- **可扩展** - 基于接口的规则体系 -- **生命周期管理** - 自动的注册和注销机制 -- **模块化** - 支持架构模块安装 -- **平台无关** - Core 模块可以在任何 .NET 环境中使用 - -## 核心概念 - -### 五层架构 - -``` -┌─────────────────────────────────────────┐ -│ View / UI │ UI 层:用户界面 -├─────────────────────────────────────────┤ -│ Controller │ 控制层:处理用户输入 -├─────────────────────────────────────────┤ -│ System │ 逻辑层:业务逻辑 -├─────────────────────────────────────────┤ -│ Model │ 数据层:游戏状态 -├─────────────────────────────────────────┤ -│ Utility │ 工具层:无状态工具 -└─────────────────────────────────────────┘ -``` - -### 横切关注点 - -``` -Command ──┐ -Query ──┼──→ 跨层操作(修改/查询数据) -Event ──┘ -``` - -### 架构阶段 - -``` -初始化:Init → BeforeUtilityInit → AfterUtilityInit → BeforeModelInit → AfterModelInit → BeforeSystemInit → AfterSystemInit → Ready -销毁:Destroy → Destroying → Destroyed -``` - -## 架构图 - -### 整体架构 - -``` - ┌──────────────────┐ - │ Architecture │ ← 管理所有组件 - └────────┬─────────┘ - │ - ┌────────────────────┼────────────────────┐ - │ │ │ - ┌───▼────┐ ┌───▼────┐ ┌───▼─────┐ - │ Model │ │ System │ │ Utility │ - │ 层 │ │ 层 │ │ 层 │ - └───┬────┘ └───┬────┘ └────────┘ - │ │ - │ ┌─────────────┤ - │ │ │ - ┌───▼────▼───┐ ┌───▼──────┐ - │ Controller │ │ Command/ │ - │ 层 │ │ Query │ - └─────┬──────┘ └──────────┘ - │ - ┌─────▼─────┐ - │ View │ - │ UI │ - └───────────┘ -``` - -### 数据流向 - -``` -用户输入 → Controller → Command → System → Model → Event → Controller → View 更新 - -查询流程:Controller → Query → Model → 返回数据 -``` - -## 快速开始 - -本框架采用"约定优于配置"的设计理念,只需 4 步即可搭建完整的架构。 - -### 为什么需要这个框架? - -在传统开发中,我们经常遇到这些问题: - -- 代码耦合严重:UI 直接访问游戏逻辑,逻辑直接操作 UI -- 难以维护:修改一个功能需要改动多个文件 -- 难以测试:业务逻辑和 UI 混在一起无法独立测试 -- 难以复用:代码紧密耦合,无法在其他项目中复用 - -本框架通过清晰的分层解决这些问题。 - -### 1. 定义架构(Architecture) - -**作用**:Architecture 是整个应用的"中央调度器",负责管理所有组件的生命周期。 - -```csharp -using GFramework.Core.architecture; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 Model - 游戏数据 - RegisterModel(new PlayerModel()); - - // 注册 System - 业务逻辑 - RegisterSystem(new CombatSystem()); - - // 注册 Utility - 工具类 - RegisterUtility(new StorageUtility()); - } -} -``` - -**优势**: - -- **依赖注入**:组件通过上下文获取架构引用 -- **集中管理**:所有组件注册在一处,一目了然 -- **生命周期管理**:自动初始化和销毁 -- **平台无关**:可以在任何 .NET 环境中使用 - -### 2. 定义 Model(数据层) - -**作用**:Model 是应用的"数据库",只负责存储和管理状态。 - -```csharp -public class PlayerModel : AbstractModel -{ - // 使用 BindableProperty 实现响应式数据 - public BindableProperty Health { get; } = new(100); - public BindableProperty Gold { get; } = new(0); - - protected override void OnInit() - { - // Model 中可以监听自己的数据变化 - Health.Register(hp => - { - if (hp <= 0) this.SendEvent(new PlayerDiedEvent()); - }); - } -} - -// 也可以不使用 BindableProperty -public class PlayerModel : AbstractModel -{ - public int Health { get; private set; } - public int Gold { get; private set; } - - protected override void OnInit() - { - Health = 100; - Gold = 0; - } -} -``` - -**优势**: - -- **数据响应式**:BindableProperty 让数据变化自动通知监听者 -- **职责单一**:只存储数据,不包含复杂业务逻辑 -- **易于测试**:可以独立测试数据逻辑 - -### 3. 定义 System(业务逻辑层) - -**作用**:System 是应用的"大脑",处理所有业务逻辑。 - -```csharp -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - // System 通过事件驱动,响应游戏中的各种事件 - this.RegisterEvent(OnEnemyAttack); - } - - private void OnEnemyAttack(EnemyAttackEvent e) - { - var playerModel = this.GetModel(); - - // 处理业务逻辑:计算伤害、更新数据 - playerModel.Health.Value -= e.Damage; - - // 发送事件通知其他组件 - this.SendEvent(new PlayerTookDamageEvent { Damage = e.Damage }); - } -} -``` - -**优势**: - -- **事件驱动**:通过事件解耦,不同 System 之间松耦合 -- **可组合**:多个 System 协同工作,每个专注自己的领域 -- **易于扩展**:新增功能只需添加新的 System 和事件监听 - -### 4. 定义 Controller(控制层) - -**作用**:Controller 是"桥梁",连接 UI 和业务逻辑。 - -```csharp -public class PlayerController : IController -{ - // 通过依赖注入获取架构 - private readonly IArchitecture _architecture; - - public PlayerController(IArchitecture architecture) - { - _architecture = architecture; - } - - // 监听模型变化 - public void Initialize() - { - var playerModel = _architecture.GetModel(); - - // 数据绑定:Model 数据变化自动更新 UI - playerModel.Health.RegisterWithInitValue(OnHealthChanged); - } - - private void OnHealthChanged(int hp) - { - // 更新 UI 显示 - UpdateHealthDisplay(hp); - } - - private void UpdateHealthDisplay(int hp) { /* UI 更新逻辑 */ } -} -``` - -**优势**: - -- **自动更新 UI**:通过 BindableProperty,数据变化自动反映到界面 -- **分离关注点**:UI 逻辑和业务逻辑完全分离 -- **易于测试**:可以通过依赖注入模拟架构进行测试 - -### 完成!现在你有了一个完整的架构 - -这 4 步完成后,你就拥有了: - -- **清晰的数据层**(Model) -- **独立的业务逻辑**(System) -- **灵活的控制层**(Controller) -- **统一的生命周期管理**(Architecture) - -### 下一步该做什么? - -1. **添加 Command**:封装用户操作(如购买物品、使用技能) -2. **添加 Query**:封装数据查询(如查询背包物品数量) -3. **添加更多 System**:如任务系统、背包系统、商店系统 -4. **使用 Utility**:添加工具类(如存档工具、数学工具) -5. **使用模块**:通过 IArchitectureModule 扩展架构功能 - -## 包说明 - -| 包名 | 职责 | 文档 | -|------------------|-----------------|------------------------------| -| **architecture** | 架构核心,管理所有组件生命周期 | [查看](architecture/README.md) | -| **constants** | 框架常量定义 | 本文档 | -| **model** | 数据模型层,存储状态 | [查看](model/README.md) | -| **system** | 业务逻辑层,处理业务规则 | [查看](system/README.md) | -| **controller** | 控制器层,连接视图和逻辑 | (在 Abstractions 中) | -| **utility** | 工具类层,提供无状态工具 | [查看](utility/README.md) | -| **command** | 命令模式,封装写操作 | [查看](command/README.md) | -| **query** | 查询模式,封装读操作 | [查看](query/README.md) | -| **events** | 事件系统,组件间通信 | [查看](events/README.md) | -| **property** | 可绑定属性,响应式编程 | [查看](property/README.md) | -| **ioc** | IoC 容器,依赖注入 | [查看](ioc/README.md) | -| **rule** | 规则接口,定义组件约束 | [查看](rule/README.md) | -| **extensions** | 扩展方法,简化 API 调用 | [查看](extensions/README.md) | -| **logging** | 日志系统,记录运行日志 | [查看](logging/README.md) | -| **environment** | 环境接口,提供运行环境信息 | [查看](environment/README.md) | - -## 组件联动 - -### 1. 初始化流程 - -``` -创建 Architecture 实例 - └─> Init() - ├─> RegisterModel → Model.SetContext() → Model.Init() - ├─> RegisterSystem → System.SetContext() → System.Init() - └─> RegisterUtility → Utility 注册到容器 -``` - -### 2. Command 执行流程 - -``` -Controller.SendCommand(command) - └─> command.Execute() - └─> command.OnDo() // 子类实现 - ├─> GetModel() // 获取数据 - ├─> 修改 Model 数据 - └─> SendEvent() // 发送事件 -``` - -### 3. Event 传播流程 - -``` -组件.SendEvent(event) - └─> TypeEventSystem.Send(event) - └─> 通知所有订阅者 - ├─> Controller 响应 → 更新 UI - ├─> System 响应 → 执行逻辑 - └─> Model 响应 → 更新状态 -``` - -### 4. BindableProperty 数据绑定 - -``` -Model: BindableProperty Health = new(100); -Controller: Health.RegisterWithInitValue(hp => UpdateUI(hp)) -修改值: Health.Value = 50 → 触发所有回调 → 更新 UI -``` - -## 最佳实践 - -### 1. 分层职责原则 - -每一层都有明确的职责边界,遵循这些原则能让代码更清晰、更易维护。 - -**Model 层**: - -```csharp -// 好:只存储数据 -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - protected override void OnInit() { } -} - -// 坏:包含业务逻辑 -public class PlayerModel : AbstractModel -{ - public void TakeDamage(int damage) // 业务逻辑应在 System - { - Health.Value -= damage; - if (Health.Value <= 0) Die(); - } -} -``` - -**System 层**: - -```csharp -// 好:处理业务逻辑 -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnAttack); - } - - private void OnAttack(AttackEvent e) - { - var target = this.GetModel(); - int finalDamage = CalculateDamage(e.BaseDamage, target); - target.Health.Value -= finalDamage; - } -} -``` - -### 2. 通信方式选择指南 - -| 通信方式 | 使用场景 | 优势 | -|----------------------|-----------|----------| -| **Command** | 用户操作、修改状态 | 可撤销、可记录 | -| **Query** | 查询数据、检查条件 | 明确只读意图 | -| **Event** | 通知其他组件 | 松耦合、可扩展 | -| **BindableProperty** | 数据变化通知 | 自动化、不会遗漏 | - -### 3. 生命周期管理 - -**为什么需要注销?** - -忘记注销监听器会导致: - -- **内存泄漏**:对象无法被 GC 回收 -- **逻辑错误**:已销毁的对象仍在响应事件 - -```csharp -// 使用 UnRegisterList 统一管理 -private IUnRegisterList _unregisterList = new UnRegisterList(); - -public void Initialize() -{ - this.RegisterEvent(OnEvent1) - .AddToUnregisterList(_unregisterList); - - model.Property.Register(OnPropertyChanged) - .AddToUnregisterList(_unregisterList); -} - -public void Cleanup() -{ - _unregisterList.UnRegisterAll(); -} -``` - -### 4. 性能优化技巧 - -```csharp -// 低效:每帧都查询 -var model = _architecture.GetModel(); // 频繁调用 - -// 高效:缓存引用 -private PlayerModel _playerModel; - -public void Initialize() -{ - _playerModel = _architecture.GetModel(); // 只查询一次 -} -``` - -## 设计理念 - -框架的设计遵循 SOLID 原则和经典设计模式。 - -### 1. 单一职责原则(SRP) - -- **Model**:只负责存储数据 -- **System**:只负责处理业务逻辑 -- **Controller**:只负责协调和输入处理 -- **Utility**:只负责提供工具方法 - -### 2. 开闭原则(OCP) - -- 通过**事件系统**添加新功能,无需修改现有代码 -- 新的 System 可以监听现有事件,插入自己的逻辑 - -### 3. 依赖倒置原则(DIP) - -- 所有组件通过接口交互 -- 通过 IoC 容器注入依赖 -- 易于替换实现和编写测试 - -### 4. 接口隔离原则(ISP) - -```csharp -// 小而专注的接口 -public interface ICanGetModel : IBelongToArchitecture { } -public interface ICanSendCommand : IBelongToArchitecture { } -public interface ICanRegisterEvent : IBelongToArchitecture { } - -// 组合需要的能力 -public interface IController : - ICanGetModel, - ICanSendCommand, - ICanRegisterEvent { } -``` - -### 5. 组合优于继承 - -通过接口组合获得能力,而不是通过继承。 - -### 框架核心设计模式 - -| 设计模式 | 应用位置 | 解决的问题 | 带来的好处 | -|-----------|------------|----------|--------| -| **工厂模式** | IoC 容器 | 组件的创建和管理 | 解耦创建逻辑 | -| **观察者模式** | Event 系统 | 组件间的通信 | 松耦合通信 | -| **命令模式** | Command | 封装操作请求 | 支持撤销重做 | -| **策略模式** | System | 不同的业务逻辑 | 易于切换策略 | -| **依赖注入** | 整体架构 | 组件间的依赖 | 自动管理依赖 | -| **模板方法** | Abstract 类 | 定义算法骨架 | 统一流程规范 | - -### 平台无关性 - -- **GFramework.Core**:纯 .NET 库,无任何平台特定代码 -- **GFramework.Godot**:Godot 特定实现,包含 Node 扩展、GodotLogger 等 -- 可以轻松将 Core 框架移植到其他平台(Unity、.NET MAUI 等) - ---- - -**版本**: 1.0.0 -**许可证**: Apache 2.0 diff --git a/GFramework.Game/README.md b/GFramework.Game/README.md deleted file mode 100644 index 218c2d6..0000000 --- a/GFramework.Game/README.md +++ /dev/null @@ -1,1406 +0,0 @@ -# GFramework.Game - -> 游戏特定功能抽象 - 为游戏开发提供专门的工具和系统 - -GFramework.Game 是 GFramework 框架的游戏特定功能模块,提供了游戏开发中常用的抽象和工具,包括资产管理、存储系统、序列化等核心功能。 - -## 📋 目录 - -- [概述](#概述) -- [核心特性](#核心特性) -- [架构模块系统](#架构模块系统) -- [资产管理](#资产管理) -- [存储系统](#存储系统) -- [序列化系统](#序列化系统) -- [使用示例](#使用示例) -- [最佳实践](#最佳实践) -- [性能特性](#性能特性) - -## 概述 - -GFramework.Game 为游戏开发提供了专门的功能模块,与 GFramework.Core 的平台无关特性完美结合,为游戏项目提供了一整套完整的解决方案。 - -### 核心设计理念 - -- **游戏导向**:专门针对游戏开发场景设计 -- **模块化架构**:可插拔的模块系统,按需组合 -- **数据持久化**:完善的存档和数据管理方案 -- **资源管理**:高效的资源加载和管理机制 - -## 核心特性 - -### 🏗️ 模块化架构 - -- **AbstractModule**:可重用的架构模块基类 -- **生命周期管理**:与框架生命周期深度集成 -- **依赖注入**:模块间的依赖自动管理 -- **配置驱动**:灵活的模块配置系统 - -### 📦 资产管理 - -- **统一资源目录**:集中化的资源注册和查询 -- **类型安全**:编译时类型检查和泛型支持 -- **重复检测**:自动检测资源重复注册 -- **映射支持**:灵活的资源映射和别名系统 - -### 💾 存储系统 - -- **分层存储**:命名空间支持的存储隔离 -- **多格式支持**:JSON、二进制等多种存储格式 -- **异步操作**:完整的异步存储 API -- **版本兼容**:存档版本管理和迁移支持 - -### 🔄 序列化系统 - -- **JSON 集成**:基于 Newtonsoft.Json 的序列化 -- **自定义序列化**:支持自定义序列化逻辑 -- **性能优化**:序列化缓存和优化策略 -- **类型安全**:强类型的序列化和反序列化 - -## 架构模块系统 - -### AbstractModule 基础使用 - -```csharp -using GFramework.Game.architecture; - -public class AudioModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - // 注册音频相关系统 - architecture.RegisterSystem(new AudioSystem()); - architecture.RegisterSystem(new MusicSystem()); - - // 注册音频工具 - architecture.RegisterUtility(new AudioUtility()); - architecture.RegisterUtility(new MusicUtility()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.BeforeModelInit: - // 在模型初始化前准备音频资源 - PreloadAudioResources(); - break; - - case ArchitecturePhase.Ready: - // 架构准备就绪,开始播放背景音乐 - StartBackgroundMusic(); - break; - - case ArchitecturePhase.Destroying: - // 清理音频资源 - CleanupAudioResources(); - break; - } - } - - private void PreloadAudioResources() - { - // 预加载音频资源 - var audioUtility = architecture.GetUtility(); - audioUtility.PreloadAudio("background_music"); - audioUtility.PreloadAudio("shoot_sound"); - audioUtility.PreloadAudio("explosion_sound"); - } - - private void StartBackgroundMusic() - { - var musicSystem = architecture.GetSystem(); - musicSystem.PlayBackgroundMusic("background_music"); - } - - private void CleanupAudioResources() - { - var audioUtility = architecture.GetUtility(); - audioUtility.UnloadAllAudio(); - } -} -``` - -### 复杂模块示例 - -```csharp -public class SaveModule : AbstractModule -{ - private ISaveSystem _saveSystem; - private IDataMigrationManager _migrationManager; - - public override void Install(IArchitecture architecture) - { - // 注册存储相关组件 - _saveSystem = new SaveSystem(); - architecture.RegisterUtility(_saveSystem); - - _migrationManager = new DataMigrationManager(); - architecture.RegisterUtility(_migrationManager); - - // 注册数据版本管理 - architecture.RegisterSystem(new SaveDataVersionSystem()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.AfterModelInit: - // 在模型初始化后设置数据迁移 - SetupDataMigrations(); - break; - - case ArchitecturePhase.Ready: - // 尝试自动加载存档 - TryAutoLoadSave(); - break; - } - } - - private void SetupDataMigrations() - { - // 设置数据版本迁移 - _migrationManager.RegisterMigration(1, 2, MigratePlayerDataV1ToV2); - _migrationManager.RegisterMigration(2, 3, MigratePlayerDataV2ToV3); - } - - private void TryAutoLoadSave() - { - if (_saveSystem.HasAutoSave()) - { - _saveSystem.LoadAutoSave(); - } - } - - private PlayerData MigratePlayerDataV1ToV2(PlayerData v1Data) - { - return new PlayerData - { - // 迁移逻辑 - Name = v1Data.Name, - Health = v1Data.Health, - // 新增字段 - MaxHealth = 100, - Level = 1 - }; - } - - private PlayerData MigratePlayerDataV2ToV3(PlayerData v2Data) - { - return new PlayerData - { - // 迁移逻辑 - Name = v2Data.Name, - Health = v2Data.Health, - MaxHealth = v2Data.MaxHealth, - Level = v2Data.Level, - // 新增字段 - Experience = 0, - Skills = new List() - }; - } -} -``` - -### 模块配置 - -```csharp -public class ModuleConfig -{ - public string SaveDirectory { get; set; } = "saves"; - public int AutoSaveInterval { get; set; } = 300; // 5分钟 - public bool EnableDataCompression { get; set; } = true; - public int MaxSaveSlots { get; set; } = 10; -} - -public class ConfigurableSaveModule : AbstractModule -{ - private ModuleConfig _config; - - public ConfigurableSaveModule(ModuleConfig config) - { - _config = config; - } - - public override void Install(IArchitecture architecture) - { - var saveSystem = new SaveSystem(_config); - architecture.RegisterUtility(saveSystem); - - // 配置自动保存 - if (_config.AutoSaveInterval > 0) - { - architecture.RegisterSystem(new AutoSaveSystem(_config.AutoSaveInterval)); - } - } -} -``` - -## 资产管理 - -### AbstractAssetCatalogUtility 基础使用 - -```csharp -using GFramework.Game.assets; - -public class GameAssetCatalog : AbstractAssetCatalogUtility -{ - public override void Initialize() - { - base.Initialize(); - - // 注册场景资产 - RegisterSceneUnit("Player", "res://scenes/Player.tscn"); - RegisterSceneUnit("Enemy", "res://scenes/Enemy.tscn"); - RegisterSceneUnit("Bullet", "res://scenes/Bullet.tscn"); - - // 注册场景页面 - RegisterScenePage("MainMenu", "res://ui/MainMenu.tscn"); - RegisterScenePage("GameUI", "res://ui/GameUI.tscn"); - RegisterScenePage("PauseMenu", "res://ui/PauseMenu.tscn"); - - // 注册通用资产 - RegisterAsset("PlayerTexture", "res://textures/player.png"); - RegisterAsset("EnemyTexture", "res://textures/enemy.png"); - RegisterAsset("ShootSound", "res://audio/shoot.wav"); - RegisterAsset("ExplosionSound", "res://audio/explosion.wav"); - } - - // 自定义资产验证 - protected override bool ValidateAsset(string key, string path) - { - if (!FileAccess.FileExists(path)) - { - GD.PrintErr($"Asset file not found: {path}"); - return false; - } - - return true; - } - - // 资产加载完成回调 - protected override void OnAssetLoaded(string key, object asset) - { - GD.Print($"Asset loaded: {key}"); - - // 对特定资产进行额外处理 - if (key == "PlayerTexture") - { - var texture = (Texture2D)asset; - // 预处理纹理... - } - } -} -``` - -### 资产映射系统 - -```csharp -public class AssetMapping -{ - public string Key { get; set; } - public string Path { get; set; } - public Type Type { get; set; } - public Dictionary Metadata { get; set; } = new(); -} - -public class AdvancedAssetCatalog : AbstractAssetCatalogUtility -{ - public override void Initialize() - { - base.Initialize(); - - // 使用映射对象注册资产 - RegisterAsset(new AssetMapping - { - Key = "Player", - Path = "res://scenes/Player.tscn", - Type = typeof(PackedScene), - Metadata = new Dictionary - { - ["category"] = "character", - ["tags"] = new[] { "player", "hero", "controlled" }, - ["health"] = 100, - ["speed"] = 5.0f - } - }); - - // 批量注册 - RegisterAssetsFromDirectory("res://textures/", "*.png", "texture"); - RegisterAssetsFromDirectory("res://audio/", "*.wav", "sound"); - } - - private void RegisterAssetsFromDirectory(string directory, string pattern, string prefix) - { - var dir = DirAccess.Open(directory); - if (dir == null) return; - - dir.ListDirBegin(); - var fileName = dir.GetNext(); - - while (!string.IsNullOrEmpty(fileName)) - { - if (fileName.EndsWith(pattern.Substring(1))) - { - var key = $"{prefix}{Path.GetFileNameWithoutExtension(fileName)}"; - var path = Path.Combine(directory, fileName); - - RegisterAsset(key, path); - } - - fileName = dir.GetNext(); - } - - dir.ListDirEnd(); - } -} -``` - -### 资产工厂模式 - -```csharp -public interface IAssetFactory -{ - T Create(string key); - bool CanCreate(string key); -} - -public class PlayerFactory : IAssetFactory -{ - private readonly AbstractAssetCatalogUtility _catalog; - - public PlayerFactory(AbstractAssetCatalogUtility catalog) - { - _catalog = catalog; - } - - public Player Create(string key) - { - var scene = _catalog.GetScene(key); - var player = scene.Instantiate(); - - // 配置玩家 - player.Health = GetPlayerHealth(key); - player.Speed = GetPlayerSpeed(key); - - return player; - } - - public bool CanCreate(string key) - { - return _catalog.HasScene(key) && key.StartsWith("Player"); - } - - private int GetPlayerHealth(string key) - { - var metadata = _catalog.GetAssetMetadata(key); - return metadata?.GetValueOrDefault("health", 100) ?? 100; - } - - private float GetPlayerSpeed(string key) - { - var metadata = _catalog.GetAssetMetadata(key); - return metadata?.GetValueOrDefault("speed", 5.0f) ?? 5.0f; - } -} -``` - -## 存储系统 - -### ScopedStorage 分层存储 - -```csharp -using GFramework.Game.storage; - -public class GameDataManager -{ - private readonly IStorage _rootStorage; - private readonly IStorage _playerStorage; - private readonly IStorage _saveStorage; - - public GameDataManager(IStorage rootStorage) - { - _rootStorage = rootStorage; - - // 创建分层存储 - _playerStorage = new ScopedStorage(rootStorage, "player"); - _saveStorage = new ScopedStorage(rootStorage, "saves"); - } - - public void SavePlayerData(string playerId, PlayerData data) - { - _playerStorage.Write($"{playerId}/profile", data); - _playerStorage.Write($"{playerId}/inventory", data.Inventory); - _playerStorage.Write($"{playerId}/stats", data.Stats); - } - - public PlayerData LoadPlayerData(string playerId) - { - var profile = _playerStorage.Read($"{playerId}/profile"); - var inventory = _playerStorage.Read($"{playerId}/inventory", new Inventory()); - var stats = _playerStorage.Read($"{playerId}/stats", new PlayerStats()); - - return new PlayerData - { - Profile = profile, - Inventory = inventory, - Stats = stats - }; - } - - public void SaveGame(int slotId, SaveData data) - { - _saveStorage.Write($"slot_{slotId}", data); - _saveStorage.Write($"slot_{slotId}/timestamp", DateTime.Now); - _saveStorage.Write($"slot_{slotId}/version", data.Version); - } - - public SaveData LoadGame(int slotId) - { - return _saveStorage.Read($"slot_{slotId}"); - } - - public List GetSaveSlotInfos() - { - var infos = new List(); - - for (int i = 1; i <= 10; i++) - { - var timestamp = _saveStorage.Read($"slot_{i}/timestamp"); - var version = _saveStorage.Read($"slot_{i}/version"); - - if (timestamp != default) - { - infos.Add(new SaveSlotInfo - { - SlotId = i, - Timestamp = timestamp, - Version = version - }); - } - } - - return infos; - } -} -``` - -### 自定义存储实现 - -```csharp -public class EncryptedStorage : IStorage -{ - private readonly IStorage _innerStorage; - private readonly IEncryptor _encryptor; - - public EncryptedStorage(IStorage innerStorage, IEncryptor encryptor) - { - _innerStorage = innerStorage; - _encryptor = encryptor; - } - - public void Write(string key, T data) - { - var json = JsonConvert.SerializeObject(data); - var encrypted = _encryptor.Encrypt(json); - _innerStorage.Write(key, encrypted); - } - - public T Read(string key, T defaultValue = default) - { - var encrypted = _innerStorage.Read(key); - if (string.IsNullOrEmpty(encrypted)) - return defaultValue; - - var json = _encryptor.Decrypt(encrypted); - return JsonConvert.DeserializeObject(json); - } - - public async Task WriteAsync(string key, T data) - { - var json = JsonConvert.SerializeObject(data); - var encrypted = _encryptor.Encrypt(json); - await _innerStorage.WriteAsync(key, encrypted); - } - - public async Task ReadAsync(string key, T defaultValue = default) - { - var encrypted = await _innerStorage.ReadAsync(key); - if (string.IsNullOrEmpty(encrypted)) - return defaultValue; - - var json = _encryptor.Decrypt(encrypted); - return JsonConvert.DeserializeObject(json); - } - - public bool Has(string key) - { - return _innerStorage.Has(key); - } - - public void Delete(string key) - { - _innerStorage.Delete(key); - } - - public void Clear() - { - _innerStorage.Clear(); - } -} -``` - -### 存储缓存层 - -```csharp -public class CachedStorage : IStorage -{ - private readonly IStorage _innerStorage; - private readonly Dictionary _cache; - private readonly Dictionary _cacheTimestamps; - private readonly TimeSpan _cacheExpiry; - - public CachedStorage(IStorage innerStorage, TimeSpan cacheExpiry = default) - { - _innerStorage = innerStorage; - _cacheExpiry = cacheExpiry == default ? TimeSpan.FromMinutes(5) : cacheExpiry; - _cache = new Dictionary(); - _cacheTimestamps = new Dictionary(); - } - - public T Read(string key, T defaultValue = default) - { - if (_cache.TryGetValue(key, out var cachedValue) && - !IsCacheExpired(key)) - { - return (T)cachedValue; - } - - var value = _innerStorage.Read(key, defaultValue); - UpdateCache(key, value); - - return value; - } - - public void Write(string key, T data) - { - _innerStorage.Write(key, data); - UpdateCache(key, data); - } - - public async Task ReadAsync(string key, T defaultValue = default) - { - if (_cache.TryGetValue(key, out var cachedValue) && - !IsCacheExpired(key)) - { - return (T)cachedValue; - } - - var value = await _innerStorage.ReadAsync(key, defaultValue); - UpdateCache(key, value); - - return value; - } - - public async Task WriteAsync(string key, T data) - { - await _innerStorage.WriteAsync(key, data); - UpdateCache(key, data); - } - - public bool Has(string key) - { - return _cache.ContainsKey(key) || _innerStorage.Has(key); - } - - public void Delete(string key) - { - _cache.Remove(key); - _cacheTimestamps.Remove(key); - _innerStorage.Delete(key); - } - - public void Clear() - { - _cache.Clear(); - _cacheTimestamps.Clear(); - _innerStorage.Clear(); - } - - public void ClearCache() - { - _cache.Clear(); - _cacheTimestamps.Clear(); - } - - private bool IsCacheExpired(string key) - { - if (!_cacheTimestamps.TryGetValue(key, out var timestamp)) - return true; - - return DateTime.Now - timestamp > _cacheExpiry; - } - - private void UpdateCache(string key, T value) - { - _cache[key] = value; - _cacheTimestamps[key] = DateTime.Now; - } -} -``` - -## 序列化系统 - -### JsonSerializer 使用 - -```csharp -using GFramework.Game.serializer; - -public class GameDataSerializer -{ - private readonly JsonSerializer _serializer; - - public GameDataSerializer() - { - _serializer = new JsonSerializer(new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Populate, - TypeNameHandling = TypeNameHandling.Auto - }); - - // 自定义转换器 - _serializer.Converters.Add(new Vector2JsonConverter()); - _serializer.Converters.Add(new ColorJsonConverter()); - _serializer.Converters.Add(new GodotResourceJsonConverter()); - } - - public string Serialize(T data) - { - return _serializer.Serialize(data); - } - - public T Deserialize(string json) - { - return _serializer.Deserialize(json); - } - - public void SerializeToFile(string path, T data) - { - var json = Serialize(data); - FileAccess.Open(path, FileAccess.ModeFlags.Write).StoreString(json); - } - - public T DeserializeFromFile(string path) - { - if (!FileAccess.FileExists(path)) - return default(T); - - var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); - var json = file.GetAsText(); - file.Close(); - - return Deserialize(json); - } -} -``` - -### 自定义 JSON 转换器 - -```csharp -public class Vector2JsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("x"); - writer.WriteValue(value.X); - writer.WritePropertyName("y"); - writer.WriteValue(value.Y); - writer.WriteEndObject(); - } - - public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return Vector2.Zero; - - var obj = serializer.Deserialize>(reader); - return new Vector2(obj["x"], obj["y"]); - } -} - -public class ColorJsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Color value, JsonSerializer serializer) - { - writer.WriteValue(value.ToHtml()); - } - - public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return Colors.White; - - var html = reader.Value.ToString(); - return Color.FromHtml(html); - } -} - -public class GodotResourceJsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Resource value, JsonSerializer serializer) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - writer.WriteStartObject(); - writer.WritePropertyName("$type"); - writer.WriteValue(value.GetType().Name); - writer.WritePropertyName("path"); - writer.WriteValue(value.ResourcePath); - writer.WriteEndObject(); - } - - public override Resource ReadJson(JsonReader reader, Type objectType, Resource existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return null; - - var obj = serializer.Deserialize>(reader); - var path = obj["path"]; - - return GD.Load(path); - } -} -``` - -### 版本化序列化 - -```csharp -public class VersionedData -{ - public int Version { get; set; } - public Dictionary Data { get; set; } = new(); -} - -public class VersionedSerializer -{ - private readonly Dictionary _typeVersions = new(); - private readonly Dictionary _migrations = new(); - - public void RegisterVersion(int version) - { - _typeVersions[typeof(T)] = version; - } - - public void RegisterMigration(int fromVersion, int toVersion, IDataMigration migration) - { - var key = GetMigrationKey(typeof(T), fromVersion, toVersion); - _migrations[key] = migration; - } - - public string Serialize(T data) - { - var versioned = new VersionedData - { - Version = _typeVersions.GetValueOrDefault(typeof(T), 1), - Data = new Dictionary - { - ["type"] = typeof(T).Name, - ["data"] = JsonConvert.SerializeObject(data) - } - }; - - return JsonConvert.SerializeObject(versioned); - } - - public T Deserialize(string json) - { - var versioned = JsonConvert.DeserializeObject(json); - var currentVersion = _typeVersions.GetValueOrDefault(typeof(T), 1); - - // 迁移数据到当前版本 - var dataJson = MigrateData(versioned.Data["data"] as string, versioned.Version, currentVersion); - - return JsonConvert.DeserializeObject(dataJson); - } - - private string MigrateData(string dataJson, int fromVersion, int toVersion) - { - var currentData = dataJson; - var currentVersion = fromVersion; - - while (currentVersion < toVersion) - { - var migrationKey = GetMigrationKey(typeof(T), currentVersion, currentVersion + 1); - - if (_migrations.TryGetValue(migrationKey, out var migration)) - { - currentData = migration.Migrate(currentData); - currentVersion++; - } - else - { - throw new InvalidOperationException($"No migration found from version {currentVersion} to {currentVersion + 1}"); - } - } - - return currentData; - } - - private int GetMigrationKey(Type type, int fromVersion, int toVersion) - { - return HashCode.Combine(type.Name, fromVersion, toVersion); - } -} - -public interface IDataMigration -{ - string Migrate(string data); -} - -public interface IDataMigration : IDataMigration -{ - T Migrate(T data); -} -``` - -## 使用示例 - -### 完整的游戏数据管理系统 - -```csharp -// 1. 定义数据模型 -public class GameProfile -{ - public string PlayerName { get; set; } - public DateTime LastPlayed { get; set; } - public int TotalPlayTime { get; set; } - public List UnlockedAchievements { get; set; } = new(); -} - -public class GameSettings -{ - public float MasterVolume { get; set; } = 1.0f; - public float MusicVolume { get; set; } = 0.8f; - public float SFXVolume { get; set; } = 0.9f; - public GraphicsSettings Graphics { get; set; } = new(); - public InputSettings Input { get; set; } = new(); -} - -// 2. 创建游戏管理器 -[ContextAware] -[Log] -public partial class GameManager : Node, IController -{ - private GameAssetCatalog _assetCatalog; - private GameDataManager _dataManager; - private GameDataSerializer _serializer; - - protected override void OnInit() - { - // 初始化资产目录 - _assetCatalog = new GameAssetCatalog(); - _assetCatalog.Initialize(); - - // 初始化存储系统 - var rootStorage = new FileStorage("user://data/"); - var cachedStorage = new CachedStorage(rootStorage); - _dataManager = new GameDataManager(cachedStorage); - - // 初始化序列化器 - _serializer = new GameDataSerializer(); - - Logger.Info("Game manager initialized"); - } - - public void StartNewGame(string playerName) - { - Logger.Info($"Starting new game for player: {playerName}"); - - // 创建新的游戏档案 - var profile = new GameProfile - { - PlayerName = playerName, - LastPlayed = DateTime.Now, - TotalPlayTime = 0 - }; - - _dataManager.SavePlayerData("current", profile); - - // 加载初始场景 - LoadInitialScene(); - - Context.SendEvent(new NewGameStartedEvent { PlayerName = playerName }); - } - - public void LoadGame(int slotId) - { - Logger.Info($"Loading game from slot {slotId}"); - - try - { - var saveData = _dataManager.LoadGame(slotId); - if (saveData != null) - { - // 恢复游戏状态 - RestoreGameState(saveData); - - Context.SendEvent(new GameLoadedEvent { SlotId = slotId }); - Logger.Info("Game loaded successfully"); - } - else - { - Logger.Warning($"No save data found in slot {slotId}"); - Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId }); - } - } - catch (Exception ex) - { - Logger.Error($"Failed to load game: {ex.Message}"); - Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message }); - } - } - - public void SaveGame(int slotId) - { - Logger.Info($"Saving game to slot {slotId}"); - - try - { - var saveData = CreateSaveData(); - _dataManager.SaveGame(slotId, saveData); - - Context.SendEvent(new GameSavedEvent { SlotId = slotId }); - Logger.Info("Game saved successfully"); - } - catch (Exception ex) - { - Logger.Error($"Failed to save game: {ex.Message}"); - Context.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message }); - } - } - - private void LoadInitialScene() - { - var playerScene = _assetCatalog.GetScene("Player"); - var player = playerScene.Instantiate(); - - var gameWorldScene = _assetCatalog.GetScene("GameWorld"); - var gameWorld = gameWorldScene.Instantiate(); - - AddChild(gameWorld); - gameWorld.AddChild(player); - } - - private void RestoreGameState(SaveData saveData) - { - // 恢复玩家位置 - var playerScene = _assetCatalog.GetScene("Player"); - var player = playerScene.Instantiate(); - player.Position = saveData.PlayerPosition; - - // 恢复游戏世界 - var gameWorldScene = _assetCatalog.GetScene("GameWorld"); - var gameWorld = gameWorldScene.Instantiate(); - - AddChild(gameWorld); - gameWorld.AddChild(player); - - // 恢复其他游戏状态 - Context.GetModel().Health.Value = saveData.PlayerHealth; - Context.GetModel().CurrentLevel.Value = saveData.CurrentLevel; - Context.GetModel().LoadFromData(saveData.Inventory); - } - - private SaveData CreateSaveData() - { - var player = GetTree().CurrentScene.FindChild("Player"); - - return new SaveData - { - PlayerPosition = player?.Position ?? Vector2.Zero, - PlayerHealth = Context.GetModel().Health.Value, - CurrentLevel = Context.GetModel().CurrentLevel.Value, - Inventory = Context.GetModel().GetData(), - Timestamp = DateTime.Now, - Version = 1 - }; - } -} -``` - -### 自动保存系统 - -```csharp -public class AutoSaveSystem : AbstractSystem -{ - private Timer _autoSaveTimer; - private int _currentSaveSlot; - private float _autoSaveInterval; - - public AutoSaveSystem(float intervalMinutes = 5.0f) - { - _autoSaveInterval = intervalMinutes * 60.0f; - } - - protected override void OnInit() - { - _autoSaveTimer = new Timer(); - _autoSaveTimer.WaitTime = _autoSaveInterval; - _autoSaveTimer.Timeout += OnAutoSave; - _autoSaveTimer.Autostart = true; - - AddChild(_autoSaveTimer); - - // 监听重要事件,立即自动保存 - this.RegisterEvent(OnImportantEvent); - this.RegisterEvent(OnImportantEvent); - this.RegisterEvent(OnImportantEvent); - - Logger.Info("Auto-save system initialized"); - } - - protected override void OnDestroy() - { - _autoSaveTimer?.Stop(); - - // 最后一次自动保存 - PerformAutoSave(); - } - - private void OnAutoSave() - { - PerformAutoSave(); - Logger.Debug("Periodic auto-save completed"); - } - - private void OnImportantEvent(IEvent e) - { - Logger.Info($"Important event {e.GetType().Name}, triggering auto-save"); - PerformAutoSave(); - } - - private void PerformAutoSave() - { - try - { - var saveData = CreateAutoSaveData(); - - // 保存到自动存档槽 - var storage = Context.GetUtility(); - storage.Write("autosave", saveData); - storage.Write("autosave/timestamp", DateTime.Now); - - Logger.Debug("Auto-save completed successfully"); - } - catch (Exception ex) - { - Logger.Error($"Auto-save failed: {ex.Message}"); - } - } - - private SaveData CreateAutoSaveData() - { - return new SaveData - { - // ... 创建保存数据 - }; - } -} -``` - -## 最佳实践 - -### 🏗️ 数据模型设计 - -#### 1. 版本化数据结构 - -```csharp -// 好的做法:版本化数据模型 -[Serializable] -public class PlayerDataV2 -{ - public int Version { get; set; } = 2; - public string Name { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } = 100; // V2 新增 - public List Skills { get; set; } = new(); // V2 新增 -} - -// 避免:没有版本控制 -[Serializable] -public class PlayerData -{ - public string Name { get; set; } - public int Health { get; set; } - // 未来新增字段会导致兼容性问题 -} -``` - -#### 2. 数据验证 - -```csharp -public class PlayerData -{ - private string _name; - - public string Name - { - get => _name; - set - { - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Player name cannot be empty"); - _name = value; - } - } - - public int Health - { - get => _health; - set => _health = Math.Max(0, value); // 确保不为负数 - } - - public void Validate() - { - if (string.IsNullOrWhiteSpace(Name)) - throw new ValidationException("Player name is required"); - - if (Health < 0) - throw new ValidationException("Health cannot be negative"); - } -} -``` - -### 💾 存储策略 - -#### 1. 分层存储命名 - -```csharp -// 好的做法:有意义的分层结构 -var playerStorage = new ScopedStorage(rootStorage, "players"); -var saveStorage = new ScopedStorage(rootStorage, "saves"); -var settingsStorage = new ScopedStorage(rootStorage, "settings"); -var tempStorage = new ScopedStorage(rootStorage, "temp"); - -// 避免的混乱命名 -var storage1 = new ScopedStorage(rootStorage, "data1"); -var storage2 = new ScopedStorage(rootStorage, "data2"); -``` - -#### 2. 存储性能优化 - -```csharp -// 好的做法:批量操作和缓存 -public class OptimizedDataManager -{ - private readonly IStorage _storage; - private readonly Dictionary _writeBuffer = new(); - - public void QueueWrite(string key, T data) - { - _writeBuffer[key] = data; - } - - public async Task FlushWritesAsync() - { - var tasks = _writeBuffer.Select(kvp => _storage.WriteAsync(kvp.Key, kvp.Value)); - await Task.WhenAll(tasks); - _writeBuffer.Clear(); - } -} - -// 避免:频繁的小写入 -public class InefficientDataManager -{ - public void UpdatePlayerStat(string stat, int value) - { - _storage.Write($"player/stats/{stat}", value); // 每次都写入磁盘 - } -} -``` - -### 🔄 序列化优化 - -#### 1. 选择合适的序列化格式 - -```csharp -// 好的做法:根据需求选择格式 -public class GameSerializer -{ - // JSON:可读性好,调试方便 - public string SerializeForDebug(object data) => JsonConvert.SerializeObject(data, Formatting.Indented); - - // 二进制:体积小,性能好 - public byte[] SerializeForStorage(object data) => MessagePackSerializer.Serialize(data); - - // 压缩:减少存储空间 - public byte[] SerializeWithCompression(object data) - { - var json = JsonConvert.SerializeObject(data); - return Compress(Encoding.UTF8.GetBytes(json)); - } -} -``` - -#### 2. 自定义序列化逻辑 - -```csharp -public class PlayerInventory -{ - public Dictionary Items { get; set; } = new(); - - [JsonIgnore] // 排除不需要序列化的属性 - public int TotalWeight => Items.Sum(kvp => GetItemWeight(kvp.Key) * kvp.Value); - - [JsonProperty("total_items")] // 自定义序列化名称 - public int TotalItems => Items.Values.Sum(); - - public bool ShouldSerializeItems() // 条件序列化 - { - return Items.Count > 0; - } - - [OnDeserialized] // 反序列化后处理 - private void OnDeserialized(StreamingContext context) - { - // 初始化默认值或执行验证 - Items ??= new Dictionary(); - } -} -``` - -### 🏭 模块设计模式 - -#### 1. 单一职责模块 - -```csharp -// 好的做法:模块职责单一 -public class AudioModule : AbstractModule -{ - // 只负责音频相关功能 -} - -public class SaveModule : AbstractModule -{ - // 只负责存档相关功能 -} - -// 避免:功能过于庞大 -public class GameModule : AbstractModule -{ - // 音频、存档、UI、输入全部混在一起 -} -``` - -#### 2. 模块间通信 - -```csharp -public class SaveModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterUtility(new SaveUtility()); - } -} - -public class PlayerModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new PlayerSystem()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - if (phase == ArchitecturePhase.Ready) - { - // 通过事件进行模块间通信 - this.RegisterEvent(OnPlayerDeath); - } - } - - private void OnPlayerDeath(PlayerDeathEvent e) - { - // 触发保存模块的事件 - Context.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" }); - } -} -``` - -## 性能特性 - -### 📊 内存管理 - -- **缓存策略**:多层缓存减少磁盘 I/O -- **延迟加载**:按需加载资源,减少内存占用 -- **对象池化**:重用对象,减少 GC 压力 -- **内存映射**:大文件使用内存映射技术 - -### ⚡ 存储性能 - -```csharp -// 性能对比示例 -public class StoragePerformanceTest -{ - // 同步操作:简单直接 - public void SyncWrite(IStorage storage, string key, object data) - { - storage.Write(key, data); // ~1-5ms - } - - // 异步操作:非阻塞 - public async Task AsyncWrite(IStorage storage, string key, object data) - { - await storage.WriteAsync(key, data); // ~0.1-1ms (不阻塞主线程) - } - - // 批量操作:高吞吐量 - public async Task BatchWrite(IStorage storage, Dictionary data) - { - var tasks = data.Select(kvp => storage.WriteAsync(kvp.Key, kvp.Value)); - await Task.WhenAll(tasks); // ~10-50ms for 100 items - } -} -``` - -### 🔄 序列化优化 - -- **格式选择**:JSON(可读性)vs 二进制(性能) -- **压缩技术**:减少存储空间和网络传输 -- **增量序列化**:只序列化变化的部分 -- **版本控制**:向后兼容的数据迁移 - ---- - -## 依赖关系 - -```mermaid -graph TD - A[GFramework.Game] --> B[GFramework.Core] - A --> C[GFramework.Core.Abstractions] - A --> D[Newtonsoft.Json] - A --> E[System.Text.Json] -``` - -## 版本兼容性 - -- **.NET**: 6.0+ -- **Newtonsoft.Json**: 13.0.3+ -- **GFramework.Core**: 与 Core 模块版本保持同步 - -## 许可证 - -本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 - ---- - -**版本**: 1.0.0 -**更新日期**: 2026-01-12 \ No newline at end of file diff --git a/GFramework.Godot/README.md b/GFramework.Godot/README.md deleted file mode 100644 index d776d5f..0000000 --- a/GFramework.Godot/README.md +++ /dev/null @@ -1,908 +0,0 @@ -# GFramework.Godot - -> Godot 引擎深度集成 - 为 GFramework 框架提供原生的 Godot 支持 - -GFramework.Godot 是 GFramework 框架的 Godot 特定实现,将框架的架构优势与 Godot 引擎的强大功能完美结合。 - -## 📋 目录 - -- [概述](#概述) -- [核心特性](#核心特性) -- [架构集成](#架构集成) -- [Node 扩展方法](#node-扩展方法) -- [信号系统](#信号系统) -- [节点池化](#节点池化) -- [资源管理](#资源管理) -- [日志系统](#日志系统) -- [完整示例](#完整示例) -- [最佳实践](#最佳实践) -- [性能特性](#性能特性) - -## 概述 - -GFramework.Godot 提供了与 Godot 引擎的深度集成,让开发者能够在保持 GFramework 架构优势的同时,充分利用 Godot -的节点系统、信号机制和场景管理功能。 - -### 核心设计理念 - -- **无缝集成**:框架生命周期与 Godot 节点生命周期自动同步 -- **类型安全**:保持 GFramework 的强类型特性 -- **性能优化**:零额外开销的 Godot 集成 -- **开发效率**:丰富的扩展方法简化常见操作 - -## 核心特性 - -### 🎯 架构生命周期绑定 - -- 自动将框架初始化与 Godot 场景树绑定 -- 支持节点销毁时的自动清理 -- 阶段式架构初始化与 Godot `_Ready` 周期同步 - -### 🔧 丰富的 Node 扩展方法 - -- **50+** 个实用扩展方法 -- 安全的节点操作和验证 -- 流畅的场景树遍历和查找 -- 简化的输入处理 - -### 📡 流畅的信号 API - -- 类型安全的信号连接 -- 链式调用支持 -- 自动生命周期管理 -- Godot 信号与框架事件系统的桥接 - -### 🏊‍♂️ 高效的节点池化 - -- 专用的 Node 对象池 -- 自动回收和重用机制 -- 内存友好的高频节点创建/销毁 - -### 📦 智能资源管理 - -- 简化的 Godot 资源加载 -- 类型安全的资源工厂 -- 缓存和预加载支持 - -### 📝 Godot 原生日志 - -- 与 Godot 日志系统完全集成 -- 框架日志自动输出到 Godot 控制台 -- 可配置的日志级别 - -## 架构集成 - -### Architecture 基类 - -```csharp -using GFramework.Godot.architecture; - -public class GameArchitecture : AbstractArchitecture -{ - protected override void Init() - { - // 注册核心模型 - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - - // 注册系统 - RegisterSystem(new CombatSystem()); - RegisterSystem(new AudioSystem()); - - // 注册工具类 - RegisterUtility(new StorageUtility()); - RegisterUtility(new ResourceLoadUtility()); - } - - protected override void InstallModules() - { - // 安装 Godot 特定模块 - InstallGodotModule(new InputModule()); - InstallGodotModule(new AudioModule()); - } -} -``` - -### Godot 模块系统 - -```csharp -using GFramework.Godot.architecture; - -[ContextAware] -[Log] -public partial class AudioModule : AbstractGodotModule -{ - // 模块节点本身可以作为 Godot 节点 - public override Node Node => this; - - public override void Install(IArchitecture architecture) - { - // 注册音频相关系统 - architecture.RegisterSystem(new AudioSystem()); - architecture.RegisterUtility(new AudioUtility()); - } - - public override void OnAttach(Architecture architecture) - { - // 模块附加时的初始化 - Logger.Info("Audio module attached to architecture"); - } - - public override void OnDetach(Architecture architecture) - { - // 模块分离时的清理 - Logger.Info("Audio module detached from architecture"); - } - - // 响应架构生命周期阶段 - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.Ready: - // 架构准备就绪,可以开始播放背景音乐 - PlayBackgroundMusic(); - break; - } - } -} -``` - -### Controller 集成 - -```csharp -using GFramework.Godot.extensions; - -[ContextAware] -[Log] -public partial class PlayerController : Node, IController -{ - private PlayerModel _playerModel; - - public override void _Ready() - { - // 获取模型引用 - _playerModel = Context.GetModel(); - - // 注册事件监听,自动与节点生命周期绑定 - this.RegisterEvent(OnPlayerInput) - .UnRegisterWhenNodeExitTree(this); - - // 监听属性变化 - _playerModel.Health.Register(OnHealthChanged) - .UnRegisterWhenNodeExitTree(this); - } - - private void OnPlayerInput(PlayerInputEvent e) - { - // 处理玩家输入 - switch (e.Action) - { - case "move_left": - MovePlayer(-1, 0); - break; - case "move_right": - MovePlayer(1, 0); - break; - case "attack": - Context.SendCommand(new AttackCommand()); - break; - } - } - - private void OnHealthChanged(int newHealth) - { - // 更新 UI - var healthBar = GetNode("UI/HealthBar"); - healthBar.Value = newHealth; - - // 播放音效 - if (newHealth < _playerModel.PreviousHealth) - PlayHurtSound(); - } -} -``` - -## Node 扩展方法 - -GFramework.Godot 提供了 50+ 个 Node 扩展方法,大大简化了 Godot 开发中的常见操作。 - -### 🔍 节点查找与验证 - -```csharp -// 安全的节点获取 -var player = GetNodeX("Player"); // 自动 null 检查和类型转换 -var child = FindChildX("Player"); // 递归查找子节点 - -// 节点验证 -if (IsValidNode(player)) -{ - // 节点有效且在场景树中 -} - -// 安全的节点遍历 -this.ForEachChild(child => { - GD.Print($"Found child: {child.Name}"); -}); -``` - -### 🌊 流畅的场景树操作 - -```csharp -// 安全的添加子节点 -var bullet = bulletScene.Instantiate(); -AddChildX(bullet); - -// 等待节点准备就绪 -await bullet.WaitUntilReady(); - -// 获取父节点 -var parent = GetParentX(); - -// 安全的节点移除 -bullet.QueueFreeX(); // 等效于 QueueFree() 但带有验证 -bullet.FreeX(); // 立即释放(谨慎使用) -``` - -### 🎮 输入处理简化 - -```csharp -// 输入处理 -SetInputAsHandled(); // 标记输入已处理 -DisableInput(); // 禁用输入 -EnableInput(); // 启用输入 - -// 输入状态检查 -if (Input.IsActionJustPressed("jump")) -{ - Jump(); -} -``` - -### 🔄 异步操作支持 - -```csharp -// 等待信号 -await ToSignal(this, SignalName.Ready); - -// 等待条件满足 -await WaitUntil(() => IsReady); - -// 等待帧结束 -await WaitUntilProcessFrame(); - -// 延迟执行 -await WaitUntilTimeout(2.0f); -``` - -## 信号系统 - -### SignalBuilder 流畅 API - -```csharp -using GFramework.Godot.extensions; - -// 基础信号连接 -this.ConnectSignal(Button.SignalName.Pressed, OnButtonPressed); - -// 流畅的信号构建 -this.CreateSignalBuilder(Timer.SignalName.Timeout) - .WithFlags(ConnectFlags.OneShot) // 单次触发 - .CallImmediately() // 立即调用一次 - .Connect(OnTimerTimeout) - .UnRegisterWhenNodeExitTree(this); - -// 多信号连接 -this.CreateSignalBuilder() - .AddSignal(Button.SignalName.Pressed, OnButtonPressed) - .AddSignal(Button.SignalName.MouseEntered, OnButtonHover) - .AddSignal(Button.SignalName.MouseExited, OnButtonExit) - .UnRegisterWhenNodeExitTree(this); -``` - -### 信号与框架事件桥接 - -```csharp -[ContextAware] -[Log] -public partial class UIController : Node, IController -{ - public override void _Ready() - { - // Godot 信号 -> 框架事件 - this.CreateSignalBuilder(Button.SignalName.Pressed) - .Connect(() => { - Context.SendEvent(new UIButtonClickEvent { ButtonId = "start_game" }); - }) - .UnRegisterWhenNodeExitTree(this); - - // 框架事件 -> Godot 信号 - this.RegisterEvent(OnHealthChanged) - .UnRegisterWhenNodeExitTree(this); - } - - private void OnHealthChanged(HealthChangeEvent e) - { - // 更新 Godot UI - var healthBar = GetNode("HealthBar"); - healthBar.Value = e.NewHealth; - - // 发送 Godot 信号 - EmitSignal(SignalName.HealthUpdated, e.NewHealth); - } - - [Signal] - public delegate void HealthUpdatedEventHandler(int newHealth); -} -``` - -## 节点池化 - -### AbstractNodePoolSystem 使用 - -```csharp -using GFramework.Godot.pool; - -public class BulletPoolSystem : AbstractNodePoolSystem -{ - private PackedScene _bulletScene; - - public BulletPoolSystem() - { - _bulletScene = GD.Load("res://scenes/Bullet.tscn"); - } - - protected override Bullet CreateItem(string key) - { - return _bulletScene.Instantiate(); - } - - protected override void OnSpawn(Bullet item, string key) - { - // 重置子弹状态 - item.Reset(); - item.Position = Vector3.Zero; - item.Visible = true; - } - - protected override void OnDespawn(Bullet item) - { - // 隐藏子弹 - item.Visible = false; - // 移除父节点 - item.GetParent()?.RemoveChild(item); - } - - protected override bool CanDespawn(Bullet item) - { - // 只有不在使用中的子弹才能回收 - return !item.IsActive; - } -} -``` - -### 池化系统使用 - -```csharp -[ContextAware] -[Log] -public partial class WeaponController : Node, IController -{ - private BulletPoolSystem _bulletPool; - - protected override void OnInit() - { - _bulletPool = Context.GetSystem(); - } - - public void Shoot(Vector3 direction) - { - // 从池中获取子弹 - var bullet = _bulletPool.Spawn("standard"); - - if (bullet != null) - { - // 设置子弹参数 - bullet.Direction = direction; - bullet.Speed = 10.0f; - - // 添加到场景 - GetTree().Root.AddChild(bullet); - - // 注册碰撞检测 - this.RegisterEvent(e => { - if (e.Bullet == bullet) - { - // 回收子弹 - _bulletPool.Despawn(bullet); - } - }).UnRegisterWhenNodeExitTree(this); - } - } -} -``` - -## 资源管理 - -### ResourceLoadUtility 使用 - -```csharp -using GFramework.Godot.assets; - -[ContextAware] -[Log] -public partial class ResourceManager : Node, IController -{ - private ResourceLoadUtility _resourceLoader; - - protected override void OnInit() - { - _resourceLoader = new ResourceLoadUtility(); - } - - public T LoadResource(string path) where T : Resource - { - return _resourceLoader.LoadResource(path); - } - - public async Task LoadResourceAsync(string path) where T : Resource - { - return await _resourceLoader.LoadResourceAsync(path); - } - - public void PreloadResources() - { - // 预加载常用资源 - _resourceLoader.PreloadResource("res://textures/player.png"); - _resourceLoader.PreloadResource("res://audio/shoot.wav"); - _resourceLoader.PreloadResource("res://scenes/enemy.tscn"); - } -} -``` - -### 自定义资源工厂 - -```csharp -public class GameResourceFactory : AbstractResourceFactoryUtility -{ - protected override void RegisterFactories() - { - RegisterFactory(CreatePlayerData); - RegisterFactory(CreateWeaponConfig); - RegisterFactory(CreateLevelData); - } - - private PlayerData CreatePlayerData(string path) - { - var config = LoadJson(path); - return new PlayerData - { - MaxHealth = config.MaxHealth, - Speed = config.Speed, - JumpForce = config.JumpForce - }; - } - - private WeaponConfig CreateWeaponConfig(string path) - { - var data = LoadJson(path); - return new WeaponConfig - { - Damage = data.Damage, - FireRate = data.FireRate, - BulletPrefab = LoadResource(data.BulletPath) - }; - } -} -``` - -## 日志系统 - -### GodotLogger 使用 - -```csharp -using GFramework.Godot.logging; - -[ContextAware] -[Log] // 自动生成 Logger 字段 -public partial class GameController : Node, IController -{ - public override void _Ready() - { - // 使用自动生成的 Logger - Logger.Info("Game controller ready"); - - try - { - InitializeGame(); - Logger.Info("Game initialized successfully"); - } - catch (Exception ex) - { - Logger.Error($"Failed to initialize game: {ex.Message}"); - } - } - - public void StartGame() - { - Logger.Debug("Starting game"); - - // 发送游戏开始事件 - Context.SendEvent(new GameStartEvent()); - - Logger.Info("Game started"); - } - - public void PauseGame() - { - Logger.Info("Game paused"); - Context.SendEvent(new GamePauseEvent()); - } -} -``` - -### 日志配置 - -```csharp -public class GodotLoggerFactoryProvider : ILoggerFactoryProvider -{ - public ILoggerFactory CreateFactory() - { - return new GodotLoggerFactory(new LoggerProperties - { - MinLevel = LogLevel.Debug, - IncludeTimestamp = true, - IncludeCallerInfo = true - }); - } -} - -// 在架构初始化时配置日志 -public class GameArchitecture : AbstractArchitecture -{ - protected override void Init() - { - // 配置 Godot 日志工厂 - LoggerProperties = new LoggerProperties - { - LoggerFactoryProvider = new GodotLoggerFactoryProvider(), - MinLevel = LogLevel.Info - }; - - // 注册组件... - } -} -``` - -## 完整示例 - -### 简单射击游戏示例 - -```csharp -// 1. 定义架构 -public class ShooterGameArchitecture : AbstractArchitecture -{ - protected override void Init() - { - // 注册模型 - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - RegisterModel(new ScoreModel()); - - // 注册系统 - RegisterSystem(new PlayerControllerSystem()); - RegisterSystem(new BulletPoolSystem()); - RegisterSystem(new EnemySpawnSystem()); - RegisterSystem(new CollisionSystem()); - - // 注册工具 - RegisterUtility(new StorageUtility()); - RegisterUtility(new ResourceLoadUtility()); - } -} - -// 2. 玩家控制器 -[ContextAware] -[Log] -public partial class PlayerController : CharacterBody2D, IController -{ - private PlayerModel _playerModel; - - public override void _Ready() - { - _playerModel = Context.GetModel(); - - // 输入处理 - SetProcessInput(true); - - // 注册事件 - this.RegisterEvent(OnDamage) - .UnRegisterWhenNodeExitTree(this); - } - - public override void _Process(double delta) - { - var inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); - Velocity = inputDir * _playerModel.Speed.Value; - MoveAndSlide(); - } - - public override void _Input(InputEvent @event) - { - if (@event.IsActionPressed("shoot")) - { - Shoot(); - } - } - - private void Shoot() - { - if (CanShoot()) - { - var bulletPool = Context.GetSystem(); - var bullet = bulletPool.Spawn("player"); - - if (bullet != null) - { - var direction = GetGlobalMousePosition() - GlobalPosition; - bullet.Initialize(GlobalPosition, direction.Normalized()); - GetTree().Root.AddChild(bullet); - - Context.SendEvent(new BulletFiredEvent()); - } - } - } - - private void OnDamage(PlayerDamageEvent e) - { - _playerModel.Health.Value -= e.Damage; - - if (_playerModel.Health.Value <= 0) - { - Die(); - } - } - - private void Die() - { - Logger.Info("Player died"); - Context.SendEvent(new PlayerDeathEvent()); - QueueFreeX(); - } -} - -// 3. 主场景 -[ContextAware] -[Log] -public partial class MainScene : Node2D -{ - private ShooterGameArchitecture _architecture; - - public override void _Ready() - { - // 初始化架构 - _architecture = new ShooterGameArchitecture(); - _architecture.Initialize(); - - // 创建玩家 - var playerScene = GD.Load("res://scenes/Player.tscn"); - var player = playerScene.Instantiate(); - AddChild(player); - - // 注册全局事件 - this.RegisterEvent(OnPlayerDeath) - .UnRegisterWhenNodeExitTree(this); - - this.RegisterEvent(OnGameWin) - .UnRegisterWhenNodeExitTree(this); - - Logger.Info("Game started"); - } - - private void OnPlayerDeath(PlayerDeathEvent e) - { - Logger.Info("Game over"); - ShowGameOverScreen(); - } - - private void OnGameWin(GameWinEvent e) - { - Logger.Info("Victory!"); - ShowVictoryScreen(); - } - - private void ShowGameOverScreen() - { - var gameOverScene = GD.Load("res://ui/GameOver.tscn"); - var gameOverUI = gameOverScene.Instantiate(); - AddChild(gameOverUI); - } - - private void ShowVictoryScreen() - { - var victoryScene = GD.Load("res://ui/Victory.tscn"); - var victoryUI = victoryScene.Instantiate(); - AddChild(victoryUI); - } -} -``` - -## 最佳实践 - -### 🏗️ 架构设计最佳实践 - -#### 1. 模块化设计 - -```csharp -// 好的做法:按功能分组模块 -public class AudioModule : AbstractGodotModule { } -public class InputModule : AbstractGodotModule { } -public class UIModule : AbstractGodotModule { } - -// 避免的功能过于庞大的单一模块 -public class GameModule : AbstractGodotModule // ❌ 太大 -{ - // 音频、输入、UI、逻辑全部混在一起 -} -``` - -#### 2. 生命周期管理 - -```csharp -// 好的做法:使用自动清理 -this.RegisterEvent(OnGameEvent) - .UnRegisterWhenNodeExitTree(this); - -model.Property.Register(OnPropertyChange) - .UnRegisterWhenNodeExitTree(this); - -// 避免手动管理清理 -private IUnRegister _eventRegister; -public override void _Ready() -{ - _eventRegister = this.RegisterEvent(OnGameEvent); -} - -public override void _ExitTree() -{ - _eventRegister?.UnRegister(); // 容易忘记 -} -``` - -### 🎮 Godot 集成最佳实践 - -#### 1. 节点安全操作 - -```csharp -// 好的做法:使用安全扩展 -var player = GetNodeX("Player"); -var child = FindChildX("HealthBar"); - -// 避免的直接节点访问 -var player = GetNode("Player"); // 可能抛出异常 -``` - -#### 2. 信号连接模式 - -```csharp -// 好的做法:使用 SignalBuilder -this.CreateSignalBuilder(Button.SignalName.Pressed) - .UnRegisterWhenNodeExitTree(this) - .Connect(OnButtonPressed); - -// 避免的原始方式 -Button.Pressed += OnButtonPressed; // 容易忘记清理 -``` - -### 🏊‍♂️ 性能优化最佳实践 - -#### 1. 节点池化策略 - -```csharp -// 好的做法:高频创建对象使用池化 -public class BulletPool : AbstractNodePoolSystem -{ - // 为不同类型的子弹创建不同的池 -} - -// 避免的频繁创建销毁 -public void Shoot() -{ - var bullet = bulletScene.Instantiate(); // ❌ 性能问题 - // ... - bullet.QueueFree(); -} -``` - -#### 2. 资源预加载 - -```csharp -// 好的做法:预加载常用资源 -public override void _Ready() -{ - var resourceLoader = new ResourceLoadUtility(); - resourceLoader.PreloadResource("res://textures/bullet.png"); - resourceLoader.PreloadResource("res://audio/shoot.wav"); -} -``` - -### 🔧 调试和错误处理 - -#### 1. 日志使用策略 - -```csharp -// 好的做法:分级别记录 -Logger.Debug($"Player position: {Position}"); // 调试信息 -Logger.Info("Game started"); // 重要状态 -Logger.Warning($"Low health: {_playerModel.Health}"); // 警告 -Logger.Error($"Failed to load resource: {path}"); // 错误 - -// 避免的过度日志 -Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁 -``` - -#### 2. 异常处理 - -```csharp -// 好的做法:优雅的错误处理 -public T LoadResource(string path) where T : Resource -{ - try - { - return GD.Load(path); - } - catch (Exception ex) - { - Logger.Error($"Failed to load resource {path}: {ex.Message}"); - return GetDefaultResource(); - } -} -``` - -## 性能特性 - -### 📊 内存管理 - -- **节点池化**:减少 GC 压力,提高频繁创建/销毁对象的性能 -- **资源缓存**:自动缓存已加载的 Godot 资源 -- **生命周期管理**:自动清理事件监听器和资源引用 - -### ⚡ 运行时性能 - -- **零分配**:扩展方法避免不必要的对象分配 -- **编译时优化**:Source Generators 减少运行时开销 -- **类型安全**:编译时类型检查,避免运行时错误 - -### 🔄 异步支持 - -- **信号等待**:使用 `await ToSignal()` 简化异步代码 -- **条件等待**:`WaitUntil()` 和 `WaitUntilTimeout()` 简化异步逻辑 -- **场景树等待**:`WaitUntilReady()` 确保节点准备就绪 - ---- - -## 依赖关系 - -```mermaid -graph TD - A[GFramework.Godot] --> B[GFramework.Game] - A --> C[GFramework.Game.Abstractions] - A --> D[GFramework.Core.Abstractions] - A --> E[Godot.SourceGenerators] - A --> F[GodotSharpEditor] -``` - -## 版本兼容性 - -- **Godot**: 4.5.1+ -- **.NET**: 6.0+ -- **GFramework.Core**: 与 Core 模块版本保持同步 - -## 许可证 - -本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 - ---- - -**版本**: 1.0.0 -**更新日期**: 2026-01-12 \ No newline at end of file diff --git a/GFramework.SourceGenerators/README.md b/GFramework.SourceGenerators/README.md deleted file mode 100644 index a1180a4..0000000 --- a/GFramework.SourceGenerators/README.md +++ /dev/null @@ -1,997 +0,0 @@ -# GFramework.SourceGenerators - -> 编译时代码生成 - 零运行时开销的代码增强工具 - -GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通过编译时分析自动生成样板代码,显著提升开发效率并减少运行时开销。 - -## 📋 目录 - -- [概述](#概述) -- [核心特性](#核心特性) -- [安装配置](#安装配置) -- [Log 属性生成器](#log-属性生成器) -- [ContextAware 属性生成器](#contextaware-属性生成器) -- [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器) -- [诊断信息](#诊断信息) -- [性能优势](#性能优势) -- [使用示例](#使用示例) -- [最佳实践](#最佳实践) -- [常见问题](#常见问题) - -## 概述 - -GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。 - -### 核心设计理念 - -- **零运行时开销**:代码在编译时生成,无反射或动态调用 -- **类型安全**:编译时类型检查,避免运行时错误 -- **开发效率**:自动生成样板代码,减少重复工作 -- **可配置性**:支持多种配置选项满足不同需求 - -## 核心特性 - -### 🎯 主要生成器 - -- **[Log] 属性**:自动生成 ILogger 字段和日志方法 -- **[ContextAware] 属性**:自动实现 IContextAware 接口 -- **[GenerateEnumExtensions] 属性**:自动生成枚举扩展方法 - -### 🔧 高级特性 - -- **智能诊断**:生成器包含详细的错误诊断信息 -- **增量编译**:只生成修改过的代码,提高编译速度 -- **命名空间控制**:灵活控制生成代码的命名空间 -- **可访问性控制**:支持不同的访问修饰符 - -## 安装配置 - -### NuGet 包安装 - -```xml - - - net6.0 - - - - - - - -``` - -### 项目文件配置 - -```xml - - - net6.0 - true - Generated - - - - - - -``` - -## Log 属性生成器 - -[Log] 属性自动为标记的类生成日志记录功能,包括 ILogger 字段和便捷的日志方法。 - -### 基础使用 - -```csharp -using GFramework.SourceGenerators.Attributes; - -[Log] -public partial class PlayerController -{ - public void DoSomething() - { - Logger.Info("Doing something"); // 自动生成的 Logger 字段 - Logger.Debug("Debug information"); - Logger.Warning("Warning message"); - Logger.Error("Error occurred"); - } -} -``` - -### 生成的代码 - -编译器会自动生成如下代码: - -```csharp -// -using Microsoft.Extensions.Logging; - -namespace YourNamespace -{ - public partial class PlayerController - { - private static readonly ILogger Logger = - LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); - } -} -``` - -### 高级配置 - -```csharp -[Log( - fieldName = "CustomLogger", // 自定义字段名 - accessModifier = AccessModifier.Public, // 访问修饰符 - isStatic = false, // 是否为静态字段 - loggerName = "Custom.PlayerLogger", // 自定义日志器名称 - includeLoggerInterface = true // 是否包含 ILogger 接口 -)] -public partial class CustomLoggerExample -{ - public void LogSomething() - { - CustomLogger.LogInformation("Custom logger message"); - } -} -``` - -### 配置选项说明 - -| 参数 | 类型 | 默认值 | 说明 | -|--------------------------|----------------|----------|---------------------| -| `fieldName` | string | "Logger" | 生成的日志字段名称 | -| `accessModifier` | AccessModifier | Private | 字段访问修饰符 | -| `isStatic` | bool | true | 是否生成静态字段 | -| `loggerName` | string | null | 自定义日志器名称,null 时使用类名 | -| `includeLoggerInterface` | bool | false | 是否包含 ILogger 接口实现 | - -### 静态类支持 - -```csharp -[Log] -public static partial class MathHelper -{ - public static int Add(int a, int b) - { - Logger.Debug($"Adding {a} and {b}"); - return a + b; - } -} -``` - -### 日志级别控制 - -```csharp -[Log(minLevel = LogLevel.Warning)] -public partial class WarningOnlyLogger -{ - public void ProcessData() - { - Logger.Debug("This won't be logged"); // 低于最小级别,被过滤 - Logger.Warning("This will be logged"); - Logger.Error("This will also be logged"); - } -} -``` - -## ContextAware 属性生成器 - -[ContextAware] 属性自动实现 IContextAware 接口,提供便捷的架构上下文访问能力。 - -### 基础使用 - -```csharp -using GFramework.SourceGenerators.Attributes; -using GFramework.Core.Abstractions; - -[ContextAware] -public partial class PlayerController : IController -{ - public void Initialize() - { - // Context 属性自动生成,提供架构上下文访问 - var playerModel = Context.GetModel(); - var combatSystem = Context.GetSystem(); - - Context.SendEvent(new PlayerInitializedEvent()); - } -} -``` - -### 生成的代码 - -编译器会自动生成如下代码: - -```csharp -// -using GFramework.Core.Abstractions; - -namespace YourNamespace -{ - public partial class PlayerController : IContextAware - { - private IContextAware.Context _context; - - public IContextAware.Context Context => _context ??= new LazyContext(this); - - public void SetContext(IContextAware.Context context) - { - _context = context; - } - - public IContextAware.Context GetContext() - { - return _context; - } - } -} -``` - -### 延迟初始化 - -```csharp -[ContextAware(useLazy = true)] -public partial class LazyContextExample -{ - public void AccessContext() - { - // Context 会延迟初始化,直到第一次访问 - var model = Context.GetModel(); - } -} -``` - -### 上下文验证 - -```csharp -[ContextAware(validateContext = true)] -public partial class ValidatedContextExample -{ - public void AccessContext() - { - // 每次访问都会验证上下文的有效性 - var model = Context.GetModel(); - if (Context.IsInvalid) - { - throw new InvalidOperationException("Context is invalid"); - } - } -} -``` - -### 与其他属性组合 - -```csharp -[Log] -[ContextAware] -public partial class AdvancedController : IController -{ - public void ProcessRequest() - { - Logger.Info("Processing request"); - - var model = Context.GetModel(); - Logger.Info($"Player health: {model.Health}"); - - Context.SendCommand(new ProcessCommand()); - Logger.Debug("Command sent"); - } -} -``` - -## GenerateEnumExtensions 属性生成器 - -[GenerateEnumExtensions] 属性为枚举类型生成便捷的扩展方法,提高代码可读性和类型安全性。 - -### 基础使用 - -```csharp -using GFramework.SourceGenerators.Attributes; - -[GenerateEnumExtensions] -public enum GameState -{ - Playing, - Paused, - GameOver, - Menu -} - -// 自动生成的扩展方法: -public static class GameStateExtensions -{ - public static bool IsPlaying(this GameState state) => state == GameState.Playing; - public static bool IsPaused(this GameState state) => state == GameState.Paused; - public static bool IsGameOver(this GameState state) => state == GameState.GameOver; - public static bool IsMenu(this GameState state) => state == GameState.Menu; - - public static bool IsIn(this GameState state, params GameState[] values) - { - return values.Contains(state); - } -} - -// 使用示例 -public class GameManager -{ - private GameState _currentState = GameState.Menu; - - public bool CanProcessInput() - { - return _currentState.IsPlaying() || _currentState.IsMenu(); - } - - public bool IsGameOver() - { - return _currentState.IsGameOver(); - } - - public bool IsActiveState() - { - return _currentState.IsIn(GameState.Playing, GameState.Menu); - } -} -``` - -### 自定义扩展方法 - -```csharp -[GenerateEnumExtensions( - generateIsMethods = true, - generateHasMethod = true, - generateInMethod = true, - customPrefix = "Is", - includeToString = true -)] -public enum PlayerState -{ - Idle, - Walking, - Running, - Jumping, - Attacking -} - -// 生成更多扩展方法 -public static class PlayerStateExtensions -{ - public static bool IsIdle(this PlayerState state) => state == PlayerState.Idle; - public static bool IsWalking(this PlayerState state) => state == PlayerState.Walking; - public static bool IsRunning(this PlayerState state) => state == PlayerState.Running; - public static bool IsJumping(this PlayerState state) => state == PlayerState.Jumping; - public static bool IsAttacking(this PlayerState state) => state == PlayerState.Attacking; - - public static bool HasIdle(this PlayerState state) => state == PlayerState.Idle; - public static bool HasWalking(this PlayerState state) => state == PlayerState.Walking; - // ... 其他 Has 方法 - - public static bool In(this PlayerState state, params PlayerState[] values) - { - return values.Contains(state); - } - - public static string ToDisplayString(this PlayerState state) - { - return state switch - { - PlayerState.Idle => "Idle", - PlayerState.Walking => "Walking", - PlayerState.Running => "Running", - PlayerState.Jumping => "Jumping", - PlayerState.Attacking => "Attacking", - _ => state.ToString() - }; - } -} -``` - -### 位标志枚举支持 - -```csharp -[GenerateEnumExtensions] -[Flags] -public enum PlayerAbilities -{ - None = 0, - Jump = 1 << 0, - Run = 1 << 1, - Attack = 1 << 2, - Defend = 1 << 3, - Magic = 1 << 4 -} - -// 生成位标志扩展方法 -public static class PlayerAbilitiesExtensions -{ - public static bool HasJump(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Jump); - public static bool HasRun(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Run); - // ... 其他位标志方法 - - public static bool HasAny(this PlayerAbilities abilities, params PlayerAbilities[] flags) - { - return flags.Any(flag => abilities.HasFlag(flag)); - } - - public static bool HasAll(this PlayerAbilities abilities, params PlayerAbilities[] flags) - { - return flags.All(flag => abilities.HasFlag(flag)); - } -} -``` - -### 配置选项说明 - -| 参数 | 类型 | 默认值 | 说明 | -|---------------------|--------|-------|------------------------| -| `generateIsMethods` | bool | true | 是否生成 IsX() 方法 | -| `generateHasMethod` | bool | true | 是否生成 HasX() 方法 | -| `generateInMethod` | bool | true | 是否生成 In(params T[]) 方法 | -| `customPrefix` | string | "Is" | 方法名前缀 | -| `includeToString` | bool | false | 是否生成 ToString 扩展 | -| `namespace` | string | null | 生成扩展类的命名空间 | - -## 诊断信息 - -GFramework.SourceGenerators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。 - -### GF_Logging_001 - 日志字段名冲突 - -```csharp -[Log(fieldName = "Logger")] -public partial class ClassWithLogger -{ - private readonly ILogger Logger; // ❌ 冲突! -} -``` - -**错误信息**: `GF_Logging_001: Logger field name 'Logger' conflicts with existing field` - -**解决方案**: 更改字段名或移除冲突字段 - -```csharp -[Log(fieldName = "CustomLogger")] -public partial class ClassWithLogger -{ - private readonly ILogger Logger; // ✅ 不冲突 - private static readonly ILogger CustomLogger; // ✅ 生成器使用 CustomLogger -} -``` - -### GF_Rule_001 - ContextAware 接口冲突 - -```csharp -[ContextAware] -public partial class AlreadyContextAware : IContextAware // ❌ 冲突! -{ - // 已实现 IContextAware -} -``` - -**错误信息**: `GF_Rule_001: Type already implements IContextAware interface` - -**解决方案**: 移除 [ContextAware] 属性或移除手动实现 - -```csharp -// 方案1:移除属性 -public partial class AlreadyContextAware : IContextAware -{ - // 手动实现 -} - -// 方案2:移除手动实现,使用生成器 -[ContextAware] -public partial class AlreadyContextAware -{ - // 生成器自动实现 -} -``` - -### GF_Enum_001 - 枚举成员命名冲突 - -```csharp -[GenerateEnumExtensions] -public enum ConflictEnum -{ - IsPlaying, // ❌ 冲突!会生成 IsIsPlaying() - HasJump // ❌ 冲突!会生成 HasHasJump() -} -``` - -**错误信息**: `GF_Enum_001: Enum member name conflicts with generated method` - -**解决方案**: 重命名枚举成员或自定义前缀 - -```csharp -[GenerateEnumExtensions(customPrefix = "IsState")] -public enum ConflictEnum -{ - Playing, // ✅ 生成 IsStatePlaying() - Jump // ✅ 生成 IsStateJump() -} -``` - -## 性能优势 - -### 编译时 vs 运行时对比 - -| 特性 | 手动实现 | 反射实现 | 源码生成器 | -|-----------|------|------|-------| -| **运行时性能** | 最优 | 最差 | 最优 | -| **内存开销** | 最小 | 最大 | 最小 | -| **类型安全** | 编译时 | 运行时 | 编译时 | -| **开发效率** | 低 | 中 | 高 | -| **调试友好** | 好 | 差 | 好 | - -### 基准测试结果 - -```csharp -// 日志性能对比 (100,000 次调用) -// 手动实现: 0.23ms -// 反射实现: 45.67ms -// 源码生成器: 0.24ms (几乎无差异) - -// 上下文访问性能对比 (1,000,000 次访问) -// 手动实现: 0.12ms -// 反射实现: 23.45ms -// 源码生成器: 0.13ms (几乎无差异) -``` - -### 内存分配分析 - -```csharp -// 使用 source generators 的内存分配 -[Log] -[ContextAware] -public partial class EfficientController : IController -{ - public void Process() - { - Logger.Info("Processing"); // 0 分配 - var model = Context.GetModel(); // 0 分配 - } -} - -// 对比反射实现的内存分配 -public class InefficientController : IController -{ - public void Process() - { - var logger = GetLoggerViaReflection(); // 每次分配 - var model = GetModelViaReflection(); // 每次分配 - } -} -``` - -## 使用示例 - -### 完整的游戏控制器示例 - -```csharp -using GFramework.SourceGenerators.Attributes; -using GFramework.Core.Abstractions; - -[Log] -[ContextAware] -public partial class GameController : Node, IController -{ - private PlayerModel _playerModel; - private CombatSystem _combatSystem; - - public override void _Ready() - { - // 初始化模型和系统引用 - _playerModel = Context.GetModel(); - _combatSystem = Context.GetSystem(); - - // 监听事件 - this.RegisterEvent(OnPlayerInput) - .UnRegisterWhenNodeExitTree(this); - - Logger.Info("Game controller initialized"); - } - - private void OnPlayerInput(PlayerInputEvent e) - { - Logger.Debug($"Processing player input: {e.Action}"); - - switch (e.Action) - { - case "attack": - HandleAttack(); - break; - case "defend": - HandleDefend(); - break; - } - } - - private void HandleAttack() - { - if (_playerModel.CanAttack()) - { - Logger.Info("Player attacks"); - _combatSystem.ProcessAttack(); - Context.SendEvent(new AttackEvent()); - } - else - { - Logger.Warning("Player cannot attack - cooldown"); - } - } - - private void HandleDefend() - { - if (_playerModel.CanDefend()) - { - Logger.Info("Player defends"); - _playerModel.IsDefending.Value = true; - Context.SendEvent(new DefendEvent()); - } - else - { - Logger.Warning("Player cannot defend"); - } - } -} -``` - -### 枚举状态管理示例 - -```csharp -[GenerateEnumExtensions( - generateIsMethods = true, - generateHasMethod = true, - generateInMethod = true, - includeToString = true -)] -public enum CharacterState -{ - Idle, - Walking, - Running, - Jumping, - Falling, - Attacking, - Hurt, - Dead -} - -[Log] -[ContextAware] -public partial class CharacterController : Node, IController -{ - private CharacterModel _characterModel; - - public override void _Ready() - { - _characterModel = Context.GetModel(); - - // 监听状态变化 - _characterModel.State.Register(OnStateChanged); - } - - private void OnStateChanged(CharacterState newState) - { - Logger.Info($"Character state changed to: {newState.ToDisplayString()}"); - - // 使用生成的扩展方法 - if (newState.IsDead()) - { - HandleDeath(); - } - else if (newState.IsHurt()) - { - HandleHurt(); - } - else if (newState.In(CharacterState.Walking, CharacterState.Running)) - { - StartMovementEffects(); - } - - // 检查是否可以接受输入 - if (newState.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running)) - { - EnableInput(); - } - else - { - DisableInput(); - } - } - - private bool CanAttack() - { - var state = _characterModel.State.Value; - return state.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running); - } - - private void HandleDeath() - { - Logger.Info("Character died"); - DisableInput(); - PlayDeathAnimation(); - Context.SendEvent(new CharacterDeathEvent()); - } -} -``` - -## 最佳实践 - -### 🎯 属性使用策略 - -#### 1. 合理的属性组合 - -```csharp -// 好的做法:相关功能组合使用 -[Log] -[ContextAware] -public partial class BusinessLogicComponent : IComponent -{ - // 既有日志记录又有上下文访问 -} - -// 避免:不必要的属性 -[Log] // ❌ 静态工具类通常不需要日志 -public static class MathHelper -{ - public static int Add(int a, int b) => a + b; -} -``` - -#### 2. 命名约定 - -```csharp -// 好的做法:一致的命名 -[Log(fieldName = "Logger")] -public partial class PlayerController { } - -[Log(fieldName = "Logger")] -public partial class EnemyController { } - -// 避免:不一致的命名 -[Log(fieldName = "Logger")] -public partial class PlayerController { } - -[Log(fieldName = "CustomLogger")] -public partial class EnemyController { } -``` - -### 🏗️ 项目组织 - -#### 1. 分离生成器和业务逻辑 - -```csharp -// 好的做法:部分类分离 -// PlayerController.Logic.cs - 业务逻辑 -public partial class PlayerController : IController -{ - public void Move(Vector2 direction) - { - if (CanMove()) - { - UpdatePosition(direction); - Logger.Debug($"Player moved to {direction}"); - } - } -} - -// PlayerController.Generated.cs - 生成代码所在 -// 不需要手动维护,由生成器处理 -``` - -#### 2. 枚举设计 - -```csharp -// 好的做法:有意义的枚举设计 -[GenerateEnumExtensions] -public enum GameState -{ - MainMenu, // 主菜单 - Playing, // 游戏中 - Paused, // 暂停 - GameOver, // 游戏结束 - Victory // 胜利 -} - -// 避免:含义不明确的枚举值 -[GenerateEnumExtensions] -public enum State -{ - State1, - State2, - State3 -} -``` - -### 🔧 性能优化 - -#### 1. 避免过度日志 - -```csharp -// 好的做法:合理的日志级别 -[Log(minLevel = LogLevel.Information)] -public partial class PerformanceCriticalComponent -{ - public void Update() - { - // 只在必要时记录日志 - if (_performanceCounter % 1000 == 0) - { - Logger.Debug($"Performance: {_performanceCounter} ticks"); - } - } -} - -// 避免:过度日志记录 -[Log(minLevel = LogLevel.Debug)] -public partial class NoisyComponent -{ - public void Update() - { - Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁 - } -} -``` - -#### 2. 延迟上下文初始化 - -```csharp -// 好的做法:延迟初始化 -[ContextAware(useLazy = true)] -public partial class LazyContextComponent : IComponent -{ - // 只有在第一次访问 Context 时才会初始化 - public void Initialize() - { - // 如果这里不需要 Context,就不会初始化 - SomeOtherInitialization(); - } -} -``` - -### 🛡️ 错误处理 - -#### 1. 上下文验证 - -```csharp -[ContextAware(validateContext = true)] -public partial class SafeContextComponent : IComponent -{ - public void ProcessData() - { - if (Context.IsInvalid) - { - Logger.Error("Context is invalid, cannot process data"); - return; - } - - // 安全地使用 Context - var model = Context.GetModel(); - // ... - } -} -``` - -#### 2. 异常处理配合 - -```csharp -[Log] -[ContextAware] -public partial class RobustComponent : IComponent -{ - public void RiskyOperation() - { - try - { - var model = Context.GetModel(); - model.PerformRiskyOperation(); - Logger.Info("Operation completed successfully"); - } - catch (Exception ex) - { - Logger.Error($"Operation failed: {ex.Message}"); - Context.SendEvent(new OperationFailedEvent { Error = ex.Message }); - } - } -} -``` - -## 常见问题 - -### Q: 为什么需要标记类为 `partial`? - -**A**: 源代码生成器需要向现有类添加代码,`partial` 关键字允许一个类的定义分散在多个文件中。生成器会在编译时创建另一个部分类文件,包含生成的代码。 - -```csharp -[Log] -public partial class MyClass { } // ✅ 需要 partial - -[Log] -public class MyClass { } // ❌ 编译错误,无法添加生成代码 -``` - -### Q: 生成的代码在哪里? - -**A**: 生成的代码在编译过程中创建,默认位置在 `obj/Debug/net6.0/generated/` 目录下。可以在项目文件中配置输出位置: - -```xml - - Generated - -``` - -### Q: 如何调试生成器问题? - -**A**: 生成器提供了详细的诊断信息: - -1. **查看错误列表**:编译错误会显示在 IDE 中 -2. **查看生成文件**:检查生成的代码文件 -3. **启用详细日志**:在项目文件中添加: - -```xml - - true - -``` - -### Q: 可以自定义生成器吗? - -**A**: 当前版本的生成器支持有限的配置。如需完全自定义,可以创建自己的源代码生成器项目。 - -### Q: 性能影响如何? - -**A**: 源代码生成器对运行时性能的影响几乎为零: - -- **编译时**:可能会增加编译时间(通常几秒) -- **运行时**:与手写代码性能相同 -- **内存使用**:与手写代码内存使用相同 - -### Q: 与依赖注入框架兼容吗? - -**A**: 完全兼容。生成器创建的是标准代码,可以与任何依赖注入框架配合使用: - -```csharp -[Log] -[ContextAware] -public partial class ServiceComponent : IService -{ - // 可以通过构造函数注入依赖 - private readonly IDependency _dependency; - - public ServiceComponent(IDependency dependency) - { - _dependency = dependency; - Logger.Info("Service initialized with dependency injection"); - } -} -``` - ---- - -## 依赖关系 - -```mermaid -graph TD - A[GFramework.SourceGenerators] --> B[GFramework.SourceGenerators.Abstractions] - A --> C[GFramework.SourceGenerators.Common] - A --> D[GFramework.Core.Abstractions] - A --> E[Microsoft.CodeAnalysis.CSharp] - A --> F[Microsoft.CodeAnalysis.Analyzers] -``` - -## 版本兼容性 - -- **.NET**: 6.0+ -- **Visual Studio**: 2022 17.0+ -- **Rider**: 2022.3+ -- **Roslyn**: 4.0+ - -## 许可证 - -本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 - ---- - -**版本**: 1.0.0 -**更新日期**: 2026-01-12 \ No newline at end of file diff --git a/docs/.vitepress/cache/deps/_metadata.json b/docs/.vitepress/cache/deps/_metadata.json index 12e4974..e5d9b15 100644 --- a/docs/.vitepress/cache/deps/_metadata.json +++ b/docs/.vitepress/cache/deps/_metadata.json @@ -1,25 +1,25 @@ { - "hash": "38412672", + "hash": "74ecdc37", "configHash": "1c302118", - "lockfileHash": "0a72f3f9", - "browserHash": "8d0bc37a", + "lockfileHash": "42b6a898", + "browserHash": "b3e735e5", "optimized": { "vue": { "src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "f12583e4", + "fileHash": "74f911f6", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "d259496e", + "fileHash": "673694b5", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../node_modules/@vueuse/core/dist/index.js", "file": "vitepress___@vueuse_core.js", - "fileHash": "eea92d1c", + "fileHash": "6334babc", "needsInterop": false } }, diff --git a/docs/core/overview.md b/docs/core/overview.md index 9b60350..e9a9112 100644 --- a/docs/core/overview.md +++ b/docs/core/overview.md @@ -1,140 +1,507 @@ -# Core 核心框架概览 +# GFramework.Core 核心框架 -GFramework.Core 是整个框架的基础,提供了平台无关的核心架构能力。 +> 一个基于 CQRS、MVC 和事件驱动的轻量级游戏开发架构框架 -## 核心特性 +## 目录 -### 🏗️ 清洁架构 +- [框架概述](#框架概述) +- [核心概念](#核心概念) +- [架构图](#架构图) +- [快速开始](#快速开始) +- [包说明](#包说明) +- [组件联动](#组件联动) +- [最佳实践](#最佳实践) +- [设计理念](#设计理念) -基于 Model-View-Controller-System-Utility 五层架构,实现清晰的职责分离和高内聚低耦合。 +## 框架概述 -### 🔧 CQRS 模式 +本框架是一个与平台无关的轻量级架构,它结合了多种经典设计模式: -命令查询职责分离,提供类型安全的命令和查询系统,支持可撤销操作。 +- **MVC 架构模式** - 清晰的层次划分 +- **CQRS 模式** - 命令查询职责分离 +- **IoC/DI** - 依赖注入和控制反转 +- **事件驱动** - 松耦合的组件通信 +- **响应式编程** - 可绑定属性和数据流 +- **阶段式生命周期管理** - 精细化的架构状态控制 -### 📡 事件驱动 +**重要说明**:GFramework.Core 是与平台无关的核心模块,不包含任何 Godot 特定代码。Godot 集成功能在 GFramework.Godot 包中实现。 -强大的事件总线系统,支持类型安全的事件发布订阅,实现组件间松耦合通信。 +### 核心特性 -### 🔄 响应式编程 +- **清晰的分层架构** - Model、View、Controller、System、Utility 各司其职 +- **类型安全** - 基于泛型的组件获取和事件系统 +- **松耦合** - 通过事件和接口实现组件解耦 +- **易于测试** - 依赖注入和纯函数设计 +- **可扩展** - 基于接口的规则体系 +- **生命周期管理** - 自动的注册和注销机制 +- **模块化** - 支持架构模块安装 +- **平台无关** - Core 模块可以在任何 .NET 环境中使用 -可绑定属性系统,自动化的数据绑定和 UI 更新机制。 +## 核心概念 -### 🎯 依赖注入 +### 五层架构 -完善的 IoC 容器,支持自动依赖解析和生命周期管理。 - -## 核心组件 - -### 架构组件 - -- **[Architecture](/core/architecture/architecture)** - 应用架构管理器 -- **AbstractModel** - 数据模型基类 -- **AbstractSystem** - 业务系统基类 -- **AbstractUtility** - 工具类基类 - -### 命令查询系统 - -- **[AbstractCommand](/core/command-query/commands)** - 命令基类 -- **AbstractQuery** - 查询基类 -- **CommandExecutor** - 命令执行器 - -### 事件系统 - -- **[EventBus](/core/events/event-bus)** - 事件总线 -- **EasyEvent** - 简单事件 -- **OrEvent** - 组合事件 - -### 属性系统 - -- **[BindableProperty](/core/property/bindable-property)** - 可绑定属性 -- **BindablePropertyGeneric** - 泛型可绑定属性 - -### 工具类 - -- **[IoC 容器](/core/utilities/ioc-container)** - 依赖注入容器 -- **Logger** - 日志系统 -- **ObjectPool** - 对象池 - -## 使用场景 - -Core 模块适用于任何 .NET 应用场景: - -- **游戏开发** - 作为游戏架构基础 -- **桌面应用** - WPF、WinForms 应用 -- **移动应用** - MAUI、Xamarin 应用 -- **Web 应用** - ASP.NET Core 应用 -- **控制台应用** - 命令行工具 - -## 安装 - -```bash -dotnet add package GeWuYou.GFramework.Core -dotnet add package GeWuYou.GFramework.Core.Abstractions +``` +┌─────────────────────────────────────────┐ +│ View / UI │ UI 层:用户界面 +├─────────────────────────────────────────┤ +│ Controller │ 控制层:处理用户输入 +├─────────────────────────────────────────┤ +│ System │ 逻辑层:业务逻辑 +├─────────────────────────────────────────┤ +│ Model │ 数据层:游戏状态 +├─────────────────────────────────────────┤ +│ Utility │ 工具层:无状态工具 +└─────────────────────────────────────────┘ ``` -## 快速示例 +### 横切关注点 + +``` +Command ──┐ +Query ──┼──→ 跨层操作(修改/查询数据) +Event ──┘ +``` + +### 架构阶段 + +``` +初始化:Init → BeforeUtilityInit → AfterUtilityInit → BeforeModelInit → AfterModelInit → BeforeSystemInit → AfterSystemInit → Ready +销毁:Destroy → Destroying → Destroyed +``` + +## 架构图 + +### 整体架构 + +``` + ┌──────────────────┐ + │ Architecture │ ← 管理所有组件 + └────────┬─────────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ┌───▼────┐ ┌───▼────┐ ┌───▼─────┐ + │ Model │ │ System │ │ Utility │ + │ 层 │ │ 层 │ │ 层 │ + └───┬────┘ └───┬────┘ └────────┘ + │ │ + │ ┌─────────────┤ + │ │ │ + ┌───▼────▼───┐ ┌───▼──────┐ + │ Controller │ │ Command/ │ + │ 层 │ │ Query │ + └─────┬──────┘ └──────────┘ + │ + ┌─────▼─────┐ + │ View │ + │ UI │ + └───────────┘ +``` + +### 数据流向 + +``` +用户输入 → Controller → Command → System → Model → Event → Controller → View 更新 + +查询流程:Controller → Query → Model → 返回数据 +``` + +## 快速开始 + +本框架采用"约定优于配置"的设计理念,只需 4 步即可搭建完整的架构。 + +### 为什么需要这个框架? + +在传统开发中,我们经常遇到这些问题: + +- 代码耦合严重:UI 直接访问游戏逻辑,逻辑直接操作 UI +- 难以维护:修改一个功能需要改动多个文件 +- 难以测试:业务逻辑和 UI 混在一起无法独立测试 +- 难以复用:代码紧密耦合,无法在其他项目中复用 + +本框架通过清晰的分层解决这些问题。 + +### 1. 定义架构(Architecture) + +**作用**:Architecture 是整个应用的"中央调度器",负责管理所有组件的生命周期。 ```csharp using GFramework.Core.architecture; -// 定义架构 -public class MyAppArchitecture : Architecture +public class GameArchitecture : Architecture { protected override void Init() { - RegisterModel(new UserModel()); - RegisterSystem(new UserService()); + // 注册 Model - 游戏数据 + RegisterModel(new PlayerModel()); + + // 注册 System - 业务逻辑 + RegisterSystem(new CombatSystem()); + + // 注册 Utility - 工具类 RegisterUtility(new StorageUtility()); } } +``` -// 定义模型 -public class UserModel : AbstractModel +**优势**: + +- **依赖注入**:组件通过上下文获取架构引用 +- **集中管理**:所有组件注册在一处,一目了然 +- **生命周期管理**:自动初始化和销毁 +- **平台无关**:可以在任何 .NET 环境中使用 + +### 2. 定义 Model(数据层) + +**作用**:Model 是应用的"数据库",只负责存储和管理状态。 + +```csharp +public class PlayerModel : AbstractModel { - public BindableProperty Name { get; } = new("User"); - public BindableProperty Score { get; } = new(0); + // 使用 BindableProperty 实现响应式数据 + public BindableProperty Health { get; } = new(100); + public BindableProperty Gold { get; } = new(0); + + protected override void OnInit() + { + // Model 中可以监听自己的数据变化 + Health.Register(hp => + { + if (hp <= 0) this.SendEvent(new PlayerDiedEvent()); + }); + } } -// 使用架构 -var architecture = new MyAppArchitecture(); -architecture.Initialize(); - -var userModel = architecture.GetModel(); -userModel.Name.Value = "New Name"; // 自动触发更新 +// 也可以不使用 BindableProperty +public class PlayerModel : AbstractModel +{ + public int Health { get; private set; } + public int Gold { get; private set; } + + protected override void OnInit() + { + Health = 100; + Gold = 0; + } +} ``` -## 学习路径 +**优势**: -建议按以下顺序学习 Core 模块: +- **数据响应式**:BindableProperty 让数据变化自动通知监听者 +- **职责单一**:只存储数据,不包含复杂业务逻辑 +- **易于测试**:可以独立测试数据逻辑 -1. **[架构基础](/core/architecture/architecture)** - 了解架构组件和生命周期 -2. **[命令查询](/core/command-query/commands)** - 掌握 CQRS 模式 -3. **[事件系统](/core/events/event-bus)** - 学习事件驱动编程 -4. **[属性系统](/core/property/bindable-property)** - 理解响应式编程 -5. **[工具类](/core/utilities/ioc-container)** - 使用辅助工具 +### 3. 定义 System(业务逻辑层) -## 与其他模块的关系 +**作用**:System 是应用的"大脑",处理所有业务逻辑。 -``` -GFramework.Core (基础层) - ↓ 依赖 -GFramework.Game (游戏层) - ↓ 依赖 -GFramework.Godot (引擎层) +```csharp +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + // System 通过事件驱动,响应游戏中的各种事件 + this.RegisterEvent(OnEnemyAttack); + } + + private void OnEnemyAttack(EnemyAttackEvent e) + { + var playerModel = this.GetModel(); + + // 处理业务逻辑:计算伤害、更新数据 + playerModel.Health.Value -= e.Damage; + + // 发送事件通知其他组件 + this.SendEvent(new PlayerTookDamageEvent { Damage = e.Damage }); + } +} ``` -Core 模块是其他所有模块的基础,提供核心的架构能力和设计模式实现。 +**优势**: -## 性能特点 +- **事件驱动**:通过事件解耦,不同 System 之间松耦合 +- **可组合**:多个 System 协同工作,每个专注自己的领域 +- **易于扩展**:新增功能只需添加新的 System 和事件监听 -- **零额外开销** - 编译时优化,运行时无性能损失 -- **内存友好** - 自动内存管理和对象回收 -- **类型安全** - 编译时类型检查,避免运行时错误 -- **可扩展** - 支持自定义扩展和插件 +### 4. 定义 Controller(控制层) -## 下一步 +**作用**:Controller 是"桥梁",连接 UI 和业务逻辑。 -- [深入了解架构组件](/core/architecture/architecture) -- [查看完整 API 参考](/api-reference/core-api) -- [学习最佳实践](/tutorials/best-practices) \ No newline at end of file +```csharp +public class PlayerController : IController +{ + // 通过依赖注入获取架构 + private readonly IArchitecture _architecture; + + public PlayerController(IArchitecture architecture) + { + _architecture = architecture; + } + + // 监听模型变化 + public void Initialize() + { + var playerModel = _architecture.GetModel(); + + // 数据绑定:Model 数据变化自动更新 UI + playerModel.Health.RegisterWithInitValue(OnHealthChanged); + } + + private void OnHealthChanged(int hp) + { + // 更新 UI 显示 + UpdateHealthDisplay(hp); + } + + private void UpdateHealthDisplay(int hp) { /* UI 更新逻辑 */ } +} +``` + +**优势**: + +- **自动更新 UI**:通过 BindableProperty,数据变化自动反映到界面 +- **分离关注点**:UI 逻辑和业务逻辑完全分离 +- **易于测试**:可以通过依赖注入模拟架构进行测试 + +### 完成!现在你有了一个完整的架构 + +这 4 步完成后,你就拥有了: + +- **清晰的数据层**(Model) +- **独立的业务逻辑**(System) +- **灵活的控制层**(Controller) +- **统一的生命周期管理**(Architecture) + +### 下一步该做什么? + +1. **添加 Command**:封装用户操作(如购买物品、使用技能) +2. **添加 Query**:封装数据查询(如查询背包物品数量) +3. **添加更多 System**:如任务系统、背包系统、商店系统 +4. **使用 Utility**:添加工具类(如存档工具、数学工具) +5. **使用模块**:通过 IArchitectureModule 扩展架构功能 + +## 包说明 + +| 包名 | 职责 | 文档 | +|------------------|-----------------|------------------------------------| +| **architecture** | 架构核心,管理所有组件生命周期 | [查看](/core/architecture/README.md) | +| **constants** | 框架常量定义 | 本文档 | +| **model** | 数据模型层,存储状态 | [查看](/core/model/README.md) | +| **system** | 业务逻辑层,处理业务规则 | [查看](/core/system/README.md) | +| **controller** | 控制器层,连接视图和逻辑 | (在 Abstractions 中) | +| **utility** | 工具类层,提供无状态工具 | [查看](/core/utility/README.md) | +| **command** | 命令模式,封装写操作 | [查看](/core/command/README.md) | +| **query** | 查询模式,封装读操作 | [查看](/core/query/README.md) | +| **events** | 事件系统,组件间通信 | [查看](/core/events/README.md) | +| **property** | 可绑定属性,响应式编程 | [查看](/core/property/README.md) | +| **ioc** | IoC 容器,依赖注入 | [查看](/core/ioc/README.md) | +| **rule** | 规则接口,定义组件约束 | [查看](/core/rule/README.md) | +| **extensions** | 扩展方法,简化 API 调用 | [查看](/core/extensions/README.md) | +| **logging** | 日志系统,记录运行日志 | [查看](/core/logging/README.md) | +| **environment** | 环境接口,提供运行环境信息 | [查看](/core/environment/README.md) | + +## 组件联动 + +### 1. 初始化流程 + +``` +创建 Architecture 实例 + └─> Init() + ├─> RegisterModel → Model.SetContext() → Model.Init() + ├─> RegisterSystem → System.SetContext() → System.Init() + └─> RegisterUtility → Utility 注册到容器 +``` + +### 2. Command 执行流程 + +``` +Controller.SendCommand(command) + └─> command.Execute() + └─> command.OnDo() // 子类实现 + ├─> GetModel() // 获取数据 + ├─> 修改 Model 数据 + └─> SendEvent() // 发送事件 +``` + +### 3. Event 传播流程 + +``` +组件.SendEvent(event) + └─> TypeEventSystem.Send(event) + └─> 通知所有订阅者 + ├─> Controller 响应 → 更新 UI + ├─> System 响应 → 执行逻辑 + └─> Model 响应 → 更新状态 +``` + +### 4. BindableProperty 数据绑定 + +``` +Model: BindableProperty Health = new(100); +Controller: Health.RegisterWithInitValue(hp => UpdateUI(hp)) +修改值: Health.Value = 50 → 触发所有回调 → 更新 UI +``` + +## 最佳实践 + +### 1. 分层职责原则 + +每一层都有明确的职责边界,遵循这些原则能让代码更清晰、更易维护。 + +**Model 层**: + +```csharp +// 好:只存储数据 +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + protected override void OnInit() { } +} + +// 坏:包含业务逻辑 +public class PlayerModel : AbstractModel +{ + public void TakeDamage(int damage) // 业务逻辑应在 System + { + Health.Value -= damage; + if (Health.Value <= 0) Die(); + } +} +``` + +**System 层**: + +```csharp +// 好:处理业务逻辑 +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var target = this.GetModel(); + int finalDamage = CalculateDamage(e.BaseDamage, target); + target.Health.Value -= finalDamage; + } +} +``` + +### 2. 通信方式选择指南 + +| 通信方式 | 使用场景 | 优势 | +|----------------------|-----------|----------| +| **Command** | 用户操作、修改状态 | 可撤销、可记录 | +| **Query** | 查询数据、检查条件 | 明确只读意图 | +| **Event** | 通知其他组件 | 松耦合、可扩展 | +| **BindableProperty** | 数据变化通知 | 自动化、不会遗漏 | + +### 3. 生命周期管理 + +**为什么需要注销?** + +忘记注销监听器会导致: + +- **内存泄漏**:对象无法被 GC 回收 +- **逻辑错误**:已销毁的对象仍在响应事件 + +```csharp +// 使用 UnRegisterList 统一管理 +private IUnRegisterList _unregisterList = new UnRegisterList(); + +public void Initialize() +{ + this.RegisterEvent(OnEvent1) + .AddToUnregisterList(_unregisterList); + + model.Property.Register(OnPropertyChanged) + .AddToUnregisterList(_unregisterList); +} + +public void Cleanup() +{ + _unregisterList.UnRegisterAll(); +} +``` + +### 4. 性能优化技巧 + +```csharp +// 低效:每帧都查询 +var model = _architecture.GetModel(); // 频繁调用 + +// 高效:缓存引用 +private PlayerModel _playerModel; + +public void Initialize() +{ + _playerModel = _architecture.GetModel(); // 只查询一次 +} +``` + +## 设计理念 + +框架的设计遵循 SOLID 原则和经典设计模式。 + +### 1. 单一职责原则(SRP) + +- **Model**:只负责存储数据 +- **System**:只负责处理业务逻辑 +- **Controller**:只负责协调和输入处理 +- **Utility**:只负责提供工具方法 + +### 2. 开闭原则(OCP) + +- 通过**事件系统**添加新功能,无需修改现有代码 +- 新的 System 可以监听现有事件,插入自己的逻辑 + +### 3. 依赖倒置原则(DIP) + +- 所有组件通过接口交互 +- 通过 IoC 容器注入依赖 +- 易于替换实现和编写测试 + +### 4. 接口隔离原则(ISP) + +```csharp +// 小而专注的接口 +public interface ICanGetModel : IBelongToArchitecture { } +public interface ICanSendCommand : IBelongToArchitecture { } +public interface ICanRegisterEvent : IBelongToArchitecture { } + +// 组合需要的能力 +public interface IController : + ICanGetModel, + ICanSendCommand, + ICanRegisterEvent { } +``` + +### 5. 组合优于继承 + +通过接口组合获得能力,而不是通过继承。 + +### 框架核心设计模式 + +| 设计模式 | 应用位置 | 解决的问题 | 带来的好处 | +|-----------|------------|----------|--------| +| **工厂模式** | IoC 容器 | 组件的创建和管理 | 解耦创建逻辑 | +| **观察者模式** | Event 系统 | 组件间的通信 | 松耦合通信 | +| **命令模式** | Command | 封装操作请求 | 支持撤销重做 | +| **策略模式** | System | 不同的业务逻辑 | 易于切换策略 | +| **依赖注入** | 整体架构 | 组件间的依赖 | 自动管理依赖 | +| **模板方法** | Abstract 类 | 定义算法骨架 | 统一流程规范 | + +### 平台无关性 + +- **GFramework.Core**:纯 .NET 库,无任何平台特定代码 +- **GFramework.Godot**:Godot 特定实现,包含 Node 扩展、GodotLogger 等 +- 可以轻松将 Core 框架移植到其他平台(Unity、.NET MAUI 等) + +--- + +**版本**: 1.0.0 +**许可证**: Apache 2.0 \ No newline at end of file diff --git a/docs/game/overview.md b/docs/game/overview.md index 60d7d8a..218c2d6 100644 --- a/docs/game/overview.md +++ b/docs/game/overview.md @@ -1,138 +1,1406 @@ -# Game 游戏模块概览 +# GFramework.Game -GFramework.Game 为游戏开发提供专门的功能模块,包括资产管理、存储系统、序列化等核心游戏功能。 +> 游戏特定功能抽象 - 为游戏开发提供专门的工具和系统 + +GFramework.Game 是 GFramework 框架的游戏特定功能模块,提供了游戏开发中常用的抽象和工具,包括资产管理、存储系统、序列化等核心功能。 + +## 📋 目录 + +- [概述](#概述) +- [核心特性](#核心特性) +- [架构模块系统](#架构模块系统) +- [资产管理](#资产管理) +- [存储系统](#存储系统) +- [序列化系统](#序列化系统) +- [使用示例](#使用示例) +- [最佳实践](#最佳实践) +- [性能特性](#性能特性) + +## 概述 + +GFramework.Game 为游戏开发提供了专门的功能模块,与 GFramework.Core 的平台无关特性完美结合,为游戏项目提供了一整套完整的解决方案。 + +### 核心设计理念 + +- **游戏导向**:专门针对游戏开发场景设计 +- **模块化架构**:可插拔的模块系统,按需组合 +- **数据持久化**:完善的存档和数据管理方案 +- **资源管理**:高效的资源加载和管理机制 ## 核心特性 -### 🎮 游戏专用模块 +### 🏗️ 模块化架构 -- **架构模块系统** - 可插拔的游戏功能模块 -- **资产管理** - 统一的资源注册和查询系统 -- **存储系统** - 分层的数据持久化方案 -- **序列化** - 高性能的数据序列化支持 +- **AbstractModule**:可重用的架构模块基类 +- **生命周期管理**:与框架生命周期深度集成 +- **依赖注入**:模块间的依赖自动管理 +- **配置驱动**:灵活的模块配置系统 -### 🏗️ 模块化设计 +### 📦 资产管理 + +- **统一资源目录**:集中化的资源注册和查询 +- **类型安全**:编译时类型检查和泛型支持 +- **重复检测**:自动检测资源重复注册 +- **映射支持**:灵活的资源映射和别名系统 + +### 💾 存储系统 + +- **分层存储**:命名空间支持的存储隔离 +- **多格式支持**:JSON、二进制等多种存储格式 +- **异步操作**:完整的异步存储 API +- **版本兼容**:存档版本管理和迁移支持 + +### 🔄 序列化系统 + +- **JSON 集成**:基于 Newtonsoft.Json 的序列化 +- **自定义序列化**:支持自定义序列化逻辑 +- **性能优化**:序列化缓存和优化策略 +- **类型安全**:强类型的序列化和反序列化 + +## 架构模块系统 + +### AbstractModule 基础使用 ```csharp +using GFramework.Game.architecture; + public class AudioModule : AbstractModule { public override void Install(IArchitecture architecture) { + // 注册音频相关系统 architecture.RegisterSystem(new AudioSystem()); + architecture.RegisterSystem(new MusicSystem()); + + // 注册音频工具 architecture.RegisterUtility(new AudioUtility()); + architecture.RegisterUtility(new MusicUtility()); + } + + public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + switch (phase) + { + case ArchitecturePhase.BeforeModelInit: + // 在模型初始化前准备音频资源 + PreloadAudioResources(); + break; + + case ArchitecturePhase.Ready: + // 架构准备就绪,开始播放背景音乐 + StartBackgroundMusic(); + break; + + case ArchitecturePhase.Destroying: + // 清理音频资源 + CleanupAudioResources(); + break; + } + } + + private void PreloadAudioResources() + { + // 预加载音频资源 + var audioUtility = architecture.GetUtility(); + audioUtility.PreloadAudio("background_music"); + audioUtility.PreloadAudio("shoot_sound"); + audioUtility.PreloadAudio("explosion_sound"); + } + + private void StartBackgroundMusic() + { + var musicSystem = architecture.GetSystem(); + musicSystem.PlayBackgroundMusic("background_music"); + } + + private void CleanupAudioResources() + { + var audioUtility = architecture.GetUtility(); + audioUtility.UnloadAllAudio(); } } ``` -## 核心组件 - -### 模块系统 - -- **AbstractModule** - 模块基类 -- **ArchitectureModule** - 架构模块接口 -- **ModuleManager** - 模块管理器 - -### 存储系统 - -- **[ScopedStorage](/game/storage/scoped-storage)** - 分层存储 -- **IStorage** - 存储接口 -- **FileStorage** - 文件存储实现 - -### 资源管理 - -- **[AbstractAssetCatalogUtility](/game/assets/asset-catalog)** - 资源目录 -- **AssetFactory** - 资源工厂 -- **ResourceLoader** - 资源加载器 - -### 序列化 - -- **[JsonSerializer](/game/serialization/json-serializer)** - JSON 序列化 -- **BinarySerializer** - 二进制序列化 -- **CustomSerializer** - 自定义序列化 - -## 使用场景 - -Game 模块专为游戏开发设计: - -- **2D/3D 游戏** - 支持各种类型的游戏项目 -- **存档系统** - 完善的存档和读档功能 -- **资源配置** - 集中的资源管理和加载 -- **数据持久化** - 游戏数据的保存和恢复 - -## 安装 - -```bash -# 需要先安装 Core 模块 -dotnet add package GeWuYou.GFramework.Core -dotnet add package GeWuYou.GFramework.Game -dotnet add package GeWuYou.GFramework.Game.Abstractions -``` - -## 快速示例 +### 复杂模块示例 ```csharp -using GFramework.Game.storage; -using GFramework.Game.assets; - -// 存储系统使用 -public class GameDataManager +public class SaveModule : AbstractModule { - private IStorage _playerStorage; + private ISaveSystem _saveSystem; + private IDataMigrationManager _migrationManager; - public GameDataManager(IStorage rootStorage) + public override void Install(IArchitecture architecture) { - _playerStorage = new ScopedStorage(rootStorage, "player"); + // 注册存储相关组件 + _saveSystem = new SaveSystem(); + architecture.RegisterUtility(_saveSystem); + + _migrationManager = new DataMigrationManager(); + architecture.RegisterUtility(_migrationManager); + + // 注册数据版本管理 + architecture.RegisterSystem(new SaveDataVersionSystem()); } - public void SavePlayerData(PlayerData data) + public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) { - _playerStorage.Write("profile", data); - _playerStorage.Write("inventory", data.Inventory); + switch (phase) + { + case ArchitecturePhase.AfterModelInit: + // 在模型初始化后设置数据迁移 + SetupDataMigrations(); + break; + + case ArchitecturePhase.Ready: + // 尝试自动加载存档 + TryAutoLoadSave(); + break; + } + } + + private void SetupDataMigrations() + { + // 设置数据版本迁移 + _migrationManager.RegisterMigration(1, 2, MigratePlayerDataV1ToV2); + _migrationManager.RegisterMigration(2, 3, MigratePlayerDataV2ToV3); + } + + private void TryAutoLoadSave() + { + if (_saveSystem.HasAutoSave()) + { + _saveSystem.LoadAutoSave(); + } + } + + private PlayerData MigratePlayerDataV1ToV2(PlayerData v1Data) + { + return new PlayerData + { + // 迁移逻辑 + Name = v1Data.Name, + Health = v1Data.Health, + // 新增字段 + MaxHealth = 100, + Level = 1 + }; + } + + private PlayerData MigratePlayerDataV2ToV3(PlayerData v2Data) + { + return new PlayerData + { + // 迁移逻辑 + Name = v2Data.Name, + Health = v2Data.Health, + MaxHealth = v2Data.MaxHealth, + Level = v2Data.Level, + // 新增字段 + Experience = 0, + Skills = new List() + }; } } +``` + +### 模块配置 + +```csharp +public class ModuleConfig +{ + public string SaveDirectory { get; set; } = "saves"; + public int AutoSaveInterval { get; set; } = 300; // 5分钟 + public bool EnableDataCompression { get; set; } = true; + public int MaxSaveSlots { get; set; } = 10; +} + +public class ConfigurableSaveModule : AbstractModule +{ + private ModuleConfig _config; + + public ConfigurableSaveModule(ModuleConfig config) + { + _config = config; + } + + public override void Install(IArchitecture architecture) + { + var saveSystem = new SaveSystem(_config); + architecture.RegisterUtility(saveSystem); + + // 配置自动保存 + if (_config.AutoSaveInterval > 0) + { + architecture.RegisterSystem(new AutoSaveSystem(_config.AutoSaveInterval)); + } + } +} +``` + +## 资产管理 + +### AbstractAssetCatalogUtility 基础使用 + +```csharp +using GFramework.Game.assets; -// 资源管理使用 public class GameAssetCatalog : AbstractAssetCatalogUtility { public override void Initialize() { + base.Initialize(); + + // 注册场景资产 RegisterSceneUnit("Player", "res://scenes/Player.tscn"); + RegisterSceneUnit("Enemy", "res://scenes/Enemy.tscn"); + RegisterSceneUnit("Bullet", "res://scenes/Bullet.tscn"); + + // 注册场景页面 + RegisterScenePage("MainMenu", "res://ui/MainMenu.tscn"); + RegisterScenePage("GameUI", "res://ui/GameUI.tscn"); + RegisterScenePage("PauseMenu", "res://ui/PauseMenu.tscn"); + + // 注册通用资产 RegisterAsset("PlayerTexture", "res://textures/player.png"); + RegisterAsset("EnemyTexture", "res://textures/enemy.png"); + RegisterAsset("ShootSound", "res://audio/shoot.wav"); + RegisterAsset("ExplosionSound", "res://audio/explosion.wav"); + } + + // 自定义资产验证 + protected override bool ValidateAsset(string key, string path) + { + if (!FileAccess.FileExists(path)) + { + GD.PrintErr($"Asset file not found: {path}"); + return false; + } + + return true; + } + + // 资产加载完成回调 + protected override void OnAssetLoaded(string key, object asset) + { + GD.Print($"Asset loaded: {key}"); + + // 对特定资产进行额外处理 + if (key == "PlayerTexture") + { + var texture = (Texture2D)asset; + // 预处理纹理... + } } } ``` -## 学习路径 +### 资产映射系统 -建议按以下顺序学习 Game 模块: +```csharp +public class AssetMapping +{ + public string Key { get; set; } + public string Path { get; set; } + public Type Type { get; set; } + public Dictionary Metadata { get; set; } = new(); +} -1. **[模块系统](/game/modules/architecture-modules)** - 了解模块化架构 -2. **[存储系统](/game/storage/scoped-storage)** - 掌握数据持久化 -3. **[资源管理](/game/assets/asset-catalog)** - 学习资源处理 -4. **[序列化](/game/serialization/json-serializer)** - 理解数据序列化 - -## 与 Core 的关系 - -``` -GFramework.Core (提供基础架构) - ↓ 扩展 -GFramework.Game (提供游戏功能) - ↓ 集成 -GFramework.Godot (提供引擎支持) +public class AdvancedAssetCatalog : AbstractAssetCatalogUtility +{ + public override void Initialize() + { + base.Initialize(); + + // 使用映射对象注册资产 + RegisterAsset(new AssetMapping + { + Key = "Player", + Path = "res://scenes/Player.tscn", + Type = typeof(PackedScene), + Metadata = new Dictionary + { + ["category"] = "character", + ["tags"] = new[] { "player", "hero", "controlled" }, + ["health"] = 100, + ["speed"] = 5.0f + } + }); + + // 批量注册 + RegisterAssetsFromDirectory("res://textures/", "*.png", "texture"); + RegisterAssetsFromDirectory("res://audio/", "*.wav", "sound"); + } + + private void RegisterAssetsFromDirectory(string directory, string pattern, string prefix) + { + var dir = DirAccess.Open(directory); + if (dir == null) return; + + dir.ListDirBegin(); + var fileName = dir.GetNext(); + + while (!string.IsNullOrEmpty(fileName)) + { + if (fileName.EndsWith(pattern.Substring(1))) + { + var key = $"{prefix}{Path.GetFileNameWithoutExtension(fileName)}"; + var path = Path.Combine(directory, fileName); + + RegisterAsset(key, path); + } + + fileName = dir.GetNext(); + } + + dir.ListDirEnd(); + } +} ``` -Game 模块建立在 Core 模块之上,为游戏开发提供专门的功能支持。 +### 资产工厂模式 -## 性能优化 +```csharp +public interface IAssetFactory +{ + T Create(string key); + bool CanCreate(string key); +} -- **缓存机制** - 智能缓存减少重复加载 -- **批量操作** - 支持批量数据处理 -- **异步支持** - 非阻塞的 I/O 操作 -- **内存管理** - 自动资源回收和管理 +public class PlayerFactory : IAssetFactory +{ + private readonly AbstractAssetCatalogUtility _catalog; + + public PlayerFactory(AbstractAssetCatalogUtility catalog) + { + _catalog = catalog; + } + + public Player Create(string key) + { + var scene = _catalog.GetScene(key); + var player = scene.Instantiate(); + + // 配置玩家 + player.Health = GetPlayerHealth(key); + player.Speed = GetPlayerSpeed(key); + + return player; + } + + public bool CanCreate(string key) + { + return _catalog.HasScene(key) && key.StartsWith("Player"); + } + + private int GetPlayerHealth(string key) + { + var metadata = _catalog.GetAssetMetadata(key); + return metadata?.GetValueOrDefault("health", 100) ?? 100; + } + + private float GetPlayerSpeed(string key) + { + var metadata = _catalog.GetAssetMetadata(key); + return metadata?.GetValueOrDefault("speed", 5.0f) ?? 5.0f; + } +} +``` -## 下一步 +## 存储系统 -- [深入了解模块系统](/game/modules/architecture-modules) -- [查看存储系统文档](/game/storage/scoped-storage) -- [学习资源管理](/game/assets/asset-catalog) -- [查看 API 参考](/api-reference/game-api) \ No newline at end of file +### ScopedStorage 分层存储 + +```csharp +using GFramework.Game.storage; + +public class GameDataManager +{ + private readonly IStorage _rootStorage; + private readonly IStorage _playerStorage; + private readonly IStorage _saveStorage; + + public GameDataManager(IStorage rootStorage) + { + _rootStorage = rootStorage; + + // 创建分层存储 + _playerStorage = new ScopedStorage(rootStorage, "player"); + _saveStorage = new ScopedStorage(rootStorage, "saves"); + } + + public void SavePlayerData(string playerId, PlayerData data) + { + _playerStorage.Write($"{playerId}/profile", data); + _playerStorage.Write($"{playerId}/inventory", data.Inventory); + _playerStorage.Write($"{playerId}/stats", data.Stats); + } + + public PlayerData LoadPlayerData(string playerId) + { + var profile = _playerStorage.Read($"{playerId}/profile"); + var inventory = _playerStorage.Read($"{playerId}/inventory", new Inventory()); + var stats = _playerStorage.Read($"{playerId}/stats", new PlayerStats()); + + return new PlayerData + { + Profile = profile, + Inventory = inventory, + Stats = stats + }; + } + + public void SaveGame(int slotId, SaveData data) + { + _saveStorage.Write($"slot_{slotId}", data); + _saveStorage.Write($"slot_{slotId}/timestamp", DateTime.Now); + _saveStorage.Write($"slot_{slotId}/version", data.Version); + } + + public SaveData LoadGame(int slotId) + { + return _saveStorage.Read($"slot_{slotId}"); + } + + public List GetSaveSlotInfos() + { + var infos = new List(); + + for (int i = 1; i <= 10; i++) + { + var timestamp = _saveStorage.Read($"slot_{i}/timestamp"); + var version = _saveStorage.Read($"slot_{i}/version"); + + if (timestamp != default) + { + infos.Add(new SaveSlotInfo + { + SlotId = i, + Timestamp = timestamp, + Version = version + }); + } + } + + return infos; + } +} +``` + +### 自定义存储实现 + +```csharp +public class EncryptedStorage : IStorage +{ + private readonly IStorage _innerStorage; + private readonly IEncryptor _encryptor; + + public EncryptedStorage(IStorage innerStorage, IEncryptor encryptor) + { + _innerStorage = innerStorage; + _encryptor = encryptor; + } + + public void Write(string key, T data) + { + var json = JsonConvert.SerializeObject(data); + var encrypted = _encryptor.Encrypt(json); + _innerStorage.Write(key, encrypted); + } + + public T Read(string key, T defaultValue = default) + { + var encrypted = _innerStorage.Read(key); + if (string.IsNullOrEmpty(encrypted)) + return defaultValue; + + var json = _encryptor.Decrypt(encrypted); + return JsonConvert.DeserializeObject(json); + } + + public async Task WriteAsync(string key, T data) + { + var json = JsonConvert.SerializeObject(data); + var encrypted = _encryptor.Encrypt(json); + await _innerStorage.WriteAsync(key, encrypted); + } + + public async Task ReadAsync(string key, T defaultValue = default) + { + var encrypted = await _innerStorage.ReadAsync(key); + if (string.IsNullOrEmpty(encrypted)) + return defaultValue; + + var json = _encryptor.Decrypt(encrypted); + return JsonConvert.DeserializeObject(json); + } + + public bool Has(string key) + { + return _innerStorage.Has(key); + } + + public void Delete(string key) + { + _innerStorage.Delete(key); + } + + public void Clear() + { + _innerStorage.Clear(); + } +} +``` + +### 存储缓存层 + +```csharp +public class CachedStorage : IStorage +{ + private readonly IStorage _innerStorage; + private readonly Dictionary _cache; + private readonly Dictionary _cacheTimestamps; + private readonly TimeSpan _cacheExpiry; + + public CachedStorage(IStorage innerStorage, TimeSpan cacheExpiry = default) + { + _innerStorage = innerStorage; + _cacheExpiry = cacheExpiry == default ? TimeSpan.FromMinutes(5) : cacheExpiry; + _cache = new Dictionary(); + _cacheTimestamps = new Dictionary(); + } + + public T Read(string key, T defaultValue = default) + { + if (_cache.TryGetValue(key, out var cachedValue) && + !IsCacheExpired(key)) + { + return (T)cachedValue; + } + + var value = _innerStorage.Read(key, defaultValue); + UpdateCache(key, value); + + return value; + } + + public void Write(string key, T data) + { + _innerStorage.Write(key, data); + UpdateCache(key, data); + } + + public async Task ReadAsync(string key, T defaultValue = default) + { + if (_cache.TryGetValue(key, out var cachedValue) && + !IsCacheExpired(key)) + { + return (T)cachedValue; + } + + var value = await _innerStorage.ReadAsync(key, defaultValue); + UpdateCache(key, value); + + return value; + } + + public async Task WriteAsync(string key, T data) + { + await _innerStorage.WriteAsync(key, data); + UpdateCache(key, data); + } + + public bool Has(string key) + { + return _cache.ContainsKey(key) || _innerStorage.Has(key); + } + + public void Delete(string key) + { + _cache.Remove(key); + _cacheTimestamps.Remove(key); + _innerStorage.Delete(key); + } + + public void Clear() + { + _cache.Clear(); + _cacheTimestamps.Clear(); + _innerStorage.Clear(); + } + + public void ClearCache() + { + _cache.Clear(); + _cacheTimestamps.Clear(); + } + + private bool IsCacheExpired(string key) + { + if (!_cacheTimestamps.TryGetValue(key, out var timestamp)) + return true; + + return DateTime.Now - timestamp > _cacheExpiry; + } + + private void UpdateCache(string key, T value) + { + _cache[key] = value; + _cacheTimestamps[key] = DateTime.Now; + } +} +``` + +## 序列化系统 + +### JsonSerializer 使用 + +```csharp +using GFramework.Game.serializer; + +public class GameDataSerializer +{ + private readonly JsonSerializer _serializer; + + public GameDataSerializer() + { + _serializer = new JsonSerializer(new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Populate, + TypeNameHandling = TypeNameHandling.Auto + }); + + // 自定义转换器 + _serializer.Converters.Add(new Vector2JsonConverter()); + _serializer.Converters.Add(new ColorJsonConverter()); + _serializer.Converters.Add(new GodotResourceJsonConverter()); + } + + public string Serialize(T data) + { + return _serializer.Serialize(data); + } + + public T Deserialize(string json) + { + return _serializer.Deserialize(json); + } + + public void SerializeToFile(string path, T data) + { + var json = Serialize(data); + FileAccess.Open(path, FileAccess.ModeFlags.Write).StoreString(json); + } + + public T DeserializeFromFile(string path) + { + if (!FileAccess.FileExists(path)) + return default(T); + + var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); + var json = file.GetAsText(); + file.Close(); + + return Deserialize(json); + } +} +``` + +### 自定义 JSON 转换器 + +```csharp +public class Vector2JsonConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("x"); + writer.WriteValue(value.X); + writer.WritePropertyName("y"); + writer.WriteValue(value.Y); + writer.WriteEndObject(); + } + + public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return Vector2.Zero; + + var obj = serializer.Deserialize>(reader); + return new Vector2(obj["x"], obj["y"]); + } +} + +public class ColorJsonConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, Color value, JsonSerializer serializer) + { + writer.WriteValue(value.ToHtml()); + } + + public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return Colors.White; + + var html = reader.Value.ToString(); + return Color.FromHtml(html); + } +} + +public class GodotResourceJsonConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, Resource value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + writer.WriteStartObject(); + writer.WritePropertyName("$type"); + writer.WriteValue(value.GetType().Name); + writer.WritePropertyName("path"); + writer.WriteValue(value.ResourcePath); + writer.WriteEndObject(); + } + + public override Resource ReadJson(JsonReader reader, Type objectType, Resource existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + var obj = serializer.Deserialize>(reader); + var path = obj["path"]; + + return GD.Load(path); + } +} +``` + +### 版本化序列化 + +```csharp +public class VersionedData +{ + public int Version { get; set; } + public Dictionary Data { get; set; } = new(); +} + +public class VersionedSerializer +{ + private readonly Dictionary _typeVersions = new(); + private readonly Dictionary _migrations = new(); + + public void RegisterVersion(int version) + { + _typeVersions[typeof(T)] = version; + } + + public void RegisterMigration(int fromVersion, int toVersion, IDataMigration migration) + { + var key = GetMigrationKey(typeof(T), fromVersion, toVersion); + _migrations[key] = migration; + } + + public string Serialize(T data) + { + var versioned = new VersionedData + { + Version = _typeVersions.GetValueOrDefault(typeof(T), 1), + Data = new Dictionary + { + ["type"] = typeof(T).Name, + ["data"] = JsonConvert.SerializeObject(data) + } + }; + + return JsonConvert.SerializeObject(versioned); + } + + public T Deserialize(string json) + { + var versioned = JsonConvert.DeserializeObject(json); + var currentVersion = _typeVersions.GetValueOrDefault(typeof(T), 1); + + // 迁移数据到当前版本 + var dataJson = MigrateData(versioned.Data["data"] as string, versioned.Version, currentVersion); + + return JsonConvert.DeserializeObject(dataJson); + } + + private string MigrateData(string dataJson, int fromVersion, int toVersion) + { + var currentData = dataJson; + var currentVersion = fromVersion; + + while (currentVersion < toVersion) + { + var migrationKey = GetMigrationKey(typeof(T), currentVersion, currentVersion + 1); + + if (_migrations.TryGetValue(migrationKey, out var migration)) + { + currentData = migration.Migrate(currentData); + currentVersion++; + } + else + { + throw new InvalidOperationException($"No migration found from version {currentVersion} to {currentVersion + 1}"); + } + } + + return currentData; + } + + private int GetMigrationKey(Type type, int fromVersion, int toVersion) + { + return HashCode.Combine(type.Name, fromVersion, toVersion); + } +} + +public interface IDataMigration +{ + string Migrate(string data); +} + +public interface IDataMigration : IDataMigration +{ + T Migrate(T data); +} +``` + +## 使用示例 + +### 完整的游戏数据管理系统 + +```csharp +// 1. 定义数据模型 +public class GameProfile +{ + public string PlayerName { get; set; } + public DateTime LastPlayed { get; set; } + public int TotalPlayTime { get; set; } + public List UnlockedAchievements { get; set; } = new(); +} + +public class GameSettings +{ + public float MasterVolume { get; set; } = 1.0f; + public float MusicVolume { get; set; } = 0.8f; + public float SFXVolume { get; set; } = 0.9f; + public GraphicsSettings Graphics { get; set; } = new(); + public InputSettings Input { get; set; } = new(); +} + +// 2. 创建游戏管理器 +[ContextAware] +[Log] +public partial class GameManager : Node, IController +{ + private GameAssetCatalog _assetCatalog; + private GameDataManager _dataManager; + private GameDataSerializer _serializer; + + protected override void OnInit() + { + // 初始化资产目录 + _assetCatalog = new GameAssetCatalog(); + _assetCatalog.Initialize(); + + // 初始化存储系统 + var rootStorage = new FileStorage("user://data/"); + var cachedStorage = new CachedStorage(rootStorage); + _dataManager = new GameDataManager(cachedStorage); + + // 初始化序列化器 + _serializer = new GameDataSerializer(); + + Logger.Info("Game manager initialized"); + } + + public void StartNewGame(string playerName) + { + Logger.Info($"Starting new game for player: {playerName}"); + + // 创建新的游戏档案 + var profile = new GameProfile + { + PlayerName = playerName, + LastPlayed = DateTime.Now, + TotalPlayTime = 0 + }; + + _dataManager.SavePlayerData("current", profile); + + // 加载初始场景 + LoadInitialScene(); + + Context.SendEvent(new NewGameStartedEvent { PlayerName = playerName }); + } + + public void LoadGame(int slotId) + { + Logger.Info($"Loading game from slot {slotId}"); + + try + { + var saveData = _dataManager.LoadGame(slotId); + if (saveData != null) + { + // 恢复游戏状态 + RestoreGameState(saveData); + + Context.SendEvent(new GameLoadedEvent { SlotId = slotId }); + Logger.Info("Game loaded successfully"); + } + else + { + Logger.Warning($"No save data found in slot {slotId}"); + Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId }); + } + } + catch (Exception ex) + { + Logger.Error($"Failed to load game: {ex.Message}"); + Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message }); + } + } + + public void SaveGame(int slotId) + { + Logger.Info($"Saving game to slot {slotId}"); + + try + { + var saveData = CreateSaveData(); + _dataManager.SaveGame(slotId, saveData); + + Context.SendEvent(new GameSavedEvent { SlotId = slotId }); + Logger.Info("Game saved successfully"); + } + catch (Exception ex) + { + Logger.Error($"Failed to save game: {ex.Message}"); + Context.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message }); + } + } + + private void LoadInitialScene() + { + var playerScene = _assetCatalog.GetScene("Player"); + var player = playerScene.Instantiate(); + + var gameWorldScene = _assetCatalog.GetScene("GameWorld"); + var gameWorld = gameWorldScene.Instantiate(); + + AddChild(gameWorld); + gameWorld.AddChild(player); + } + + private void RestoreGameState(SaveData saveData) + { + // 恢复玩家位置 + var playerScene = _assetCatalog.GetScene("Player"); + var player = playerScene.Instantiate(); + player.Position = saveData.PlayerPosition; + + // 恢复游戏世界 + var gameWorldScene = _assetCatalog.GetScene("GameWorld"); + var gameWorld = gameWorldScene.Instantiate(); + + AddChild(gameWorld); + gameWorld.AddChild(player); + + // 恢复其他游戏状态 + Context.GetModel().Health.Value = saveData.PlayerHealth; + Context.GetModel().CurrentLevel.Value = saveData.CurrentLevel; + Context.GetModel().LoadFromData(saveData.Inventory); + } + + private SaveData CreateSaveData() + { + var player = GetTree().CurrentScene.FindChild("Player"); + + return new SaveData + { + PlayerPosition = player?.Position ?? Vector2.Zero, + PlayerHealth = Context.GetModel().Health.Value, + CurrentLevel = Context.GetModel().CurrentLevel.Value, + Inventory = Context.GetModel().GetData(), + Timestamp = DateTime.Now, + Version = 1 + }; + } +} +``` + +### 自动保存系统 + +```csharp +public class AutoSaveSystem : AbstractSystem +{ + private Timer _autoSaveTimer; + private int _currentSaveSlot; + private float _autoSaveInterval; + + public AutoSaveSystem(float intervalMinutes = 5.0f) + { + _autoSaveInterval = intervalMinutes * 60.0f; + } + + protected override void OnInit() + { + _autoSaveTimer = new Timer(); + _autoSaveTimer.WaitTime = _autoSaveInterval; + _autoSaveTimer.Timeout += OnAutoSave; + _autoSaveTimer.Autostart = true; + + AddChild(_autoSaveTimer); + + // 监听重要事件,立即自动保存 + this.RegisterEvent(OnImportantEvent); + this.RegisterEvent(OnImportantEvent); + this.RegisterEvent(OnImportantEvent); + + Logger.Info("Auto-save system initialized"); + } + + protected override void OnDestroy() + { + _autoSaveTimer?.Stop(); + + // 最后一次自动保存 + PerformAutoSave(); + } + + private void OnAutoSave() + { + PerformAutoSave(); + Logger.Debug("Periodic auto-save completed"); + } + + private void OnImportantEvent(IEvent e) + { + Logger.Info($"Important event {e.GetType().Name}, triggering auto-save"); + PerformAutoSave(); + } + + private void PerformAutoSave() + { + try + { + var saveData = CreateAutoSaveData(); + + // 保存到自动存档槽 + var storage = Context.GetUtility(); + storage.Write("autosave", saveData); + storage.Write("autosave/timestamp", DateTime.Now); + + Logger.Debug("Auto-save completed successfully"); + } + catch (Exception ex) + { + Logger.Error($"Auto-save failed: {ex.Message}"); + } + } + + private SaveData CreateAutoSaveData() + { + return new SaveData + { + // ... 创建保存数据 + }; + } +} +``` + +## 最佳实践 + +### 🏗️ 数据模型设计 + +#### 1. 版本化数据结构 + +```csharp +// 好的做法:版本化数据模型 +[Serializable] +public class PlayerDataV2 +{ + public int Version { get; set; } = 2; + public string Name { get; set; } + public int Health { get; set; } + public int MaxHealth { get; set; } = 100; // V2 新增 + public List Skills { get; set; } = new(); // V2 新增 +} + +// 避免:没有版本控制 +[Serializable] +public class PlayerData +{ + public string Name { get; set; } + public int Health { get; set; } + // 未来新增字段会导致兼容性问题 +} +``` + +#### 2. 数据验证 + +```csharp +public class PlayerData +{ + private string _name; + + public string Name + { + get => _name; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Player name cannot be empty"); + _name = value; + } + } + + public int Health + { + get => _health; + set => _health = Math.Max(0, value); // 确保不为负数 + } + + public void Validate() + { + if (string.IsNullOrWhiteSpace(Name)) + throw new ValidationException("Player name is required"); + + if (Health < 0) + throw new ValidationException("Health cannot be negative"); + } +} +``` + +### 💾 存储策略 + +#### 1. 分层存储命名 + +```csharp +// 好的做法:有意义的分层结构 +var playerStorage = new ScopedStorage(rootStorage, "players"); +var saveStorage = new ScopedStorage(rootStorage, "saves"); +var settingsStorage = new ScopedStorage(rootStorage, "settings"); +var tempStorage = new ScopedStorage(rootStorage, "temp"); + +// 避免的混乱命名 +var storage1 = new ScopedStorage(rootStorage, "data1"); +var storage2 = new ScopedStorage(rootStorage, "data2"); +``` + +#### 2. 存储性能优化 + +```csharp +// 好的做法:批量操作和缓存 +public class OptimizedDataManager +{ + private readonly IStorage _storage; + private readonly Dictionary _writeBuffer = new(); + + public void QueueWrite(string key, T data) + { + _writeBuffer[key] = data; + } + + public async Task FlushWritesAsync() + { + var tasks = _writeBuffer.Select(kvp => _storage.WriteAsync(kvp.Key, kvp.Value)); + await Task.WhenAll(tasks); + _writeBuffer.Clear(); + } +} + +// 避免:频繁的小写入 +public class InefficientDataManager +{ + public void UpdatePlayerStat(string stat, int value) + { + _storage.Write($"player/stats/{stat}", value); // 每次都写入磁盘 + } +} +``` + +### 🔄 序列化优化 + +#### 1. 选择合适的序列化格式 + +```csharp +// 好的做法:根据需求选择格式 +public class GameSerializer +{ + // JSON:可读性好,调试方便 + public string SerializeForDebug(object data) => JsonConvert.SerializeObject(data, Formatting.Indented); + + // 二进制:体积小,性能好 + public byte[] SerializeForStorage(object data) => MessagePackSerializer.Serialize(data); + + // 压缩:减少存储空间 + public byte[] SerializeWithCompression(object data) + { + var json = JsonConvert.SerializeObject(data); + return Compress(Encoding.UTF8.GetBytes(json)); + } +} +``` + +#### 2. 自定义序列化逻辑 + +```csharp +public class PlayerInventory +{ + public Dictionary Items { get; set; } = new(); + + [JsonIgnore] // 排除不需要序列化的属性 + public int TotalWeight => Items.Sum(kvp => GetItemWeight(kvp.Key) * kvp.Value); + + [JsonProperty("total_items")] // 自定义序列化名称 + public int TotalItems => Items.Values.Sum(); + + public bool ShouldSerializeItems() // 条件序列化 + { + return Items.Count > 0; + } + + [OnDeserialized] // 反序列化后处理 + private void OnDeserialized(StreamingContext context) + { + // 初始化默认值或执行验证 + Items ??= new Dictionary(); + } +} +``` + +### 🏭 模块设计模式 + +#### 1. 单一职责模块 + +```csharp +// 好的做法:模块职责单一 +public class AudioModule : AbstractModule +{ + // 只负责音频相关功能 +} + +public class SaveModule : AbstractModule +{ + // 只负责存档相关功能 +} + +// 避免:功能过于庞大 +public class GameModule : AbstractModule +{ + // 音频、存档、UI、输入全部混在一起 +} +``` + +#### 2. 模块间通信 + +```csharp +public class SaveModule : AbstractModule +{ + public override void Install(IArchitecture architecture) + { + architecture.RegisterUtility(new SaveUtility()); + } +} + +public class PlayerModule : AbstractModule +{ + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new PlayerSystem()); + } + + public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + if (phase == ArchitecturePhase.Ready) + { + // 通过事件进行模块间通信 + this.RegisterEvent(OnPlayerDeath); + } + } + + private void OnPlayerDeath(PlayerDeathEvent e) + { + // 触发保存模块的事件 + Context.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" }); + } +} +``` + +## 性能特性 + +### 📊 内存管理 + +- **缓存策略**:多层缓存减少磁盘 I/O +- **延迟加载**:按需加载资源,减少内存占用 +- **对象池化**:重用对象,减少 GC 压力 +- **内存映射**:大文件使用内存映射技术 + +### ⚡ 存储性能 + +```csharp +// 性能对比示例 +public class StoragePerformanceTest +{ + // 同步操作:简单直接 + public void SyncWrite(IStorage storage, string key, object data) + { + storage.Write(key, data); // ~1-5ms + } + + // 异步操作:非阻塞 + public async Task AsyncWrite(IStorage storage, string key, object data) + { + await storage.WriteAsync(key, data); // ~0.1-1ms (不阻塞主线程) + } + + // 批量操作:高吞吐量 + public async Task BatchWrite(IStorage storage, Dictionary data) + { + var tasks = data.Select(kvp => storage.WriteAsync(kvp.Key, kvp.Value)); + await Task.WhenAll(tasks); // ~10-50ms for 100 items + } +} +``` + +### 🔄 序列化优化 + +- **格式选择**:JSON(可读性)vs 二进制(性能) +- **压缩技术**:减少存储空间和网络传输 +- **增量序列化**:只序列化变化的部分 +- **版本控制**:向后兼容的数据迁移 + +--- + +## 依赖关系 + +```mermaid +graph TD + A[GFramework.Game] --> B[GFramework.Core] + A --> C[GFramework.Core.Abstractions] + A --> D[Newtonsoft.Json] + A --> E[System.Text.Json] +``` + +## 版本兼容性 + +- **.NET**: 6.0+ +- **Newtonsoft.Json**: 13.0.3+ +- **GFramework.Core**: 与 Core 模块版本保持同步 + +## 许可证 + +本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 + +--- + +**版本**: 1.0.0 +**更新日期**: 2026-01-12 \ No newline at end of file diff --git a/docs/godot/overview.md b/docs/godot/overview.md index 3f56edb..d776d5f 100644 --- a/docs/godot/overview.md +++ b/docs/godot/overview.md @@ -1,75 +1,607 @@ -# Godot 集成概览 +# GFramework.Godot -GFramework.Godot 提供与 Godot 引擎的深度集成,让开发者能够在保持框架架构优势的同时,充分利用 Godot 的强大功能。 +> Godot 引擎深度集成 - 为 GFramework 框架提供原生的 Godot 支持 + +GFramework.Godot 是 GFramework 框架的 Godot 特定实现,将框架的架构优势与 Godot 引擎的强大功能完美结合。 + +## 📋 目录 + +- [概述](#概述) +- [核心特性](#核心特性) +- [架构集成](#架构集成) +- [Node 扩展方法](#node-扩展方法) +- [信号系统](#信号系统) +- [节点池化](#节点池化) +- [资源管理](#资源管理) +- [日志系统](#日志系统) +- [完整示例](#完整示例) +- [最佳实践](#最佳实践) +- [性能特性](#性能特性) + +## 概述 + +GFramework.Godot 提供了与 Godot 引擎的深度集成,让开发者能够在保持 GFramework 架构优势的同时,充分利用 Godot +的节点系统、信号机制和场景管理功能。 + +### 核心设计理念 + +- **无缝集成**:框架生命周期与 Godot 节点生命周期自动同步 +- **类型安全**:保持 GFramework 的强类型特性 +- **性能优化**:零额外开销的 Godot 集成 +- **开发效率**:丰富的扩展方法简化常见操作 ## 核心特性 -### 🎮 Godot 深度集成 +### 🎯 架构生命周期绑定 -- **架构生命周期绑定** - 自动同步框架和 Godot 节点生命周期 -- **丰富的 Node 扩展** - 50+ 个实用的节点扩展方法 -- **类型安全信号** - 强类型的信号连接和处理 -- **高效对象池** - 专门的 Node 对象池系统 +- 自动将框架初始化与 Godot 场景树绑定 +- 支持节点销毁时的自动清理 +- 阶段式架构初始化与 Godot `_Ready` 周期同步 -### 🔧 开发者友好 +### 🔧 丰富的 Node 扩展方法 -- **零配置集成** - 简单的安装和配置过程 -- **流畅的 API** - 链式调用和现代 C# 语法 -- **自动清理** - 智能的资源和事件监听器管理 -- **调试支持** - 完善的日志和调试工具 +- **50+** 个实用扩展方法 +- 安全的节点操作和验证 +- 流畅的场景树遍历和查找 +- 简化的输入处理 -## 核心组件 +### 📡 流畅的信号 API -### 架构集成 +- 类型安全的信号连接 +- 链式调用支持 +- 自动生命周期管理 +- Godot 信号与框架事件系统的桥接 -- **AbstractArchitecture** - Godot 架构基类 -- **AbstractGodotModule** - Godot 模块基类 -- **NodeController** - 节点控制器基类 +### 🏊‍♂️ 高效的节点池化 -### 节点扩展 +- 专用的 Node 对象池 +- 自动回收和重用机制 +- 内存友好的高频节点创建/销毁 -- **[NodeExtensions](/godot/node-extensions/node-extensions)** - 节点扩展方法 -- **SignalBuilder** - 信号构建器 -- **AsyncExtensions** - 异步扩展 +### 📦 智能资源管理 -### 对象池系统 +- 简化的 Godot 资源加载 +- 类型安全的资源工厂 +- 缓存和预加载支持 -- **[AbstractNodePoolSystem](/godot/pooling/node-pool)** - 节点池基类 -- **NodePoolManager** - 节点池管理器 -- **PoolableObject** - 可池化对象接口 +### 📝 Godot 原生日志 -### 日志系统 +- 与 Godot 日志系统完全集成 +- 框架日志自动输出到 Godot 控制台 +- 可配置的日志级别 -- **[GodotLogger](/godot/logging/godot-logger)** - Godot 集成日志 -- **LoggerFactory** - 日志工厂 -- **Log Attributes** - 日志特性 +## 架构集成 -## 使用场景 - -Godot 模块适用于所有使用 Godot 引擎的项目: - -- **2D 游戏** - 平台游戏、RPG、策略游戏 -- **3D 游戏** - 动作游戏、射击游戏、模拟游戏 -- **工具应用** - 编辑器、可视化工具 -- **原型开发** - 快速游戏原型制作 - -## 安装要求 - -```bash -# 需要安装 Core 和 Game 模块 -dotnet add package GeWuYou.GFramework.Core -dotnet add package GeWuYou.GFramework.Game -dotnet add package GeWuYou.GFramework.Godot - -# 需要 Godot 4.5+ 版本 -``` - -## 快速示例 +### Architecture 基类 ```csharp using GFramework.Godot.architecture; + +public class GameArchitecture : AbstractArchitecture +{ + protected override void Init() + { + // 注册核心模型 + RegisterModel(new PlayerModel()); + RegisterModel(new GameModel()); + + // 注册系统 + RegisterSystem(new CombatSystem()); + RegisterSystem(new AudioSystem()); + + // 注册工具类 + RegisterUtility(new StorageUtility()); + RegisterUtility(new ResourceLoadUtility()); + } + + protected override void InstallModules() + { + // 安装 Godot 特定模块 + InstallGodotModule(new InputModule()); + InstallGodotModule(new AudioModule()); + } +} +``` + +### Godot 模块系统 + +```csharp +using GFramework.Godot.architecture; + +[ContextAware] +[Log] +public partial class AudioModule : AbstractGodotModule +{ + // 模块节点本身可以作为 Godot 节点 + public override Node Node => this; + + public override void Install(IArchitecture architecture) + { + // 注册音频相关系统 + architecture.RegisterSystem(new AudioSystem()); + architecture.RegisterUtility(new AudioUtility()); + } + + public override void OnAttach(Architecture architecture) + { + // 模块附加时的初始化 + Logger.Info("Audio module attached to architecture"); + } + + public override void OnDetach(Architecture architecture) + { + // 模块分离时的清理 + Logger.Info("Audio module detached from architecture"); + } + + // 响应架构生命周期阶段 + public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + switch (phase) + { + case ArchitecturePhase.Ready: + // 架构准备就绪,可以开始播放背景音乐 + PlayBackgroundMusic(); + break; + } + } +} +``` + +### Controller 集成 + +```csharp using GFramework.Godot.extensions; +[ContextAware] +[Log] +public partial class PlayerController : Node, IController +{ + private PlayerModel _playerModel; + + public override void _Ready() + { + // 获取模型引用 + _playerModel = Context.GetModel(); + + // 注册事件监听,自动与节点生命周期绑定 + this.RegisterEvent(OnPlayerInput) + .UnRegisterWhenNodeExitTree(this); + + // 监听属性变化 + _playerModel.Health.Register(OnHealthChanged) + .UnRegisterWhenNodeExitTree(this); + } + + private void OnPlayerInput(PlayerInputEvent e) + { + // 处理玩家输入 + switch (e.Action) + { + case "move_left": + MovePlayer(-1, 0); + break; + case "move_right": + MovePlayer(1, 0); + break; + case "attack": + Context.SendCommand(new AttackCommand()); + break; + } + } + + private void OnHealthChanged(int newHealth) + { + // 更新 UI + var healthBar = GetNode("UI/HealthBar"); + healthBar.Value = newHealth; + + // 播放音效 + if (newHealth < _playerModel.PreviousHealth) + PlayHurtSound(); + } +} +``` + +## Node 扩展方法 + +GFramework.Godot 提供了 50+ 个 Node 扩展方法,大大简化了 Godot 开发中的常见操作。 + +### 🔍 节点查找与验证 + +```csharp +// 安全的节点获取 +var player = GetNodeX("Player"); // 自动 null 检查和类型转换 +var child = FindChildX("Player"); // 递归查找子节点 + +// 节点验证 +if (IsValidNode(player)) +{ + // 节点有效且在场景树中 +} + +// 安全的节点遍历 +this.ForEachChild(child => { + GD.Print($"Found child: {child.Name}"); +}); +``` + +### 🌊 流畅的场景树操作 + +```csharp +// 安全的添加子节点 +var bullet = bulletScene.Instantiate(); +AddChildX(bullet); + +// 等待节点准备就绪 +await bullet.WaitUntilReady(); + +// 获取父节点 +var parent = GetParentX(); + +// 安全的节点移除 +bullet.QueueFreeX(); // 等效于 QueueFree() 但带有验证 +bullet.FreeX(); // 立即释放(谨慎使用) +``` + +### 🎮 输入处理简化 + +```csharp +// 输入处理 +SetInputAsHandled(); // 标记输入已处理 +DisableInput(); // 禁用输入 +EnableInput(); // 启用输入 + +// 输入状态检查 +if (Input.IsActionJustPressed("jump")) +{ + Jump(); +} +``` + +### 🔄 异步操作支持 + +```csharp +// 等待信号 +await ToSignal(this, SignalName.Ready); + +// 等待条件满足 +await WaitUntil(() => IsReady); + +// 等待帧结束 +await WaitUntilProcessFrame(); + +// 延迟执行 +await WaitUntilTimeout(2.0f); +``` + +## 信号系统 + +### SignalBuilder 流畅 API + +```csharp +using GFramework.Godot.extensions; + +// 基础信号连接 +this.ConnectSignal(Button.SignalName.Pressed, OnButtonPressed); + +// 流畅的信号构建 +this.CreateSignalBuilder(Timer.SignalName.Timeout) + .WithFlags(ConnectFlags.OneShot) // 单次触发 + .CallImmediately() // 立即调用一次 + .Connect(OnTimerTimeout) + .UnRegisterWhenNodeExitTree(this); + +// 多信号连接 +this.CreateSignalBuilder() + .AddSignal(Button.SignalName.Pressed, OnButtonPressed) + .AddSignal(Button.SignalName.MouseEntered, OnButtonHover) + .AddSignal(Button.SignalName.MouseExited, OnButtonExit) + .UnRegisterWhenNodeExitTree(this); +``` + +### 信号与框架事件桥接 + +```csharp +[ContextAware] +[Log] +public partial class UIController : Node, IController +{ + public override void _Ready() + { + // Godot 信号 -> 框架事件 + this.CreateSignalBuilder(Button.SignalName.Pressed) + .Connect(() => { + Context.SendEvent(new UIButtonClickEvent { ButtonId = "start_game" }); + }) + .UnRegisterWhenNodeExitTree(this); + + // 框架事件 -> Godot 信号 + this.RegisterEvent(OnHealthChanged) + .UnRegisterWhenNodeExitTree(this); + } + + private void OnHealthChanged(HealthChangeEvent e) + { + // 更新 Godot UI + var healthBar = GetNode("HealthBar"); + healthBar.Value = e.NewHealth; + + // 发送 Godot 信号 + EmitSignal(SignalName.HealthUpdated, e.NewHealth); + } + + [Signal] + public delegate void HealthUpdatedEventHandler(int newHealth); +} +``` + +## 节点池化 + +### AbstractNodePoolSystem 使用 + +```csharp +using GFramework.Godot.pool; + +public class BulletPoolSystem : AbstractNodePoolSystem +{ + private PackedScene _bulletScene; + + public BulletPoolSystem() + { + _bulletScene = GD.Load("res://scenes/Bullet.tscn"); + } + + protected override Bullet CreateItem(string key) + { + return _bulletScene.Instantiate(); + } + + protected override void OnSpawn(Bullet item, string key) + { + // 重置子弹状态 + item.Reset(); + item.Position = Vector3.Zero; + item.Visible = true; + } + + protected override void OnDespawn(Bullet item) + { + // 隐藏子弹 + item.Visible = false; + // 移除父节点 + item.GetParent()?.RemoveChild(item); + } + + protected override bool CanDespawn(Bullet item) + { + // 只有不在使用中的子弹才能回收 + return !item.IsActive; + } +} +``` + +### 池化系统使用 + +```csharp +[ContextAware] +[Log] +public partial class WeaponController : Node, IController +{ + private BulletPoolSystem _bulletPool; + + protected override void OnInit() + { + _bulletPool = Context.GetSystem(); + } + + public void Shoot(Vector3 direction) + { + // 从池中获取子弹 + var bullet = _bulletPool.Spawn("standard"); + + if (bullet != null) + { + // 设置子弹参数 + bullet.Direction = direction; + bullet.Speed = 10.0f; + + // 添加到场景 + GetTree().Root.AddChild(bullet); + + // 注册碰撞检测 + this.RegisterEvent(e => { + if (e.Bullet == bullet) + { + // 回收子弹 + _bulletPool.Despawn(bullet); + } + }).UnRegisterWhenNodeExitTree(this); + } + } +} +``` + +## 资源管理 + +### ResourceLoadUtility 使用 + +```csharp +using GFramework.Godot.assets; + +[ContextAware] +[Log] +public partial class ResourceManager : Node, IController +{ + private ResourceLoadUtility _resourceLoader; + + protected override void OnInit() + { + _resourceLoader = new ResourceLoadUtility(); + } + + public T LoadResource(string path) where T : Resource + { + return _resourceLoader.LoadResource(path); + } + + public async Task LoadResourceAsync(string path) where T : Resource + { + return await _resourceLoader.LoadResourceAsync(path); + } + + public void PreloadResources() + { + // 预加载常用资源 + _resourceLoader.PreloadResource("res://textures/player.png"); + _resourceLoader.PreloadResource("res://audio/shoot.wav"); + _resourceLoader.PreloadResource("res://scenes/enemy.tscn"); + } +} +``` + +### 自定义资源工厂 + +```csharp +public class GameResourceFactory : AbstractResourceFactoryUtility +{ + protected override void RegisterFactories() + { + RegisterFactory(CreatePlayerData); + RegisterFactory(CreateWeaponConfig); + RegisterFactory(CreateLevelData); + } + + private PlayerData CreatePlayerData(string path) + { + var config = LoadJson(path); + return new PlayerData + { + MaxHealth = config.MaxHealth, + Speed = config.Speed, + JumpForce = config.JumpForce + }; + } + + private WeaponConfig CreateWeaponConfig(string path) + { + var data = LoadJson(path); + return new WeaponConfig + { + Damage = data.Damage, + FireRate = data.FireRate, + BulletPrefab = LoadResource(data.BulletPath) + }; + } +} +``` + +## 日志系统 + +### GodotLogger 使用 + +```csharp +using GFramework.Godot.logging; + +[ContextAware] +[Log] // 自动生成 Logger 字段 +public partial class GameController : Node, IController +{ + public override void _Ready() + { + // 使用自动生成的 Logger + Logger.Info("Game controller ready"); + + try + { + InitializeGame(); + Logger.Info("Game initialized successfully"); + } + catch (Exception ex) + { + Logger.Error($"Failed to initialize game: {ex.Message}"); + } + } + + public void StartGame() + { + Logger.Debug("Starting game"); + + // 发送游戏开始事件 + Context.SendEvent(new GameStartEvent()); + + Logger.Info("Game started"); + } + + public void PauseGame() + { + Logger.Info("Game paused"); + Context.SendEvent(new GamePauseEvent()); + } +} +``` + +### 日志配置 + +```csharp +public class GodotLoggerFactoryProvider : ILoggerFactoryProvider +{ + public ILoggerFactory CreateFactory() + { + return new GodotLoggerFactory(new LoggerProperties + { + MinLevel = LogLevel.Debug, + IncludeTimestamp = true, + IncludeCallerInfo = true + }); + } +} + +// 在架构初始化时配置日志 +public class GameArchitecture : AbstractArchitecture +{ + protected override void Init() + { + // 配置 Godot 日志工厂 + LoggerProperties = new LoggerProperties + { + LoggerFactoryProvider = new GodotLoggerFactoryProvider(), + MinLevel = LogLevel.Info + }; + + // 注册组件... + } +} +``` + +## 完整示例 + +### 简单射击游戏示例 + +```csharp +// 1. 定义架构 +public class ShooterGameArchitecture : AbstractArchitecture +{ + protected override void Init() + { + // 注册模型 + RegisterModel(new PlayerModel()); + RegisterModel(new GameModel()); + RegisterModel(new ScoreModel()); + + // 注册系统 + RegisterSystem(new PlayerControllerSystem()); + RegisterSystem(new BulletPoolSystem()); + RegisterSystem(new EnemySpawnSystem()); + RegisterSystem(new CollisionSystem()); + + // 注册工具 + RegisterUtility(new StorageUtility()); + RegisterUtility(new ResourceLoadUtility()); + } +} + +// 2. 玩家控制器 [ContextAware] [Log] public partial class PlayerController : CharacterBody2D, IController @@ -80,97 +612,297 @@ public partial class PlayerController : CharacterBody2D, IController { _playerModel = Context.GetModel(); - // 安全的节点操作 - var healthBar = GetNodeX("UI/HealthBar"); + // 输入处理 + SetProcessInput(true); - // 类型安全的信号连接 - this.CreateSignalBuilder(Button.SignalName.Pressed) - .UnRegisterWhenNodeExitTree(this) - .Connect(OnButtonPressed); - - // 响应式数据绑定 - _playerModel.Health.Register(OnHealthChanged) + // 注册事件 + this.RegisterEvent(OnDamage) .UnRegisterWhenNodeExitTree(this); } - private void OnButtonPressed() + public override void _Process(double delta) { - Context.SendCommand(new AttackCommand()); + var inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); + Velocity = inputDir * _playerModel.Speed.Value; + MoveAndSlide(); } - private void OnHealthChanged(int newHealth) + public override void _Input(InputEvent @event) { - // 自动更新 UI - var healthBar = GetNode("UI/HealthBar"); - healthBar.Value = newHealth; + if (@event.IsActionPressed("shoot")) + { + Shoot(); + } + } + + private void Shoot() + { + if (CanShoot()) + { + var bulletPool = Context.GetSystem(); + var bullet = bulletPool.Spawn("player"); + + if (bullet != null) + { + var direction = GetGlobalMousePosition() - GlobalPosition; + bullet.Initialize(GlobalPosition, direction.Normalized()); + GetTree().Root.AddChild(bullet); + + Context.SendEvent(new BulletFiredEvent()); + } + } + } + + private void OnDamage(PlayerDamageEvent e) + { + _playerModel.Health.Value -= e.Damage; + + if (_playerModel.Health.Value <= 0) + { + Die(); + } + } + + private void Die() + { + Logger.Info("Player died"); + Context.SendEvent(new PlayerDeathEvent()); + QueueFreeX(); + } +} + +// 3. 主场景 +[ContextAware] +[Log] +public partial class MainScene : Node2D +{ + private ShooterGameArchitecture _architecture; + + public override void _Ready() + { + // 初始化架构 + _architecture = new ShooterGameArchitecture(); + _architecture.Initialize(); + + // 创建玩家 + var playerScene = GD.Load("res://scenes/Player.tscn"); + var player = playerScene.Instantiate(); + AddChild(player); + + // 注册全局事件 + this.RegisterEvent(OnPlayerDeath) + .UnRegisterWhenNodeExitTree(this); + + this.RegisterEvent(OnGameWin) + .UnRegisterWhenNodeExitTree(this); + + Logger.Info("Game started"); + } + + private void OnPlayerDeath(PlayerDeathEvent e) + { + Logger.Info("Game over"); + ShowGameOverScreen(); + } + + private void OnGameWin(GameWinEvent e) + { + Logger.Info("Victory!"); + ShowVictoryScreen(); + } + + private void ShowGameOverScreen() + { + var gameOverScene = GD.Load("res://ui/GameOver.tscn"); + var gameOverUI = gameOverScene.Instantiate(); + AddChild(gameOverUI); + } + + private void ShowVictoryScreen() + { + var victoryScene = GD.Load("res://ui/Victory.tscn"); + var victoryUI = victoryScene.Instantiate(); + AddChild(victoryUI); } } ``` -## 学习路径 +## 最佳实践 -建议按以下顺序学习 Godot 集成: +### 🏗️ 架构设计最佳实践 -1. **[架构集成](/godot/integration/architecture-integration)** - 了解集成基础 -2. **[节点扩展](/godot/node-extensions/node-extensions)** - 掌握扩展方法 -3. **[对象池](/godot/pooling/node-pool)** - 学习性能优化 -4. **[日志系统](/godot/logging/godot-logger)** - 掌握调试工具 - -## 与其它模块的关系 - -``` -GFramework.Core (基础架构) - ↓ -GFramework.Game (游戏功能) - ↓ -GFramework.Godot (Godot 集成) -``` - -Godot 模块建立在 Core 和 Game 模块之上,提供与 Godot 引擎的无缝集成。 - -## 性能特点 - -- **零额外开销** - 扩展方法编译时优化 -- **内存安全** - 自动资源管理和清理 -- **类型安全** - 编译时类型检查 -- **异步支持** - 完善的异步操作支持 - -## 常见用法 - -### 节点安全操作 +#### 1. 模块化设计 ```csharp -// 安全获取节点 +// 好的做法:按功能分组模块 +public class AudioModule : AbstractGodotModule { } +public class InputModule : AbstractGodotModule { } +public class UIModule : AbstractGodotModule { } + +// 避免的功能过于庞大的单一模块 +public class GameModule : AbstractGodotModule // ❌ 太大 +{ + // 音频、输入、UI、逻辑全部混在一起 +} +``` + +#### 2. 生命周期管理 + +```csharp +// 好的做法:使用自动清理 +this.RegisterEvent(OnGameEvent) + .UnRegisterWhenNodeExitTree(this); + +model.Property.Register(OnPropertyChange) + .UnRegisterWhenNodeExitTree(this); + +// 避免手动管理清理 +private IUnRegister _eventRegister; +public override void _Ready() +{ + _eventRegister = this.RegisterEvent(OnGameEvent); +} + +public override void _ExitTree() +{ + _eventRegister?.UnRegister(); // 容易忘记 +} +``` + +### 🎮 Godot 集成最佳实践 + +#### 1. 节点安全操作 + +```csharp +// 好的做法:使用安全扩展 var player = GetNodeX("Player"); var child = FindChildX("HealthBar"); -// 安全添加子节点 -AddChildX(bullet); +// 避免的直接节点访问 +var player = GetNode("Player"); // 可能抛出异常 ``` -### 信号处理 +#### 2. 信号连接模式 ```csharp -// 流畅的信号连接 -this.CreateSignalBuilder(Timer.SignalName.Timeout) - .WithFlags(ConnectFlags.OneShot) - .Connect(OnTimeout) - .UnRegisterWhenNodeExitTree(this); +// 好的做法:使用 SignalBuilder +this.CreateSignalBuilder(Button.SignalName.Pressed) + .UnRegisterWhenNodeExitTree(this) + .Connect(OnButtonPressed); + +// 避免的原始方式 +Button.Pressed += OnButtonPressed; // 容易忘记清理 ``` -### 异步操作 +### 🏊‍♂️ 性能优化最佳实践 + +#### 1. 节点池化策略 ```csharp -// 等待信号 -await ToSignal(this, SignalName.Ready); +// 好的做法:高频创建对象使用池化 +public class BulletPool : AbstractNodePoolSystem +{ + // 为不同类型的子弹创建不同的池 +} -// 等待条件 -await WaitUntil(() => IsReady); +// 避免的频繁创建销毁 +public void Shoot() +{ + var bullet = bulletScene.Instantiate(); // ❌ 性能问题 + // ... + bullet.QueueFree(); +} ``` -## 下一步 +#### 2. 资源预加载 -- [深入了解架构集成](/godot/integration/architecture-integration) -- [学习节点扩展方法](/godot/node-extensions/node-extensions) -- [掌握对象池系统](/godot/pooling/node-pool) -- [查看完整 API 参考](/api-reference/godot-api) \ No newline at end of file +```csharp +// 好的做法:预加载常用资源 +public override void _Ready() +{ + var resourceLoader = new ResourceLoadUtility(); + resourceLoader.PreloadResource("res://textures/bullet.png"); + resourceLoader.PreloadResource("res://audio/shoot.wav"); +} +``` + +### 🔧 调试和错误处理 + +#### 1. 日志使用策略 + +```csharp +// 好的做法:分级别记录 +Logger.Debug($"Player position: {Position}"); // 调试信息 +Logger.Info("Game started"); // 重要状态 +Logger.Warning($"Low health: {_playerModel.Health}"); // 警告 +Logger.Error($"Failed to load resource: {path}"); // 错误 + +// 避免的过度日志 +Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁 +``` + +#### 2. 异常处理 + +```csharp +// 好的做法:优雅的错误处理 +public T LoadResource(string path) where T : Resource +{ + try + { + return GD.Load(path); + } + catch (Exception ex) + { + Logger.Error($"Failed to load resource {path}: {ex.Message}"); + return GetDefaultResource(); + } +} +``` + +## 性能特性 + +### 📊 内存管理 + +- **节点池化**:减少 GC 压力,提高频繁创建/销毁对象的性能 +- **资源缓存**:自动缓存已加载的 Godot 资源 +- **生命周期管理**:自动清理事件监听器和资源引用 + +### ⚡ 运行时性能 + +- **零分配**:扩展方法避免不必要的对象分配 +- **编译时优化**:Source Generators 减少运行时开销 +- **类型安全**:编译时类型检查,避免运行时错误 + +### 🔄 异步支持 + +- **信号等待**:使用 `await ToSignal()` 简化异步代码 +- **条件等待**:`WaitUntil()` 和 `WaitUntilTimeout()` 简化异步逻辑 +- **场景树等待**:`WaitUntilReady()` 确保节点准备就绪 + +--- + +## 依赖关系 + +```mermaid +graph TD + A[GFramework.Godot] --> B[GFramework.Game] + A --> C[GFramework.Game.Abstractions] + A --> D[GFramework.Core.Abstractions] + A --> E[Godot.SourceGenerators] + A --> F[GodotSharpEditor] +``` + +## 版本兼容性 + +- **Godot**: 4.5.1+ +- **.NET**: 6.0+ +- **GFramework.Core**: 与 Core 模块版本保持同步 + +## 许可证 + +本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 + +--- + +**版本**: 1.0.0 +**更新日期**: 2026-01-12 \ No newline at end of file diff --git a/docs/source-generators/overview.md b/docs/source-generators/overview.md index fcd5e15..a1180a4 100644 --- a/docs/source-generators/overview.md +++ b/docs/source-generators/overview.md @@ -1,234 +1,997 @@ -# 源码生成器概览 +# GFramework.SourceGenerators -GFramework 源码生成器基于 Roslyn 编译器平台,自动生成常用的样板代码,提高开发效率并减少错误。 +> 编译时代码生成 - 零运行时开销的代码增强工具 + +GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通过编译时分析自动生成样板代码,显著提升开发效率并减少运行时开销。 + +## 📋 目录 + +- [概述](#概述) +- [核心特性](#核心特性) +- [安装配置](#安装配置) +- [Log 属性生成器](#log-属性生成器) +- [ContextAware 属性生成器](#contextaware-属性生成器) +- [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器) +- [诊断信息](#诊断信息) +- [性能优势](#性能优势) +- [使用示例](#使用示例) +- [最佳实践](#最佳实践) +- [常见问题](#常见问题) + +## 概述 + +GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。 + +### 核心设计理念 + +- **零运行时开销**:代码在编译时生成,无反射或动态调用 +- **类型安全**:编译时类型检查,避免运行时错误 +- **开发效率**:自动生成样板代码,减少重复工作 +- **可配置性**:支持多种配置选项满足不同需求 ## 核心特性 -### ⚡ 自动代码生成 +### 🎯 主要生成器 -- **日志代码生成** - 自动生成日志字段和方法调用 -- **枚举扩展生成** - 为枚举类型生成实用扩展方法 -- **规则代码生成** - 根据规则定义生成验证代码 -- **性能优化** - 编译时生成,运行时零开销 +- **[Log] 属性**:自动生成 ILogger 字段和日志方法 +- **[ContextAware] 属性**:自动实现 IContextAware 接口 +- **[GenerateEnumExtensions] 属性**:自动生成枚举扩展方法 -### 🎯 智能识别 +### 🔧 高级特性 -- **特性驱动** - 通过特性标记触发生成 -- **上下文感知** - 理解代码语义和结构 -- **类型安全** - 生成的代码完全类型安全 -- **错误预防** - 编译时检查,避免运行时错误 - -## 核心生成器 - -### 日志生成器 - -- **[LogAttribute](/source-generators/logging-generator)** - 自动生成日志字段 -- **ContextAwareAttribute** - 生成上下文感知代码 -- **性能监控** - 自动生成方法执行时间记录 - -### 枚举扩展生成器 - -- **[EnumExtensions](/source-generators/enum-extensions)** - 生成枚举实用方法 -- **字符串转换** - 枚举与字符串的双向转换 -- **描述获取** - 从特性中提取枚举描述 - -### 规则生成器 - -- **[RuleGenerator](/source-generators/rule-generator)** - 生成验证规则代码 -- **业务规则** - 基于规则定义生成验证逻辑 -- **数据验证** - 自动生成数据完整性检查 - -## 使用场景 - -源码生成器适用于需要大量样板代码的场景: - -- **日志记录** - 统一的日志格式和级别管理 -- **枚举处理** - 频繁的枚举转换和操作 -- **数据验证** - 复杂的业务规则验证 -- **性能监控** - 方法执行时间跟踪 +- **智能诊断**:生成器包含详细的错误诊断信息 +- **增量编译**:只生成修改过的代码,提高编译速度 +- **命名空间控制**:灵活控制生成代码的命名空间 +- **可访问性控制**:支持不同的访问修饰符 ## 安装配置 +### NuGet 包安装 + ```xml - + + + net6.0 + + + + + + + ``` -## 快速示例 +### 项目文件配置 -### 日志生成器 +```xml + + + net6.0 + true + Generated + + + + + + +``` + +## Log 属性生成器 + +[Log] 属性自动为标记的类生成日志记录功能,包括 ILogger 字段和便捷的日志方法。 + +### 基础使用 ```csharp -[Log] // 自动生成 Logger 字段 -public partial class PlayerController : IController +using GFramework.SourceGenerators.Attributes; + +[Log] +public partial class PlayerController { - // 生成的代码: - // private ILogger _logger; - // public ILogger Logger => _logger ??= LoggerFactory.Create(GetType()); - - public void Attack() + public void DoSomething() { - Logger.Info("Player attacking"); // 直接使用生成的 Logger - // 业务逻辑... + Logger.Info("Doing something"); // 自动生成的 Logger 字段 + Logger.Debug("Debug information"); + Logger.Warning("Warning message"); + Logger.Error("Error occurred"); } } ``` -### 枚举扩展生成 +### 生成的代码 + +编译器会自动生成如下代码: ```csharp -[GenerateEnumExtensions] // 生成枚举扩展方法 +// +using Microsoft.Extensions.Logging; + +namespace YourNamespace +{ + public partial class PlayerController + { + private static readonly ILogger Logger = + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + } +} +``` + +### 高级配置 + +```csharp +[Log( + fieldName = "CustomLogger", // 自定义字段名 + accessModifier = AccessModifier.Public, // 访问修饰符 + isStatic = false, // 是否为静态字段 + loggerName = "Custom.PlayerLogger", // 自定义日志器名称 + includeLoggerInterface = true // 是否包含 ILogger 接口 +)] +public partial class CustomLoggerExample +{ + public void LogSomething() + { + CustomLogger.LogInformation("Custom logger message"); + } +} +``` + +### 配置选项说明 + +| 参数 | 类型 | 默认值 | 说明 | +|--------------------------|----------------|----------|---------------------| +| `fieldName` | string | "Logger" | 生成的日志字段名称 | +| `accessModifier` | AccessModifier | Private | 字段访问修饰符 | +| `isStatic` | bool | true | 是否生成静态字段 | +| `loggerName` | string | null | 自定义日志器名称,null 时使用类名 | +| `includeLoggerInterface` | bool | false | 是否包含 ILogger 接口实现 | + +### 静态类支持 + +```csharp +[Log] +public static partial class MathHelper +{ + public static int Add(int a, int b) + { + Logger.Debug($"Adding {a} and {b}"); + return a + b; + } +} +``` + +### 日志级别控制 + +```csharp +[Log(minLevel = LogLevel.Warning)] +public partial class WarningOnlyLogger +{ + public void ProcessData() + { + Logger.Debug("This won't be logged"); // 低于最小级别,被过滤 + Logger.Warning("This will be logged"); + Logger.Error("This will also be logged"); + } +} +``` + +## ContextAware 属性生成器 + +[ContextAware] 属性自动实现 IContextAware 接口,提供便捷的架构上下文访问能力。 + +### 基础使用 + +```csharp +using GFramework.SourceGenerators.Attributes; +using GFramework.Core.Abstractions; + +[ContextAware] +public partial class PlayerController : IController +{ + public void Initialize() + { + // Context 属性自动生成,提供架构上下文访问 + var playerModel = Context.GetModel(); + var combatSystem = Context.GetSystem(); + + Context.SendEvent(new PlayerInitializedEvent()); + } +} +``` + +### 生成的代码 + +编译器会自动生成如下代码: + +```csharp +// +using GFramework.Core.Abstractions; + +namespace YourNamespace +{ + public partial class PlayerController : IContextAware + { + private IContextAware.Context _context; + + public IContextAware.Context Context => _context ??= new LazyContext(this); + + public void SetContext(IContextAware.Context context) + { + _context = context; + } + + public IContextAware.Context GetContext() + { + return _context; + } + } +} +``` + +### 延迟初始化 + +```csharp +[ContextAware(useLazy = true)] +public partial class LazyContextExample +{ + public void AccessContext() + { + // Context 会延迟初始化,直到第一次访问 + var model = Context.GetModel(); + } +} +``` + +### 上下文验证 + +```csharp +[ContextAware(validateContext = true)] +public partial class ValidatedContextExample +{ + public void AccessContext() + { + // 每次访问都会验证上下文的有效性 + var model = Context.GetModel(); + if (Context.IsInvalid) + { + throw new InvalidOperationException("Context is invalid"); + } + } +} +``` + +### 与其他属性组合 + +```csharp +[Log] +[ContextAware] +public partial class AdvancedController : IController +{ + public void ProcessRequest() + { + Logger.Info("Processing request"); + + var model = Context.GetModel(); + Logger.Info($"Player health: {model.Health}"); + + Context.SendCommand(new ProcessCommand()); + Logger.Debug("Command sent"); + } +} +``` + +## GenerateEnumExtensions 属性生成器 + +[GenerateEnumExtensions] 属性为枚举类型生成便捷的扩展方法,提高代码可读性和类型安全性。 + +### 基础使用 + +```csharp +using GFramework.SourceGenerators.Attributes; + +[GenerateEnumExtensions] +public enum GameState +{ + Playing, + Paused, + GameOver, + Menu +} + +// 自动生成的扩展方法: +public static class GameStateExtensions +{ + public static bool IsPlaying(this GameState state) => state == GameState.Playing; + public static bool IsPaused(this GameState state) => state == GameState.Paused; + public static bool IsGameOver(this GameState state) => state == GameState.GameOver; + public static bool IsMenu(this GameState state) => state == GameState.Menu; + + public static bool IsIn(this GameState state, params GameState[] values) + { + return values.Contains(state); + } +} + +// 使用示例 +public class GameManager +{ + private GameState _currentState = GameState.Menu; + + public bool CanProcessInput() + { + return _currentState.IsPlaying() || _currentState.IsMenu(); + } + + public bool IsGameOver() + { + return _currentState.IsGameOver(); + } + + public bool IsActiveState() + { + return _currentState.IsIn(GameState.Playing, GameState.Menu); + } +} +``` + +### 自定义扩展方法 + +```csharp +[GenerateEnumExtensions( + generateIsMethods = true, + generateHasMethod = true, + generateInMethod = true, + customPrefix = "Is", + includeToString = true +)] public enum PlayerState { - [Description("空闲")] Idle, - - [Description("移动中")] - Moving, - - [Description("攻击中")] + Walking, + Running, + Jumping, Attacking } -// 生成的扩展方法: -// PlayerStateExtensions.GetDescription() -// PlayerStateExtensions.FromString() -// PlayerStateExtensions.GetAllValues() +// 生成更多扩展方法 +public static class PlayerStateExtensions +{ + public static bool IsIdle(this PlayerState state) => state == PlayerState.Idle; + public static bool IsWalking(this PlayerState state) => state == PlayerState.Walking; + public static bool IsRunning(this PlayerState state) => state == PlayerState.Running; + public static bool IsJumping(this PlayerState state) => state == PlayerState.Jumping; + public static bool IsAttacking(this PlayerState state) => state == PlayerState.Attacking; + + public static bool HasIdle(this PlayerState state) => state == PlayerState.Idle; + public static bool HasWalking(this PlayerState state) => state == PlayerState.Walking; + // ... 其他 Has 方法 + + public static bool In(this PlayerState state, params PlayerState[] values) + { + return values.Contains(state); + } + + public static string ToDisplayString(this PlayerState state) + { + return state switch + { + PlayerState.Idle => "Idle", + PlayerState.Walking => "Walking", + PlayerState.Running => "Running", + PlayerState.Jumping => "Jumping", + PlayerState.Attacking => "Attacking", + _ => state.ToString() + }; + } +} ``` -### 规则生成器 +### 位标志枚举支持 ```csharp -[GenerateValidationRules] // 生成验证规则 -public class PlayerData +[GenerateEnumExtensions] +[Flags] +public enum PlayerAbilities { - [Required] - [StringLength(50, MinimumLength = 3)] - public string Name { get; set; } - - [Range(0, 100)] - public int Health { get; set; } - - [Email] - public string Email { get; set; } + None = 0, + Jump = 1 << 0, + Run = 1 << 1, + Attack = 1 << 2, + Defend = 1 << 3, + Magic = 1 << 4 } -// 生成的验证方法: -// PlayerDataValidator.Validate() -// PlayerDataValidator.ValidateName() -// PlayerDataValidator.ValidateHealth() +// 生成位标志扩展方法 +public static class PlayerAbilitiesExtensions +{ + public static bool HasJump(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Jump); + public static bool HasRun(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Run); + // ... 其他位标志方法 + + public static bool HasAny(this PlayerAbilities abilities, params PlayerAbilities[] flags) + { + return flags.Any(flag => abilities.HasFlag(flag)); + } + + public static bool HasAll(this PlayerAbilities abilities, params PlayerAbilities[] flags) + { + return flags.All(flag => abilities.HasFlag(flag)); + } +} ``` -## 生成器配置 +### 配置选项说明 -### 全局配置 +| 参数 | 类型 | 默认值 | 说明 | +|---------------------|--------|-------|------------------------| +| `generateIsMethods` | bool | true | 是否生成 IsX() 方法 | +| `generateHasMethod` | bool | true | 是否生成 HasX() 方法 | +| `generateInMethod` | bool | true | 是否生成 In(params T[]) 方法 | +| `customPrefix` | string | "Is" | 方法名前缀 | +| `includeToString` | bool | false | 是否生成 ToString 扩展 | +| `namespace` | string | null | 生成扩展类的命名空间 | -```xml - - Debug - true - true - -``` +## 诊断信息 -### 特性配置 +GFramework.SourceGenerators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。 + +### GF_Logging_001 - 日志字段名冲突 ```csharp -[Log(LogLevel = LogLevel.Debug, IncludeCallerInfo = true)] -[GenerateEnumExtensions(CamelCase = true)] -[GenerateValidationRules(ThrowOnInvalid = true)] +[Log(fieldName = "Logger")] +public partial class ClassWithLogger +{ + private readonly ILogger Logger; // ❌ 冲突! +} +``` + +**错误信息**: `GF_Logging_001: Logger field name 'Logger' conflicts with existing field` + +**解决方案**: 更改字段名或移除冲突字段 + +```csharp +[Log(fieldName = "CustomLogger")] +public partial class ClassWithLogger +{ + private readonly ILogger Logger; // ✅ 不冲突 + private static readonly ILogger CustomLogger; // ✅ 生成器使用 CustomLogger +} +``` + +### GF_Rule_001 - ContextAware 接口冲突 + +```csharp +[ContextAware] +public partial class AlreadyContextAware : IContextAware // ❌ 冲突! +{ + // 已实现 IContextAware +} +``` + +**错误信息**: `GF_Rule_001: Type already implements IContextAware interface` + +**解决方案**: 移除 [ContextAware] 属性或移除手动实现 + +```csharp +// 方案1:移除属性 +public partial class AlreadyContextAware : IContextAware +{ + // 手动实现 +} + +// 方案2:移除手动实现,使用生成器 +[ContextAware] +public partial class AlreadyContextAware +{ + // 生成器自动实现 +} +``` + +### GF_Enum_001 - 枚举成员命名冲突 + +```csharp +[GenerateEnumExtensions] +public enum ConflictEnum +{ + IsPlaying, // ❌ 冲突!会生成 IsIsPlaying() + HasJump // ❌ 冲突!会生成 HasHasJump() +} +``` + +**错误信息**: `GF_Enum_001: Enum member name conflicts with generated method` + +**解决方案**: 重命名枚举成员或自定义前缀 + +```csharp +[GenerateEnumExtensions(customPrefix = "IsState")] +public enum ConflictEnum +{ + Playing, // ✅ 生成 IsStatePlaying() + Jump // ✅ 生成 IsStateJump() +} ``` ## 性能优势 -### 编译时生成 +### 编译时 vs 运行时对比 -- **零运行时开销** - 代码在编译时生成 -- **类型安全** - 编译时类型检查 -- **IDE 支持** - 生成的代码完全支持 IntelliSense +| 特性 | 手动实现 | 反射实现 | 源码生成器 | +|-----------|------|------|-------| +| **运行时性能** | 最优 | 最差 | 最优 | +| **内存开销** | 最小 | 最大 | 最小 | +| **类型安全** | 编译时 | 运行时 | 编译时 | +| **开发效率** | 低 | 中 | 高 | +| **调试友好** | 好 | 差 | 好 | -### 代码质量 - -- **一致性** - 统一的代码风格和模式 -- **错误减少** - 自动生成减少手写错误 -- **维护性** - 集中管理样板代码逻辑 - -## 学习路径 - -建议按以下顺序学习源码生成器: - -1. **[日志生成器](/source-generators/logging-generator)** - 基础的日志代码生成 -2. **[枚举扩展](/source-generators/enum-extensions)** - 枚举处理自动化 -3. **[规则生成器](/source-generators/rule-generator)** - 复杂验证逻辑生成 - -## 调试生成的代码 - -### 查看生成的代码 - -```xml - - true - Generated - -``` - -### 调试配置 +### 基准测试结果 ```csharp -// 在调试时查看生成的代码 -#if DEBUG -// 生成的代码会被写入到指定目录 -#endif +// 日志性能对比 (100,000 次调用) +// 手动实现: 0.23ms +// 反射实现: 45.67ms +// 源码生成器: 0.24ms (几乎无差异) + +// 上下文访问性能对比 (1,000,000 次访问) +// 手动实现: 0.12ms +// 反射实现: 23.45ms +// 源码生成器: 0.13ms (几乎无差异) +``` + +### 内存分配分析 + +```csharp +// 使用 source generators 的内存分配 +[Log] +[ContextAware] +public partial class EfficientController : IController +{ + public void Process() + { + Logger.Info("Processing"); // 0 分配 + var model = Context.GetModel(); // 0 分配 + } +} + +// 对比反射实现的内存分配 +public class InefficientController : IController +{ + public void Process() + { + var logger = GetLoggerViaReflection(); // 每次分配 + var model = GetModelViaReflection(); // 每次分配 + } +} +``` + +## 使用示例 + +### 完整的游戏控制器示例 + +```csharp +using GFramework.SourceGenerators.Attributes; +using GFramework.Core.Abstractions; + +[Log] +[ContextAware] +public partial class GameController : Node, IController +{ + private PlayerModel _playerModel; + private CombatSystem _combatSystem; + + public override void _Ready() + { + // 初始化模型和系统引用 + _playerModel = Context.GetModel(); + _combatSystem = Context.GetSystem(); + + // 监听事件 + this.RegisterEvent(OnPlayerInput) + .UnRegisterWhenNodeExitTree(this); + + Logger.Info("Game controller initialized"); + } + + private void OnPlayerInput(PlayerInputEvent e) + { + Logger.Debug($"Processing player input: {e.Action}"); + + switch (e.Action) + { + case "attack": + HandleAttack(); + break; + case "defend": + HandleDefend(); + break; + } + } + + private void HandleAttack() + { + if (_playerModel.CanAttack()) + { + Logger.Info("Player attacks"); + _combatSystem.ProcessAttack(); + Context.SendEvent(new AttackEvent()); + } + else + { + Logger.Warning("Player cannot attack - cooldown"); + } + } + + private void HandleDefend() + { + if (_playerModel.CanDefend()) + { + Logger.Info("Player defends"); + _playerModel.IsDefending.Value = true; + Context.SendEvent(new DefendEvent()); + } + else + { + Logger.Warning("Player cannot defend"); + } + } +} +``` + +### 枚举状态管理示例 + +```csharp +[GenerateEnumExtensions( + generateIsMethods = true, + generateHasMethod = true, + generateInMethod = true, + includeToString = true +)] +public enum CharacterState +{ + Idle, + Walking, + Running, + Jumping, + Falling, + Attacking, + Hurt, + Dead +} + +[Log] +[ContextAware] +public partial class CharacterController : Node, IController +{ + private CharacterModel _characterModel; + + public override void _Ready() + { + _characterModel = Context.GetModel(); + + // 监听状态变化 + _characterModel.State.Register(OnStateChanged); + } + + private void OnStateChanged(CharacterState newState) + { + Logger.Info($"Character state changed to: {newState.ToDisplayString()}"); + + // 使用生成的扩展方法 + if (newState.IsDead()) + { + HandleDeath(); + } + else if (newState.IsHurt()) + { + HandleHurt(); + } + else if (newState.In(CharacterState.Walking, CharacterState.Running)) + { + StartMovementEffects(); + } + + // 检查是否可以接受输入 + if (newState.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running)) + { + EnableInput(); + } + else + { + DisableInput(); + } + } + + private bool CanAttack() + { + var state = _characterModel.State.Value; + return state.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running); + } + + private void HandleDeath() + { + Logger.Info("Character died"); + DisableInput(); + PlayDeathAnimation(); + Context.SendEvent(new CharacterDeathEvent()); + } +} ``` ## 最佳实践 -### 1. 合理使用特性 +### 🎯 属性使用策略 + +#### 1. 合理的属性组合 ```csharp -// ✅ 好的做法:在需要的地方使用 +// 好的做法:相关功能组合使用 [Log] -public class ImportantService { } - -// ❌ 避免过度使用 -[Log] // 不是每个类都需要日志 -public class SimpleDataClass { } -``` - -### 2. 配置优化 - -```xml - - - Warning - - - - - Debug - -``` - -### 3. 性能考虑 - -```csharp -// ✅ 生成器适合复杂场景 -[GenerateValidationRules] // 复杂验证逻辑 -public class BusinessEntity { } - -// ❌ 简单场景手动实现更高效 -public class SimpleConfig +[ContextAware] +public partial class BusinessLogicComponent : IComponent { - public string Name { get; set; } - // 简单属性不需要生成器 + // 既有日志记录又有上下文访问 +} + +// 避免:不必要的属性 +[Log] // ❌ 静态工具类通常不需要日志 +public static class MathHelper +{ + public static int Add(int a, int b) => a + b; } ``` -## 下一步 +#### 2. 命名约定 -- [深入了解日志生成器](/source-generators/logging-generator) -- [学习枚举扩展生成](/source-generators/enum-extensions) -- [掌握规则生成器](/source-generators/rule-generator) -- [查看生成器 API](/api-reference/generators-api) \ No newline at end of file +```csharp +// 好的做法:一致的命名 +[Log(fieldName = "Logger")] +public partial class PlayerController { } + +[Log(fieldName = "Logger")] +public partial class EnemyController { } + +// 避免:不一致的命名 +[Log(fieldName = "Logger")] +public partial class PlayerController { } + +[Log(fieldName = "CustomLogger")] +public partial class EnemyController { } +``` + +### 🏗️ 项目组织 + +#### 1. 分离生成器和业务逻辑 + +```csharp +// 好的做法:部分类分离 +// PlayerController.Logic.cs - 业务逻辑 +public partial class PlayerController : IController +{ + public void Move(Vector2 direction) + { + if (CanMove()) + { + UpdatePosition(direction); + Logger.Debug($"Player moved to {direction}"); + } + } +} + +// PlayerController.Generated.cs - 生成代码所在 +// 不需要手动维护,由生成器处理 +``` + +#### 2. 枚举设计 + +```csharp +// 好的做法:有意义的枚举设计 +[GenerateEnumExtensions] +public enum GameState +{ + MainMenu, // 主菜单 + Playing, // 游戏中 + Paused, // 暂停 + GameOver, // 游戏结束 + Victory // 胜利 +} + +// 避免:含义不明确的枚举值 +[GenerateEnumExtensions] +public enum State +{ + State1, + State2, + State3 +} +``` + +### 🔧 性能优化 + +#### 1. 避免过度日志 + +```csharp +// 好的做法:合理的日志级别 +[Log(minLevel = LogLevel.Information)] +public partial class PerformanceCriticalComponent +{ + public void Update() + { + // 只在必要时记录日志 + if (_performanceCounter % 1000 == 0) + { + Logger.Debug($"Performance: {_performanceCounter} ticks"); + } + } +} + +// 避免:过度日志记录 +[Log(minLevel = LogLevel.Debug)] +public partial class NoisyComponent +{ + public void Update() + { + Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁 + } +} +``` + +#### 2. 延迟上下文初始化 + +```csharp +// 好的做法:延迟初始化 +[ContextAware(useLazy = true)] +public partial class LazyContextComponent : IComponent +{ + // 只有在第一次访问 Context 时才会初始化 + public void Initialize() + { + // 如果这里不需要 Context,就不会初始化 + SomeOtherInitialization(); + } +} +``` + +### 🛡️ 错误处理 + +#### 1. 上下文验证 + +```csharp +[ContextAware(validateContext = true)] +public partial class SafeContextComponent : IComponent +{ + public void ProcessData() + { + if (Context.IsInvalid) + { + Logger.Error("Context is invalid, cannot process data"); + return; + } + + // 安全地使用 Context + var model = Context.GetModel(); + // ... + } +} +``` + +#### 2. 异常处理配合 + +```csharp +[Log] +[ContextAware] +public partial class RobustComponent : IComponent +{ + public void RiskyOperation() + { + try + { + var model = Context.GetModel(); + model.PerformRiskyOperation(); + Logger.Info("Operation completed successfully"); + } + catch (Exception ex) + { + Logger.Error($"Operation failed: {ex.Message}"); + Context.SendEvent(new OperationFailedEvent { Error = ex.Message }); + } + } +} +``` + +## 常见问题 + +### Q: 为什么需要标记类为 `partial`? + +**A**: 源代码生成器需要向现有类添加代码,`partial` 关键字允许一个类的定义分散在多个文件中。生成器会在编译时创建另一个部分类文件,包含生成的代码。 + +```csharp +[Log] +public partial class MyClass { } // ✅ 需要 partial + +[Log] +public class MyClass { } // ❌ 编译错误,无法添加生成代码 +``` + +### Q: 生成的代码在哪里? + +**A**: 生成的代码在编译过程中创建,默认位置在 `obj/Debug/net6.0/generated/` 目录下。可以在项目文件中配置输出位置: + +```xml + + Generated + +``` + +### Q: 如何调试生成器问题? + +**A**: 生成器提供了详细的诊断信息: + +1. **查看错误列表**:编译错误会显示在 IDE 中 +2. **查看生成文件**:检查生成的代码文件 +3. **启用详细日志**:在项目文件中添加: + +```xml + + true + +``` + +### Q: 可以自定义生成器吗? + +**A**: 当前版本的生成器支持有限的配置。如需完全自定义,可以创建自己的源代码生成器项目。 + +### Q: 性能影响如何? + +**A**: 源代码生成器对运行时性能的影响几乎为零: + +- **编译时**:可能会增加编译时间(通常几秒) +- **运行时**:与手写代码性能相同 +- **内存使用**:与手写代码内存使用相同 + +### Q: 与依赖注入框架兼容吗? + +**A**: 完全兼容。生成器创建的是标准代码,可以与任何依赖注入框架配合使用: + +```csharp +[Log] +[ContextAware] +public partial class ServiceComponent : IService +{ + // 可以通过构造函数注入依赖 + private readonly IDependency _dependency; + + public ServiceComponent(IDependency dependency) + { + _dependency = dependency; + Logger.Info("Service initialized with dependency injection"); + } +} +``` + +--- + +## 依赖关系 + +```mermaid +graph TD + A[GFramework.SourceGenerators] --> B[GFramework.SourceGenerators.Abstractions] + A --> C[GFramework.SourceGenerators.Common] + A --> D[GFramework.Core.Abstractions] + A --> E[Microsoft.CodeAnalysis.CSharp] + A --> F[Microsoft.CodeAnalysis.Analyzers] +``` + +## 版本兼容性 + +- **.NET**: 6.0+ +- **Visual Studio**: 2022 17.0+ +- **Rider**: 2022.3+ +- **Roslyn**: 4.0+ + +## 许可证 + +本项目基于 Apache 2.0 许可证 - 详情请参阅 [LICENSE](../LICENSE) 文件。 + +--- + +**版本**: 1.0.0 +**更新日期**: 2026-01-12 \ No newline at end of file