mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-09 10:19:00 +08:00
- 补充 Scene 与 UI 入口对配置系统正式边界页的指引 - 明确 oneOf、anyOf 与非 false additionalProperties 不属于默认采用路径 - 更新 Godot storage 入口对 VS Code 工具辅助层与 raw YAML 回退路径的说明
278 lines
8.9 KiB
Markdown
278 lines
8.9 KiB
Markdown
---
|
||
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)
|