- 更新 Godot 场景文档,按当前 factory、registry、root、provider 与 AutoScene 接线收口采用路径 - 更新 Godot UI 文档,明确 layer 语义、provider 要求、root 接线与 AutoUiPage 用法 - 同步 documentation-governance-and-refresh 跟踪与 trace 到 RP-015,并把下一步切到 signal/extensions
9.3 KiB
title, description
| title | description |
|---|---|
| Godot 场景系统 | 以当前 GFramework.Godot 源码、Game 场景契约与 CoreGrid 接线为准,说明 PackedScene 场景工厂、行为包装和最小接入路径。 |
Godot 场景系统
GFramework.Godot 在场景这一层负责的是 Godot runtime 适配,而不是再提供一个 Godot 专属 router。
当前真正参与场景接线的核心类型是:
IGodotSceneRegistry/GodotSceneRegistryGodotSceneFactorySceneBehaviorFactorySceneBehaviorBase<T>及其Node2D/Node3D/Control/Generic实现- 项目侧实现的
ISceneRoot - 项目侧继承
SceneRouterBase的 router
也就是说,Godot 集成页的重点不是“再造一套场景导航 API”,而是把 PackedScene、Node 和 GFramework.Game 的
ISceneRouter / ISceneBehavior 契约接起来。
当前公开入口
IGodotSceneRegistry
Godot 侧的场景资源表,底层是 IAssetRegistry<PackedScene>。它只负责:
sceneKey -> PackedScene映射- 让
GodotSceneFactory能按 key 实例化场景
框架当前不会自动扫描项目里的 .tscn 文件并填充 registry。
GodotSceneFactory
GodotSceneFactory.Create(string sceneKey) 的当前行为很明确:
- 从
IGodotSceneRegistry取出PackedScene - 调用
Instantiate() - 如果节点实现了
ISceneBehaviorProvider,优先返回provider.GetScene() - 否则回退到
SceneBehaviorFactory.Create(node, sceneKey)
这和旧文档里“必须有 Godot 专属 router / 专属 scene provider 才能工作”的说法不同。当前源码允许两条路径:
- 显式 provider:项目自己决定行为对象
- 自动包装:按节点类型回退到默认 behavior
SceneBehaviorBase<T>
SceneBehaviorBase<T> 是当前 Godot 场景行为包装基类。它把 ISceneBehavior 的生命周期接到 Node 上:
OnLoadAsyncOnEnterAsyncOnPauseAsyncOnResumeAsyncOnExitAsyncOnUnloadAsync
如果 owner 还实现了 IScene,这些阶段会继续转发到业务节点;如果没有实现 IScene,默认 behavior 仍会处理 Godot 节点的
process 开关和 QueueFreeX() 释放。
SceneBehaviorFactory
自动包装的选择规则来自当前实现:
Node2D->Node2DSceneBehaviorNode3D->Node3DSceneBehaviorControl->ControlSceneBehavior- 其他
Node->GenericSceneBehavior
这意味着 Godot runtime 确实能“自动给节点补一个 behavior”,但它不会替你补项目侧 router、root 或 registry。
最小接入路径
推荐按下面顺序接入。
1. 继续在项目层保留自己的 router
GFramework.Godot 当前没有 GodotSceneRouter 类型。消费者项目的实际做法,是在项目层继承
GFramework.Game.Scene.SceneRouterBase。
ai-libs/CoreGrid 的 router 就是这样:
using global::CoreGrid.global;
using LoggingTransitionHandler = GFramework.Game.Scene.Handler.LoggingTransitionHandler;
namespace CoreGrid.scripts.core.scene;
public partial class SceneRouter : SceneRouterBase
{
[GetUtility] private IGodotSceneRegistry _sceneRegistry = null!;
public Node? SceneRoot => Root as Node;
protected override void RegisterHandlers()
{
__InjectContextBindings_Generated();
RegisterHandler(new LoggingTransitionHandler());
RegisterAroundHandler(
new SceneTransitionAnimationHandler(() => SceneTransitionManager.Instance!, _sceneRegistry.GetAll()));
}
}
这里可以看到,Godot 适配点在 factory / registry / root / transition handler 上,而 router 仍然是项目类。
2. 注册 IGodotSceneRegistry 与 ISceneFactory
最小 wiring 需要把 registry 和 factory 装进架构:
using GFramework.Game.Abstractions.Scene;
using GFramework.Godot.Scene;
using Godot;
public sealed class GameSceneRegistry : GodotSceneRegistry
{
public GameSceneRegistry()
{
Register(nameof(SceneKey.MainMenu), GD.Load<PackedScene>("res://scenes/main_menu.tscn"));
Register(nameof(SceneKey.Gameplay), GD.Load<PackedScene>("res://scenes/gameplay.tscn"));
}
}
architecture.RegisterUtility<IGodotSceneRegistry>(new GameSceneRegistry());
architecture.RegisterUtility<ISceneFactory>(new GodotSceneFactory());
architecture.RegisterSystem(new SceneRouter());
项目用什么 key 类型、资源目录或配置表都可以,但最终要能落到 sceneKey -> PackedScene。
3. 提供 ISceneRoot
SceneRouterBase 只负责切换编排,真正把场景节点挂到 Godot 场景树的是项目自己的 ISceneRoot。
CoreGrid 的 SceneRoot 当前做了两件关键事:
- 在
_Ready()时调用_sceneRouter.BindRoot(this) - 在
AddScene/RemoveScene里把scene.Original当作Node挂入或移出树
最小形态可以写成:
public sealed class SceneRoot : Node2D, ISceneRoot
{
[GetSystem] private ISceneRouter _sceneRouter = null!;
public override void _Ready()
{
__InjectContextBindings_Generated();
_sceneRouter.BindRoot(this);
}
public void AddScene(ISceneBehavior scene)
{
if (scene.Original is not Node node)
throw new InvalidOperationException("SceneBehavior must inherit Godot Node.");
if (node.GetParent() == null)
AddChild(node);
}
public void RemoveScene(ISceneBehavior scene)
{
if (scene.Original is Node node && node.GetParent() == this)
RemoveChild(node);
}
}
4. 让场景节点提供 behavior
当前有两种可行方式。
方式 A:实现 ISceneBehaviorProvider
如果你想显式控制 behavior 类型,直接实现 GetScene():
public partial class GameplayRoot : Node2D, ISceneBehaviorProvider, IScene
{
private ISceneBehavior? _scene;
public ISceneBehavior GetScene()
{
return _scene ??= SceneBehaviorFactory.Create(this, nameof(SceneKey.Gameplay));
}
public ValueTask OnLoadAsync(ISceneEnterParam? param)
{
return ValueTask.CompletedTask;
}
public ValueTask OnEnterAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnPauseAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnResumeAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnExitAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnUnloadAsync()
{
return ValueTask.CompletedTask;
}
}
方式 B:用 [AutoScene] 让生成器补样板
当前更贴近真实消费者 wiring 的方式,是让 GFramework.Godot.SourceGenerators 生成 SceneKeyStr 和 GetScene():
using GFramework.Game.Abstractions.Scene;
using GFramework.Godot.SourceGenerators.Abstractions.UI;
using Godot;
[AutoScene(nameof(SceneKey.Gameplay))]
public partial class GameplayRoot : Node2D, ISceneBehaviorProvider, IScene
{
public ValueTask OnLoadAsync(ISceneEnterParam? param)
{
return ValueTask.CompletedTask;
}
public ValueTask OnEnterAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnPauseAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnResumeAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnExitAsync()
{
return ValueTask.CompletedTask;
}
public ValueTask OnUnloadAsync()
{
return ValueTask.CompletedTask;
}
}
生成器当前会补出与源码一致的 GetScene():
public ISceneBehavior GetScene()
{
return __autoSceneBehavior_Generated ??= SceneBehaviorFactory.Create(this, SceneKeyStr);
}
要注意两点:
[AutoScene]只生成方法和 key,不会替你自动补: ISceneBehaviorProviderIScene仍然是业务生命周期契约;不实现它时,默认 behavior 只会保留基础节点切换语义
5. 从业务代码发起导航
一旦 registry、factory、router、root 都装好,导航入口仍然是 ISceneRouter:
await sceneRouter.ReplaceAsync(nameof(SceneKey.MainMenu));
await sceneRouter.ReplaceAsync(nameof(SceneKey.Gameplay), new GameplayEnterParam());
await sceneRouter.PushAsync(nameof(SceneKey.PauseMenu));
await sceneRouter.PopAsync();
当前边界
没有 GodotSceneRouter
仓库当前不存在 GodotSceneRouter 类型。旧文档里把它写成默认入口是失真的;实际入口仍然是项目侧继承
SceneRouterBase 的 router。
没有自动注册所有场景
当前运行时只认识你注册进 IGodotSceneRegistry 的 PackedScene。它不会扫描目录、不会从脚本类型自动反推出注册表。
provider 是“优先路径”,不是“唯一路径”
GodotSceneFactory 会优先使用 ISceneBehaviorProvider,但没有 provider 时仍会按节点类型自动包装。这个行为和 UI 系统不同;
UI 工厂当前没有同等的自动回退。
root 仍然是项目职责
ISceneRoot 的实现决定:
- 节点挂到哪里
- 移除时如何释放
- 是否保留额外的当前视图引用
Godot runtime 不会替项目生成统一的 root 节点。