mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
- 重写 game/scene 与 game/ui 专题页,按当前 router、factory、root、输入与暂停语义说明接入方式\n- 更新 documentation-governance-and-refresh 的 tracking 与 trace,记录 RP-006 与后续 source-generators 核对重点\n- 验证 docs 站点构建通过
225 lines
6.7 KiB
Markdown
225 lines
6.7 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` 或项目注册表的职责
|
||
|
||
## 最小接入路径
|
||
|
||
推荐按下面的顺序接入。
|
||
|
||
### 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 是“挂载/移除容器”,不是路由器本身。当前 `ai-libs/` 参考实现也是在项目自己的 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、资源映射和具体引擎装配
|
||
- 文档中的最小示例应优先说明职责边界,而不是继续堆叠大而全教程
|
||
|
||
## 推荐阅读
|
||
|
||
1. [game/index.md](./index.md)
|
||
2. [ui.md](./ui.md)
|
||
3. `GFramework.Game/README.md`
|
||
4. `GFramework.Game.Abstractions/README.md`
|