- 补充 Scene 与 UI 入口对配置系统正式边界页的指引 - 明确 oneOf、anyOf 与非 false additionalProperties 不属于默认采用路径 - 更新 Godot storage 入口对 VS Code 工具辅助层与 raw YAML 回退路径的说明
8.9 KiB
title, description
| title | description |
|---|---|
| 场景系统 | 说明 GFramework.Game 场景路由的当前入口、项目侧接入职责与扩展边界。 |
场景系统
GFramework.Game 的场景系统是“路由基类 + 场景契约 + 过渡管线”的组合,不是替你包办注册表、节点树和引擎对象装配的
一体化方案。
框架当前负责的是:
- 场景栈管理
Load -> Enter -> Pause -> Resume -> Exit -> Unload生命周期顺序- 路由守卫与过渡处理器执行时机
SceneRouterBase这一层的默认切换编排
项目或引擎适配层仍然需要自己提供:
ISceneFactoryISceneRoot- 具体的
ISceneBehavior/IScene - 场景键和资源、节点、预制体之间的映射关系
如果你把它理解为“可复用的场景路由底座”而不是“现成的完整场景框架”,后续接法会更贴近源码。
当前公开入口
IScene
业务场景生命周期契约,描述加载、进入、暂停、恢复、退出、卸载这六个阶段。
ISceneBehavior
路由器直接操作的运行时对象。它除了场景生命周期外,还携带:
KeyOriginalIsLoadedIsActiveIsTransitioning
如果你的引擎对象本身就能承担这些语义,可以直接实现 ISceneBehavior。如果你更想把业务逻辑放在纯 C# 场景类中,也可以由
项目侧行为包装器承载真正的引擎节点,再把业务场景逻辑委托出去。
ISceneRouter
当前公开的路由接口,重点入口是:
BindRoot(ISceneRoot root)ReplaceAsync(string sceneKey, ISceneEnterParam? param = null)PushAsync(string sceneKey, ISceneEnterParam? param = null)PopAsync()ClearAsync()Contains(string sceneKey)
SceneRouterBase
GFramework.Game 提供的默认实现基类。它会:
- 在
OnInit()中获取ISceneFactory - 通过
SemaphoreSlim串行化切换 - 调用守卫、过渡处理器和环绕处理器
- 维护场景栈与恢复顺序
通常项目不会直接修改框架里的 SceneRouterBase,而是在项目层继承它。
场景栈的真实语义
按当前实现,最常用的三个动作语义如下:
ReplaceAsync- 清空整个栈,再加载并进入目标场景。
PushAsync- 先检查守卫,再创建新场景,挂到
ISceneRoot,执行OnLoadAsync(),暂停当前栈顶,最后让新场景OnEnterAsync()。
- 先检查守卫,再创建新场景,挂到
PopAsync- 对栈顶执行离开检查,通过后退出并卸载它,再从
ISceneRoot移除,然后恢复新的栈顶。
- 对栈顶执行离开检查,通过后退出并卸载它,再从
当前还有两个容易混淆的点:
SceneRouterBase默认不允许同一个sceneKey在栈中重复存在;内部会先做Contains(sceneKey)检查- 框架不会替你实现“场景键 -> 具体场景实例”的注册逻辑;这仍然是
ISceneFactory或项目注册表的职责
最小接入路径
推荐按下面的顺序接入。
推荐目录与文件约定(项目侧)
场景系统的目录结构不由框架强制,但建议把“路由编排、实例创建、引擎挂载、业务场景”分开放置,避免后续把
SceneRouterBase 派生类写成巨型协调器。
Game/Scene/
GameSceneRouter.cs
GameSceneFactory.cs
SceneRoot.cs
Scenes/
GameplayScene.cs
PauseMenuScene.cs
Params/
GameplayEnterParam.cs
Registry/
SceneRegistry.cs
推荐约定如下:
GameSceneRouter.cs:项目侧 router,继承SceneRouterBase,只注册 guard、transition handler 和 around handlerGameSceneFactory.cs:实现ISceneFactory,负责sceneKey -> ISceneBehavior的映射与实例创建SceneRoot.cs:实现ISceneRoot,负责把行为对象对应的引擎节点挂到场景容器并移除Scenes/*:放具体业务场景、行为包装器或引擎节点包装类型Params/*:放实现ISceneEnterParam的进入参数,按业务场景拆分Registry/*:如果项目已有场景表或资源表,建议收口在这里,再由GameSceneFactory使用
最小 wiring 通常是:
architecture.RegisterUtility<ISceneFactory>(new GameSceneFactory());
architecture.RegisterSystem(new GameSceneRouter());
然后在 SceneRoot 的引擎生命周期就绪点调用 BindRoot(this)。如果项目已有不同的资源目录、节点层级或场景注册表,
保留原结构即可;只要最终能提供 ISceneFactory、ISceneRoot 和 ISceneBehavior,就不需要为了框架重排所有文件。
1. 准备项目自己的 router
using GFramework.Game.Scene;
using LoggingTransitionHandler = GFramework.Game.Scene.Handler.LoggingTransitionHandler;
public sealed class GameSceneRouter : SceneRouterBase
{
protected override void RegisterHandlers()
{
RegisterHandler(new LoggingTransitionHandler());
}
}
这一步只解决“切换流程怎么跑”,不解决“场景从哪来”。
2. 提供 ISceneFactory
SceneRouterBase 会在初始化阶段通过 GetUtility<ISceneFactory>() 获取工厂,因此项目必须先注册它。
工厂的职责通常是:
- 按
sceneKey找到项目自己的注册表、预制体或资源描述 - 创建或获取
ISceneBehavior - 决定行为对象如何包裹引擎节点与业务场景逻辑
如果项目里已经有场景注册表,也建议把它收口在 factory 内部,而不是让文档继续暗示框架自带统一注册中心。
3. 提供 ISceneRoot
ISceneRoot 只做两件事:
AddScene(ISceneBehavior scene)RemoveScene(ISceneBehavior scene)
也就是说,root 是“挂载/移除容器”,不是路由器本身。项目通常会在自己的 Godot 节点里实现
ISceneRoot,并在 _Ready() 时调用 BindRoot(this)。
4. 把 router 和 factory 装进架构
architecture.RegisterUtility<ISceneFactory>(new GameSceneFactory());
architecture.RegisterSystem(new GameSceneRouter());
如果你的项目还需要动画、黑幕或 loading 过渡,可以继续在 RegisterHandlers() 里补自己的处理器。
5. 在 root 就绪后绑定
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)
{
// 项目侧决定如何把 scene.Original 挂进引擎节点树
}
public void RemoveScene(ISceneBehavior scene)
{
// 项目侧决定如何移除并释放引擎对象
}
}
6. 从业务代码发起导航
await sceneRouter.ReplaceAsync(
"Gameplay",
new GameplayEnterParam
{
Seed = "new-game"
});
await sceneRouter.PushAsync("PauseMenu");
await sceneRouter.PopAsync();
扩展点
路由守卫
如果你要在进入或离开场景前做业务检查,实现 ISceneRouteGuard:
CanEnterAsync(string sceneKey, ISceneEnterParam? param)CanLeaveAsync(string sceneKey)
适合放:
- 未保存进度拦截
- 场景解锁条件检查
- 新手引导流程限制
过渡处理器
SceneRouterBase 公开了:
RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null)RegisterAroundHandler(ISceneAroundTransitionHandler handler, SceneTransitionHandlerOptions? options = null)
适合放:
- 日志
- 黑幕、淡入淡出或 loading 动画
- 切场前后的指标采集
如果你的项目已经有复杂引擎过渡逻辑,优先把这些逻辑放进 handler,而不是把 SceneRouterBase 派生类本身做成巨型协调器。
与旧写法的边界
下面这些说法不再适合作为默认接入指导:
- “框架会帮你直接注册和发现所有场景类型”
- “只要写一个
IScene就能自动接入所有引擎对象” - “场景系统本身自带统一注册表和完整项目结构”
当前更准确的理解是:
- 框架提供通用场景切换编排
- 项目提供 factory、root、资源映射和具体引擎装配
- 文档中的最小示例应优先说明职责边界,而不是继续堆叠大而全教程
配置系统边界提示
如果你的场景路由接线同时依赖 AI-First 配置系统,本页只负责说明场景宿主、路由和生命周期接法,不负责定义配置 schema 的正式支持边界。涉及 YAML 配置契约、组合关键字或编辑器辅助能力时,请回到 Game 配置系统 作为正式说明页。
默认采用路径之外的场景包括:
oneOf/anyOf- 非
false的additionalProperties - 依赖开放对象形状、形状合并或更复杂嵌套数组的 schema shape
这类复杂 shape 不应从场景接线页推断支持范围。VS Code 工具只是辅助编辑与预览层;如果遇到这些情况,应直接回到 raw YAML
和 schema 本体设计处理。