From 5d436694f8060d1363cf4f30a28fb6403a5ad97f Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:50:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(godot):=20=E6=94=B6=E5=8F=A3=20Godot=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E4=B8=8E=E6=9E=B6=E6=9E=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 Godot landing page,使包关系、最小接入路径与运行时边界回到源码与测试契约 - 重写 Godot architecture 页面,明确锚点生命周期、模块挂接顺序与 IGodotModule 契约边界 - 更新 documentation-governance topic 的 RP-014 跟踪与验证记录,标记下一轮收口目标 --- ...ntation-governance-and-refresh-tracking.md | 32 +- ...umentation-governance-and-refresh-trace.md | 23 +- docs/zh-CN/godot/architecture.md | 648 ++-------- docs/zh-CN/godot/index.md | 1052 +++-------------- 4 files changed, 304 insertions(+), 1451 deletions(-) diff --git a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md index 3dc3a5e7..e59bd2a3 100644 --- a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md +++ b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md @@ -7,19 +7,21 @@ ## 当前恢复点 -- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-013` +- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-014` - 当前阶段:`Phase 3` - 当前焦点: - 已建立统一公开 skill:`.agents/skills/gframework-doc-refresh/` - 文档重构入口已从“按 guide/tutorial/api 类型拆 skill”收口为“按源码模块驱动文档刷新” - - `docs/zh-CN/tutorials/godot-integration.md` 已按当前实现重写,教程入口不再复刻旧 Godot API 叙述 - - `docs/zh-CN/godot/index.md` 与 `docs/zh-CN/godot/architecture.md` 仍保留同类旧写法,成为下一轮高优先级收口对象 + - `docs/zh-CN/godot/index.md` 已改成源码优先的模块 landing page,不再把 `GetNodeX`、`CreateSignalBuilder`、`InstallGodotModule(...)` 写成默认入口 + - `docs/zh-CN/godot/architecture.md` 已改成当前锚点生命周期、模块挂接顺序和接口边界说明,不再沿用旧版 `.Wait()` 叙述 + - `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md` 仍保留 `GodotSceneRouter` / `GodotUiRouter` 一类旧接线叙述,成为下一轮高优先级收口对象 ## 当前状态摘要 - 文档治理规则已收口到仓库规范,README、站点入口与采用链路不再依赖旧文档自证 - 高优先级模块入口、`core` 关键专题页与 `tutorials/godot-integration.md` 已回到“以源码 / 测试 / README 为准”的状态 -- 当前主题仍是 active topic,因为 `docs/zh-CN/godot/` 栏目入口和架构页仍有旧 API / 旧接入路径叙述,Godot 文档链路尚未完全收口 +- `docs/zh-CN/godot/index.md` 与 `docs/zh-CN/godot/architecture.md` 已完成当前实现收口 +- 当前主题仍是 active topic,因为 `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md` 仍保留旧 router / 工厂接线叙述,Godot 文档链路尚未完全收口 ## 当前活跃事实 @@ -68,6 +70,12 @@ - `docs/zh-CN/tutorials/godot-integration.md` 已改成“包关系、`project.godot` 接线、`[GetNode]` / `[BindNodeSignal]` 协作顺序、运行时扩展边界、迁移提醒”的结构, 不再把 `GetNodeX`、`CreateSignalBuilder`、`AbstractGodotModule` 默认化叙述为当前推荐路径 - `docs/zh-CN/tutorials/index.md` 中 Godot 教程入口摘要已同步改成“项目级配置 + 生成器协作 + 生命周期边界”,不再继续宣传对象池 / 性能优化式旧范围 +- `docs/zh-CN/godot/index.md` 已改成“模块定位、包关系、最小接入路径、关键入口、当前边界”的 landing page 结构,并明确把 + `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 归到 `GFramework.Godot.SourceGenerators` +- `docs/zh-CN/godot/architecture.md` 已改成“何时继承 `AbstractArchitecture`、何时使用 `InstallGodotModule(...)`、锚点生命周期、 + `IGodotModule` 契约边界”的结构,不再把 `OnPhase(...)` / `OnArchitecturePhase(...)` 写成稳定自动广播 +- 本轮再次执行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh` 校验 `godot/index.md` 与 + `godot/architecture.md`,并执行 `cd docs && bun run build`,站点构建继续通过 - `.agents/skills/gframework-doc-refresh/SKILL.md` 已改成标准 YAML frontmatter skill,并明确支持模块输入、证据顺序、输出优先级与验证步骤 - `.agents/skills/gframework-doc-refresh/SKILL.md` 的 `description` 已加引号,修复 `Recommended command:` 中冒号导致的 invalid YAML skill 加载警告 @@ -88,10 +96,10 @@ `godot-project-generator.md`、`get-node-generator.md`、`bind-node-signal-generator.md` 与 `auto-register-exported-collections-generator.md` 已完成收口; 继续按源码、测试、`*.csproj` 与 `ai-libs/` 下已验证参考实现核对剩余 Godot 相关页面,不把旧文档当事实来源 -- Godot 栏目入口失真风险:`docs/zh-CN/godot/index.md` 与 `docs/zh-CN/godot/architecture.md` 仍保留 `GetNodeX`、 - `CreateSignalBuilder`、`InstallGodotModule(...).Wait()` 等旧叙述,可能覆盖新教程的采用路径 - - 缓解措施:本轮已完成 tutorial 收口;下一轮优先按当前 `GFramework.Godot` / `GFramework.Godot.SourceGenerators` / - `ai-libs/CoreGrid` 的真实采用路径重写 `godot/index.md`,并评估 `godot/architecture.md` 的最小收口范围 +- Godot 场景 / UI 专题页失真风险:`docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md` 仍保留 `GodotSceneRouter`、 + `GodotUiRouter` 和旧工厂接线叙述,可能把已经收口的入口页再次带偏 + - 缓解措施:本轮已完成 `godot/index.md` 与 `godot/architecture.md` 收口;下一轮优先按当前 `GFramework.Godot.Scene` / + `GFramework.Godot.UI` 实现与 `ai-libs/CoreGrid` 的真实采用路径重写 `scene.md`、`ui.md` - 采用路径误导风险:根聚合包与模块边界若再次被写错,会继续误导消费者的包选择 - 缓解措施:保持“源码与包关系优先”的证据顺序,改动采用说明时同步核对包依赖与生成器 wiring - 模块映射不全风险:统一 skill 若遗漏模块别名、测试项目或 docs 栏目映射,会让后续扫描阶段直接失焦 @@ -139,9 +147,13 @@ - `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md` - `rg -n "GetNodeX|CreateSignalBuilder|GodotGameArchitecture|AbstractGodotModule|InstallGodotModule\(|GFramework\.Godot\.Pool" docs/zh-CN/godot docs/zh-CN/tutorials -S` - `cd docs && bun run build` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/architecture.md` +- `rg -n "GodotSceneRouter|GodotUiRouter|CreateSignalBuilder|GetNodeX|InstallGodotModule\(" docs/zh-CN/godot -S` +- `cd docs && bun run build` ## 下一步 -1. 优先重写 `docs/zh-CN/godot/index.md`,清掉 `GetNodeX`、`CreateSignalBuilder`、`InstallGodotModule(...)` 默认化叙述 -2. 视 `godot/index.md` 收口结果,决定是否同步压缩 `docs/zh-CN/godot/architecture.md`,避免两页继续互相复制旧接入路径 +1. 优先重写 `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md`,清掉 `GodotSceneRouter` / `GodotUiRouter` 一类旧接线叙述 +2. 视 `scene.md` / `ui.md` 收口结果,决定是否同步压缩 `docs/zh-CN/godot/signal.md` 与 `extensions.md` 3. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否按预期收敛 diff --git a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md index 300549ca..c741c7ac 100644 --- a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md +++ b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md @@ -2,7 +2,7 @@ ## 2026-04-22 -### 当前恢复点:RP-013 +### 当前恢复点:RP-014 - 本轮从 PR #268 的最新 review 数据恢复,未发现失败检查;CTRF 报告显示 2139 个测试全部通过 - 本轮复核确认当前 PR 的 latest-head open thread 同时来自 `coderabbitai[bot]` 与 `greptile-apps[bot]` @@ -26,8 +26,12 @@ - 本轮已重写 `docs/zh-CN/tutorials/godot-integration.md`,把内容收口为“包关系、`project.godot` 接线、`[GetNode]` / `[BindNodeSignal]` 协作顺序、运行时扩展边界、迁移提醒”,不再把旧 Godot API 列表当事实来源 - `docs/zh-CN/tutorials/index.md` 的 Godot 教程入口摘要已同步改成当前采用路径,避免入口页继续把教程描述成对象池 / 性能优化总览 -- 本轮检索确认 Godot 栏目仍有下一批高风险页面:`docs/zh-CN/godot/index.md` 与 `docs/zh-CN/godot/architecture.md` 还保留 - `GetNodeX`、`CreateSignalBuilder`、`InstallGodotModule(...).Wait()` 等旧叙述,应作为 tutorial 之后的下一轮收口对象 +- 本轮已重写 `docs/zh-CN/godot/index.md`,改成“模块定位、包关系、最小接入路径、关键入口、当前边界”的 landing page 结构, + 明确把 `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 收口到 `GFramework.Godot.SourceGenerators` +- 本轮已重写 `docs/zh-CN/godot/architecture.md`,改成“锚点生命周期、`InstallGodotModule(...)` 执行顺序、`IGodotModule` + 契约边界”的结构,不再沿用旧版 `.Wait()` 和自动阶段广播叙述 +- 本轮检索确认 Godot 栏目新的高优先级页面转为 `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md`:两页仍保留 + `GodotSceneRouter` / `GodotUiRouter` 一类旧接线叙述,应作为 landing / architecture 之后的下一轮收口对象 ### 当前决策 @@ -39,7 +43,10 @@ - `BindNodeSignal` 页面明确记录“当前不自动生成 `_Ready()` / `_ExitTree()`”,避免继续把它写成自动生命周期织入器 - `auto-register-exported-collections` 页面明确区分“运行时 null 时跳过注册”和“配置错误时编译期报错”,避免旧文档把两类边界混为一谈 - `godot-integration.md` 已重新成为可用的采用路径入口;后续 Godot 文档收口应优先处理 `godot/index.md` 和 `godot/architecture.md` -- `docs/zh-CN/godot/index.md` 若继续保留旧写法,会重新把 tutorial 已清掉的旧接入路径带回导航入口,因此优先级高于继续扩展新教程 +- `godot/index.md` 与 `godot/architecture.md` 现在都必须维持“运行时包与生成器包分边界”的写法,不能再把场景注入和项目元数据生成写回 + `GFramework.Godot` 运行时契约 +- `scene.md` 与 `ui.md` 的下一轮重写应以 `GFramework.Godot.Scene`、`GFramework.Godot.UI`、`ai-libs/CoreGrid` 的当前 wiring 为准, + 不再继续复刻并不存在的 `GodotSceneRouter` / `GodotUiRouter` ### 验证 @@ -58,9 +65,13 @@ - `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md` - `cd docs && bun run build` - `rg -n "GetNodeX|CreateSignalBuilder|GodotGameArchitecture|AbstractGodotModule|InstallGodotModule\(|GFramework\\.Godot\\.Pool" docs/zh-CN/godot docs/zh-CN/tutorials -S` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/architecture.md` +- `rg -n "GodotSceneRouter|GodotUiRouter|CreateSignalBuilder|GetNodeX|InstallGodotModule\(" docs/zh-CN/godot -S` +- `cd docs && bun run build` ### 下一步 -1. 优先重写 `docs/zh-CN/godot/index.md`,清掉 `GetNodeX`、`CreateSignalBuilder`、`InstallGodotModule(...)` 默认化叙述 -2. 视 `godot/index.md` 收口结果,决定是否同步压缩 `docs/zh-CN/godot/architecture.md` +1. 优先重写 `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md`,清掉 `GodotSceneRouter` / `GodotUiRouter` 一类旧接线叙述 +2. 视 `scene.md` / `ui.md` 收口结果,决定是否同步压缩 `docs/zh-CN/godot/signal.md` 与 `extensions.md` 3. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否关闭或减少 diff --git a/docs/zh-CN/godot/architecture.md b/docs/zh-CN/godot/architecture.md index b5efb31d..3261a6b3 100644 --- a/docs/zh-CN/godot/architecture.md +++ b/docs/zh-CN/godot/architecture.md @@ -1,612 +1,176 @@ --- title: Godot 架构集成 -description: Godot 架构集成提供了 GFramework 与 Godot 引擎的无缝连接,实现生命周期同步和模块化开发。 +description: 说明 AbstractArchitecture、ArchitectureAnchor 和 Godot 模块挂接的当前生命周期语义,避免继续沿用旧版 `.Wait()` 接法。 --- # Godot 架构集成 ## 概述 -Godot 架构集成是 GFramework.Godot 中连接框架与 Godot 引擎的核心组件。它提供了架构与 Godot 场景树的生命周期绑定、模块化扩展系统,以及与 -Godot 节点系统的深度集成。 +`GFramework.Godot` 当前的架构集成目标很直接:让 `Architecture` 能安全地感知 Godot `SceneTree` 生命周期,并在需要时把 +带 `Node` 的扩展模块挂到场景树上。 -通过 Godot 架构集成,你可以在 Godot 项目中使用 GFramework 的所有功能,同时保持与 Godot 引擎的完美兼容。 +当前真正参与这条链路的核心类型只有三类: -**主要特性**: +- `AbstractArchitecture`:在原有 `Architecture` 之上增加 Godot 生命周期绑定 +- `ArchitectureAnchor`:挂在 `SceneTree.Root` 下的锚点节点,负责把 `_ExitTree()` 事件转回架构销毁 +- `IGodotModule` / `AbstractGodotModule`:当模块本身需要携带 Godot `Node` 时使用 -- 架构与 Godot 生命周期自动同步 -- 模块化的 Godot 扩展系统 -- 架构锚点节点管理 -- 自动资源清理 -- 热重载支持 -- 与 Godot 场景树深度集成 +它不是另一套独立的模块系统,也不意味着所有模块都必须改成 `InstallGodotModule(...)`。 -## 核心概念 +## 什么时候该用 `AbstractArchitecture` -### 抽象架构 +当你的架构需要满足下面任一条件时,可以让它继承 `AbstractArchitecture`: -`AbstractArchitecture` 是 Godot 项目中架构的基类: +- 需要把架构生命周期绑定到 Godot `SceneTree` +- 需要在架构里安装带 `Node` 的扩展模块 +- 需要通过受保护的 `ArchitectureRoot` 访问锚点节点,继续挂接 Godot 子节点 + +如果你只是做普通的 Model / System / Utility 注册,`AbstractArchitecture` 的主要价值仍然是“让架构知道自己何时跟随 +Godot 场景树销毁”,而不是改变注册方式。 + +## 最小接入路径 + +### 常规模块仍然用 `InstallModule(...)` + +当前消费者 `ai-libs/CoreGrid` 的默认做法,是保持普通模块注册方式: ```csharp -public abstract class AbstractArchitecture : Architecture +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Environment; +using GFramework.Godot.Architectures; + +namespace MyGame.Scripts.Core; + +public sealed class GameArchitecture( + IArchitectureConfiguration configuration, + IEnvironment environment) + : AbstractArchitecture(configuration, environment) { - protected Node ArchitectureRoot { get; } - protected abstract void InstallModules(); - protected Task InstallGodotModule(TModule module); -} -``` - -### 架构锚点 - -`ArchitectureAnchor` 是连接架构与 Godot 场景树的桥梁: - -```csharp -public partial class ArchitectureAnchor : Node -{ - public void Bind(Action onExit); - public override void _ExitTree(); -} -``` - -### Godot 模块 - -`IGodotModule` 定义了 Godot 特定的模块接口: - -```csharp -public interface IGodotModule : IArchitectureModule -{ - Node Node { get; } - void OnPhase(ArchitecturePhase phase, IArchitecture architecture); - void OnAttach(Architecture architecture); - void OnDetach(); -} -``` - -## 基本用法 - -### 创建 Godot 架构 - -```csharp -using GFramework.Godot.Architecture; -using GFramework.Core.Abstractions.Architecture; - -public class GameArchitecture : AbstractArchitecture -{ - // 单例实例 - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - protected override void InstallModules() { - // 注册 Model - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - - // 注册 System - RegisterSystem(new GameplaySystem()); - RegisterSystem(new AudioSystem()); - - // 注册 Utility - RegisterUtility(new StorageUtility()); + InstallModule(new UtilityModule()); + InstallModule(new ModelModule()); + InstallModule(new GameplayModule()); + InstallModule(new SystemModule()); } } ``` -### 在 Godot 场景中初始化架构 +这里继承 `AbstractArchitecture` 的意义,是把架构绑定到 Godot 生命周期,而不是把普通模块注册改写成 Godot 风格 API。 + +### 只有携带 `Node` 的模块才需要 `InstallGodotModule(...)` + +如果模块本身暴露一个 Godot `Node`,并且希望由架构锚点统一托管,可以这样写: ```csharp -using Godot; -using GFramework.Godot.Architecture; - -public partial class GameRoot : Node -{ - private GameArchitecture _architecture; - - public override void _Ready() - { - // 创建并初始化架构 - _architecture = new GameArchitecture(); - _architecture.InitializeAsync().AsTask().Wait(); - - GD.Print("架构已初始化"); - } -} -``` - -### 使用架构锚点 - -架构锚点会自动创建并绑定到场景树: - -```csharp -// 架构会自动创建锚点节点 -// 节点名称格式: __GFramework__GameArchitecture__[HashCode]__ArchitectureAnchor__ - -// 当场景树销毁时,锚点会自动触发架构清理 -``` - -## 高级用法 - -### 创建 Godot 模块 - -```csharp -using GFramework.Godot.Architecture; +using GFramework.Core.Abstractions.Architectures; +using GFramework.Godot.Architectures; using Godot; -public class CoroutineModule : AbstractGodotModule +namespace MyGame.Scripts.Core; + +public sealed class HudModule : AbstractGodotModule { - private Node _coroutineNode; - - public override Node Node => _coroutineNode; - - public CoroutineModule() + private readonly Control _root = new() { - _coroutineNode = new Node { Name = "CoroutineScheduler" }; - } + Name = "HudModule" + }; + + public override Node Node => _root; public override void Install(IArchitecture architecture) { - // 注册协程调度器 - var scheduler = new CoroutineScheduler(new GodotTimeSource()); - architecture.RegisterSystem(scheduler); - - GD.Print("协程模块已安装"); } - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + public override void OnAttach(GFramework.Core.Architectures.Architecture architecture) { - if (phase == ArchitecturePhase.Ready) - { - GD.Print("协程模块已就绪"); - } } public override void OnDetach() { - GD.Print("协程模块已分离"); - _coroutineNode?.QueueFree(); + _root.QueueFree(); } } ``` -### 安装 Godot 模块 +这类模块的关键点不是“注册更多框架能力”,而是“让模块节点跟着架构锚点进出场景树”。 +真正调用 `InstallGodotModule(...)` 时,也应该把它放在能够接受异步挂接流程的初始化路径里,而不是继续沿用旧文档里的 +`.Wait()` 叙述。 -```csharp -public class GameArchitecture : AbstractArchitecture -{ - protected override void InstallModules() - { - // 安装核心模块 - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); +## 当前生命周期 - // 安装 Godot 模块 - InstallGodotModule(new CoroutineModule()).Wait(); - InstallGodotModule(new SceneModule()).Wait(); - InstallGodotModule(new UiModule()).Wait(); - } -} -``` +### 初始化阶段 -### 访问架构根节点 +`AbstractArchitecture.OnInitialize()` 目前会按这个顺序工作: -```csharp -public class SceneModule : AbstractGodotModule -{ - private Node _sceneRoot; +1. 生成唯一的锚点节点名称 +2. 调用 `AttachToGodotLifecycle()` +3. 在可用的 `SceneTree` 上创建并绑定 `ArchitectureAnchor` +4. 执行你重写的 `InstallModules()` - public override Node Node => _sceneRoot; +也就是说,Godot 生命周期绑定先发生,业务模块注册后发生。 - public SceneModule() - { - _sceneRoot = new Node { Name = "SceneRoot" }; - } +### `InstallGodotModule(...)` 的执行顺序 - public override void Install(IArchitecture architecture) - { - // 访问架构根节点 - if (architecture is AbstractArchitecture godotArch) - { - var root = godotArch.ArchitectureRoot; - root.AddChild(_sceneRoot); - } - } -} -``` +当前实现里,`InstallGodotModule(...)` 会: -### 监听架构阶段 +1. 检查模块参数是否为 `null` +2. 检查 `_anchor` 是否已初始化 +3. 先执行 `module.Install(this)` +4. 把模块登记进内部 `_extensions` +5. `await anchor.WaitUntilReadyAsync()` +6. 通过 `CallDeferred(AddChild, module.Node)` 把模块节点挂到锚点下 +7. 调用 `module.OnAttach(this)` -```csharp -public class AnalyticsModule : AbstractGodotModule -{ - private Node _analyticsNode; +这条顺序有两个实际意义: - public override Node Node => _analyticsNode; +- 模块会在挂接节点前先完成框架侧注册 +- 只有等锚点真正 ready 后,才进入需要访问 Godot 节点 API 的附加阶段 - public AnalyticsModule() - { - _analyticsNode = new Node { Name = "Analytics" }; - } +### 销毁阶段 - public override void Install(IArchitecture architecture) - { - // 安装分析系统 - } +`ArchitectureAnchor._ExitTree()` 会触发绑定好的退出回调,随后 `AbstractArchitecture` 会开始观察异步销毁流程: - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.Initializing: - GD.Print("架构正在初始化"); - break; +- 防止重复销毁 +- 依次调用已登记 Godot 模块的 `OnDetach()` +- 清空内部扩展列表 +- 再进入基类 `DestroyAsync()` - case ArchitecturePhase.Ready: - GD.Print("架构已就绪,开始追踪"); - StartTracking(); - break; +如果异步销毁抛异常,当前实现会把错误写到 Godot 错误输出,而不是静默吞掉。 - case ArchitecturePhase.Destroying: - GD.Prin构正在销毁,停止追踪"); - StopTracking(); - break; - } - } +## 当前边界 - private void StartTracking() { } - private void StopTracking() { } -} -``` +### 没有锚点时不会偷偷安装模块 -### 自定义架构配置 +`GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs` 已覆盖一个关键边界: -```csharp -using GFramework.Core.Abstractions.Architecture; -using GFramework.Core.Abstractions.Environment; +- 当锚点尚未初始化时,`InstallGodotModule(...)` 会直接抛 `InvalidOperationException("Anchor not initialized")` +- 失败发生在 `module.Install(...)` 之前,因此不会留下半安装副作用 -public class GameArchitecture : AbstractArchitecture -{ - public GameArchitecture() : base( - configuration: CreateConfiguration(), - environment: CreateEnvironment() - ) - { - } +这也是为什么文档不应该再把 `InstallGodotModule(...).Wait()` 写成一种随处可用的默认初始化方式。 - private static IArchitectureConfiguration CreateConfiguration() - { - return new ArchitectureConfiguration - { - EnableLogging - LogLevel = LogLevel.Debug - }; - } +### `AbstractGodotModule` 只是便捷基类,不代表自动阶段广播 - private static IEnvironment CreateEnvironment() - { - return new DefaultEnvironment - { - IsDevelopment = OS.IsDebugBuild() - }; - } +当前接口 `IGodotModule` 真正保证的成员只有: - protected override void InstallModules() - { - // 根据环境配置安装模块 - if (Environment.IsDevelopment) - { - InstallGodotModule(new DebugModule()).Wait(); - } +- `Node` +- `Install(IArchitecture architecture)` +- `OnAttach(Architecture architecture)` +- `OnDetach()` - // 安装核心模块 - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); - } -} -``` +`AbstractGodotModule` 里虽然保留了 `OnPhase(...)` / `OnArchitecturePhase(...)` 虚方法,但它们不在当前接口契约内,也没有在 +这条挂接流程里形成稳定的自动广播语义。不要把它写成当前公开保证。 -### 热重载支持 +### `ArchitectureRoot` 只在锚点就绪后可用 -```csharp -public class GameArchitecture : AbstractArchitecture -{ - private static bool _initialized; +`ArchitectureRoot` 是受保护属性,底层直接返回 `_anchor`。如果锚点尚未准备好或架构已经失效,它会抛 +`InvalidOperationException("Architecture root not ready")`。因此它适合放在明确依赖锚点存在的挂接逻辑里,而不是拿来做 +任意时机的全局节点查找。 - protected override void OnInitialize() - { - // 防止热重载时重复初始化 - if (_initialized) - { - GD.Print("架构已初始化,跳过重复初始化"); - return; - } +## 继续阅读 - base.OnInitialize(); - _initialized = true; - } - - protected override async ValueTask OnDestroyAsync() - { - await base.OnDestroyAsync(); - _initialized = false; - } -} -``` - -### 在节点中使用架构 - -```csharp -using Godot; -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class Player : CharacterBody2D, IController -{ - public override void _Ready() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var playerModel = this.GetModel(); - var gameplaySystem = this.GetSystem(); - - // 发送事件 - this.SendEvent(new PlayerSpawnedEvent()); - - // 执行命令 - this.SendCommand(new InitPlayerCommand()); - } - - public override void _Process(double delta) - { - // 在 Process 中使用架构组件 - var inputSystem = this.GetSystem(); - var movement = inputSystem.GetMovementInput(); - - Velocity = movement * 200; - MoveAndSlide(); - } -} -``` - -### 多架构支持 - -```csharp -// 游戏架构 -public class GameArchitecture : AbstractArchitecture -{ - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - - protected override void InstallModules() - { - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); - } -} - -// UI 架构 -public class UiArchitecture : AbstractArchitecture -{ - public static UiArchitecture Interface { get; private set; } - - public UiArchitecture() - { - Interface = this; - } - - protected override void InstallModules() - { - RegisterModel(new UiModel()); - RegisterSystem(new UiSystem()); - } -} - -// 在不同节点中使用不同架构 -[ContextAware] -public partial class GameNode : Node, IController -{ - // 配置使用 GameArchitecture 的上下文提供者 - static GameNode() - { - SetContextProvider(new GameContextProvider()); - } -} - -[ContextAware] -public partial class UiNode : Control, IController -{ - // 配置使用 UiArchitecture 的上下文提供者 - static UiNode() - { - SetContextProvider(new UiContextProvider()); - } -} -``` - -## 最佳实践 - -1. **使用单例模式**:为架构提供全局访问点 - ```csharp - public class GameArchitecture : AbstractArchitecture - { - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - } - ``` - -2. **在根节点初始化架构**:确保架构在所有节点之前就绪 - ```csharp - public partial class GameRoot : Node - { - public override void _Ready() - { - new GameArchitecture().InitializeAsync().AsTask().Wait(); - } - } - ``` - -3. **使用 Godot 模块组织功能**:将相关功能封装为模块 - ```csharp - InstallGodotModule(new CoroutineModule()).Wait(); - InstallGodotModule(new SceneModule()).Wait(); - InstallGodotModule(new UiModule()).Wait(); - ``` - -4. **利用架构阶段钩子**:在适当的时机执行逻辑 - ```csharp - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - if (phase == ArchitecturePhase.Ready) - { - // 架构就绪后的初始化 - } - } - ``` - -5. **正确清理资源**:在 OnDetach 中释放 Godot 节点 - ```csharp - public override void OnDetach() - { - _node?.QueueFree(); - _node = null; - } - ``` - -6. **避免在构造函数中访问架构**:使用 _Ready 或 OnPhase - ```csharp - ✗ public Player() - { - var model = this.GetModel(); // 架构可能未就绪 - } - - ✓ public override void _Ready() - { - var model = this.GetModel(); // 安全 - } - ``` - -## 常见问题 - -### 问题:架构什么时候初始化? - -**解答**: -在根节点的 `_Ready` 方法中初始化: - -```csharp -public partial class GameRoot : Node -{ - public override void _Ready() - { - new GameArchitecture().InitializeAsync().AsTask().Wait(); - } -} -``` - -### 问题:如何在节点中访问架构? - -**解答**: -使用 `[ContextAware]` 特性或直接使用单例: - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -// 方式 1: 使用 [ContextAware] 特性(推荐) -[ContextAware] -public partial class Player : Node, IController -{ - public override void _Ready() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var model = this.GetModel(); - var system = this.GetSystem(); - } -} - -// 方式 2: 直接使用单例 -public partial class Enemy : Node -{ - public override void _Ready() - { - var model = GameArchitecture.Interface.GetModel(); - } -} -``` - -**注意**: - -- `IController` 是标记接口,不包含任何方法 -- 架构访问能力由 `[ContextAware]` 特性提供 -- `[ContextAware]` 会自动生成 `Context` 属性和实现 `IContextAware` 接口 -- 扩展方法(如 `this.GetModel()`)基于 `IContextAware` 接口,而非 `IController` - -### 问题:架构锚点节点是什么? - -**解答**: -架构锚点是一个隐藏的节点,用于将架构绑定到 Godot 场景树。当场景树销毁时,锚点会自动触发架构清理。 - -### 问题:如何支持热重载? - -**解答**: -使用静态标志防止重复初始化: - -```csharp -private static bool _initialized; - -protected override void OnInitialize() -{ - if (_initialized) return; - base.OnInitialize(); - _initialized = true; -} -``` - -### 问题:可以有多个架构吗? - -**解答**: -可以,但通常一个游戏只需要一个主架构。如果需要多个架构,为每个架构提供独立的单例: - -```csharp -public class GameArchitecture : AbstractArchitecture -{ - public static GameArchitecture Interface { get; private set; } -} - -public class UiArchitecture : AbstractArchitecture -{ - public static UiArchitecture Interface { get; private set; } -} -``` - -### 问题:Godot 模块和普通模块有什么区别? - -**解答**: - -- **普通模块**:纯 C# 逻辑,不依赖 Godot -- **Godot 模块**:包含 Godot 节点,与场景树集成 - -```csharp -// 普通模块 -InstallModule(new CoreModule()); - -// Godot 模块 -InstallGodotModule(new SceneModule()).Wait(); -``` - -## 相关文档 - -- [架构组件](/zh-CN/core/architecture) - 核心架构系统 -- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期 -- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景集成 -- [Godot UI 系统](/zh-CN/godot/ui) - Godot UI 集成 -- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法 +1. [Godot 运行时集成](./index.md) +2. [Godot 集成教程](../tutorials/godot-integration.md) +3. [Godot 场景系统](./scene.md) +4. [Godot UI 系统](./ui.md) diff --git a/docs/zh-CN/godot/index.md b/docs/zh-CN/godot/index.md index 49e0d89f..e1f19bb8 100644 --- a/docs/zh-CN/godot/index.md +++ b/docs/zh-CN/godot/index.md @@ -1,899 +1,165 @@ -# 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 = this.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": - this.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.WaitUntilReadyAsync(); - -// 获取父节点 -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(() => { - this.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 = this.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"); - - // 发送游戏开始事件 - this.SendEvent(new GameStartEvent()); - - Logger.Info("Game started"); - } - - public void PauseGame() - { - Logger.Info("Game paused"); - this.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 = this.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 = this.GetSystem(); - var bullet = bulletPool.Spawn("player"); - - if (bullet != null) - { - var direction = GetGlobalMousePosition() - GlobalPosition; - bullet.Initialize(GlobalPosition, direction.Normalized()); - GetTree().Root.AddChild(bullet); - - this.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"); - this.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()` 确保节点准备就绪 - +--- +title: Godot 运行时集成 +description: 以当前 GFramework.Godot 源码、测试与 CoreGrid 接线为准,说明 Godot 运行时包的定位、最小接入路径和文档入口。 --- -## 依赖关系 +# Godot 运行时集成 -```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] +## 模块定位 + +`GFramework.Godot` 是 `GFramework` 在 Godot 侧的运行时适配层。它负责把框架已有的 Core / Game 能力接到 Godot 的 +`Node`、`SceneTree`、`PackedScene`、`FileAccess` 和 `AudioServer` 上,而不是重新定义一套独立架构。 + +当前仓库里仍然成立的 Godot 运行时职责,主要集中在这些方向: + +- 架构生命周期与场景树绑定:`AbstractArchitecture`、`ArchitectureAnchor` +- 节点运行时辅助:`WaitUntilReadyAsync()`、`AddChildXAsync()`、`QueueFreeX()`、`UnRegisterWhenNodeExitTree(...)` +- Godot 风格的 Scene / UI 工厂与 registry:`GodotSceneFactory`、`GodotUiFactory` +- Godot 特化的存储、设置与配置加载:`GodotFileStorage`、`GodotAudioSettings`、`GodotYamlConfigLoader` +- 少量面向运行时交互的扩展:`Signal(...)` fluent API、暂停处理、富文本效果、协程时间源 + +它不是 `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 的来源。这些能力属于 +`GFramework.Godot.SourceGenerators`。 + +## 包关系 + +- `GeWuYou.GFramework`:聚合运行时包,提供 Core / Game 常用能力 +- `GeWuYou.GFramework.Godot`:当前页面对应的 Godot 运行时适配层 +- `GeWuYou.GFramework.Core.SourceGenerators`:`[ContextAware]`、`[GetModel]`、`[GetSystem]` 等 Core 侧生成器 +- `GeWuYou.GFramework.Godot.SourceGenerators`:`project.godot` 元数据、`[GetNode]`、`[BindNodeSignal]` + +从当前 `GFramework.Godot.csproj` 看,Godot 运行时包直接依赖: + +- `GFramework.Game` +- `GFramework.Game.Abstractions` +- `GFramework.Core.Abstractions` +- `GodotSharp` + +这意味着它更像是“把现有框架能力接到 Godot 宿主”的桥接层,而不是单独的 gameplay 框架。 + +## 最小接入路径 + +### 1. 先区分运行时包和生成器包 + +如果你只需要 Godot 运行时辅助,可以先安装: + +```bash +dotnet add package GeWuYou.GFramework +dotnet add package GeWuYou.GFramework.Godot ``` -## 版本兼容性 +如果你还需要 `project.godot` 的强类型入口、节点字段注入和 CLR event 绑定,再额外安装: -- **Godot**: 4.6 -- **.NET**: 6.0+ -- **GFramework.Core**: 与 Core 模块版本保持同步 \ No newline at end of file +```bash +dotnet add package GeWuYou.GFramework.Core.SourceGenerators +dotnet add package GeWuYou.GFramework.Godot.SourceGenerators +``` + +只装运行时包时,不会生成 `AutoLoads`、`InputActions`、`__InjectGetNodes_Generated()` 或 +`__BindNodeSignals_Generated()`。 + +### 2. 让架构继续负责注册,让 Godot 节点负责场景接线 + +`ai-libs/CoreGrid` 当前的真实做法,是让架构继承 `AbstractArchitecture`,但模块注册依然使用普通的 +`InstallModule(...)`: + +```csharp +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Environment; +using GFramework.Godot.Architectures; + +namespace MyGame.Scripts.Core; + +public sealed class GameArchitecture( + IArchitectureConfiguration configuration, + IEnvironment environment) + : AbstractArchitecture(configuration, environment) +{ + protected override void InstallModules() + { + InstallModule(new UtilityModule()); + InstallModule(new ModelModule()); + InstallModule(new GameplayModule()); + InstallModule(new SystemModule()); + } +} +``` + +也就是说,`GFramework.Godot` 的默认接入点不是“把所有注册都改成 Godot 模块”,而是“保持现有架构注册方式,只把需要 +Godot 生命周期协作的部分接到场景树上”。 + +### 3. 只在真的需要 Godot 节点挂接时使用 `InstallGodotModule(...)` + +`InstallGodotModule(...)` 适合这类模块: + +- 模块自身暴露一个 `Node` +- 模块希望在架构锚点就绪后被挂到场景树 +- 模块需要在 `OnAttach(...)` / `OnDetach()` 里处理 Godot 生命周期副作用 + +如果模块只是注册 Model、System、Utility,继续使用普通 `InstallModule(...)` 更符合当前消费者接法。 + +### 4. 节点脚本用运行时辅助,静态样板交给生成器 + +当前最常见的运行时入口是: + +- `UnRegisterWhenNodeExitTree(this Node node)`:把框架事件解绑挂到节点退出树 +- `WaitUntilReadyAsync()`:等待节点真正进入场景树 +- `AddChildXAsync()`:添加子节点后等待 ready +- `Signal(...)`:对 `GodotObject.Connect(...)` 的 fluent 包装 + +```csharp +using GFramework.Godot.Extensions; +using GFramework.Godot.Extensions.Signal; +using Godot; + +public partial class SettingsPanel : Control +{ + public override async void _Ready() + { + var button = GetNode