gewuyou 85f7c1707e docs(game): 同步场景与宿主入口配置边界
- 补充 Scene 与 UI 入口对配置系统正式边界页的指引

- 明确 oneOf、anyOf 与非 false additionalProperties 不属于默认采用路径

- 更新 Godot storage 入口对 VS Code 工具辅助层与 raw YAML 回退路径的说明
2026-04-30 13:25:29 +08:00

278 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 场景系统
description: 说明 GFramework.Game 场景路由的当前入口、项目侧接入职责与扩展边界。
---
# 场景系统
`GFramework.Game` 的场景系统是“路由基类 + 场景契约 + 过渡管线”的组合,不是替你包办注册表、节点树和引擎对象装配的
一体化方案。
框架当前负责的是:
- 场景栈管理
- `Load -> Enter -> Pause -> Resume -> Exit -> Unload` 生命周期顺序
- 路由守卫与过渡处理器执行时机
- `SceneRouterBase` 这一层的默认切换编排
项目或引擎适配层仍然需要自己提供:
- `ISceneFactory`
- `ISceneRoot`
- 具体的 `ISceneBehavior` / `IScene`
- 场景键和资源、节点、预制体之间的映射关系
如果你把它理解为“可复用的场景路由底座”而不是“现成的完整场景框架”,后续接法会更贴近源码。
## 当前公开入口
### `IScene`
业务场景生命周期契约,描述加载、进入、暂停、恢复、退出、卸载这六个阶段。
### `ISceneBehavior`
路由器直接操作的运行时对象。它除了场景生命周期外,还携带:
- `Key`
- `Original`
- `IsLoaded`
- `IsActive`
- `IsTransitioning`
如果你的引擎对象本身就能承担这些语义,可以直接实现 `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` 派生类写成巨型协调器。
```text
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 handler
- `GameSceneFactory.cs`:实现 `ISceneFactory`,负责 `sceneKey -> ISceneBehavior` 的映射与实例创建
- `SceneRoot.cs`:实现 `ISceneRoot`,负责把行为对象对应的引擎节点挂到场景容器并移除
- `Scenes/*`:放具体业务场景、行为包装器或引擎节点包装类型
- `Params/*`:放实现 `ISceneEnterParam` 的进入参数,按业务场景拆分
- `Registry/*`:如果项目已有场景表或资源表,建议收口在这里,再由 `GameSceneFactory` 使用
最小 wiring 通常是:
```csharp
architecture.RegisterUtility<ISceneFactory>(new GameSceneFactory());
architecture.RegisterSystem(new GameSceneRouter());
```
然后在 `SceneRoot` 的引擎生命周期就绪点调用 `BindRoot(this)`。如果项目已有不同的资源目录、节点层级或场景注册表,
保留原结构即可;只要最终能提供 `ISceneFactory``ISceneRoot``ISceneBehavior`,就不需要为了框架重排所有文件。
### 1. 准备项目自己的 router
```csharp
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 装进架构
```csharp
architecture.RegisterUtility<ISceneFactory>(new GameSceneFactory());
architecture.RegisterSystem(new GameSceneRouter());
```
如果你的项目还需要动画、黑幕或 loading 过渡,可以继续在 `RegisterHandlers()` 里补自己的处理器。
### 5. 在 root 就绪后绑定
```csharp
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. 从业务代码发起导航
```csharp
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 配置系统](./config-system.md) 作为正式说明页。
默认采用路径之外的场景包括:
- `oneOf` / `anyOf`
-`false``additionalProperties`
- 依赖开放对象形状、形状合并或更复杂嵌套数组的 schema shape
这类复杂 shape 不应从场景接线页推断支持范围。`VS Code` 工具只是辅助编辑与预览层;如果遇到这些情况,应直接回到 raw YAML
和 schema 本体设计处理。
## 推荐阅读
1. [Game 模块总览](./index.md)
2. [UI 系统](./ui.md)
3. [Game 抽象层说明](../abstractions/game-abstractions.md)
4. [API 参考](../api-reference/index.md)