场景
Turian 场景可以被实例化为节点树(类似于 Godot 场景或 Unity 预制体)。场景管理器补充了另一半功能——像 Unity 的 SceneManager 一样将场景视为命名的、可寻址的单元,可以整体加载和卸载,支持叠加场景、持久对象、异步加载和生命周期事件。
引导场景来自你的项目设置(first_scene);从那里开始,你的游戏通过 engine.SceneManager 服务驱动场景切换。
从脚本中访问管理器
管理器作为服务注入,可从任何组件的 Frame 访问:
const engine = @import("engine");
pub const SceneDirector = struct {
pub const is_component = true;
// 在检查器中配置,非硬编码——参见"场景引用作为字段"
level_a: engine.TypedAssetRef(.scene) = .{},
level_b: engine.TypedAssetRef(.scene) = .{},
pub fn start(self: *SceneDirector, frame: engine.Frame) void {
const mgr = frame.service(engine.SceneManager) orelse return;
// 保持引导场景(摄像机/导演)在关卡切换时存活
if (mgr.getActiveScene()) |boot| mgr.setScenePersistent(boot, true);
mgr.requestLoad(self.level_a.guid(), .additive);
}
pub fn update(self: *SceneDirector, frame: engine.Frame) void {
const mgr = frame.service(engine.SceneManager) orelse return;
if (frame.input.wasKeyPressed(.num_2)) mgr.requestLoad(self.level_b.guid(), .single);
}
};
场景引用作为字段
与其在代码中粘贴场景 GUID,不如声明一个 engine.TypedAssetRef(.scene) 字段。检查器会为其渲染一个场景资源选择器(从资源浏览器拖动场景,或使用 ... 按钮从筛选为场景的列表中选择)。所选 GUID 保存到场景文件中,并在运行时还原到字段中,因此脚本使用 field.guid() 读取它:
next_scene: engine.TypedAssetRef(.scene) = .{},
pub fn update(self: *@This(), frame: engine.Frame) void {
const mgr = frame.service(engine.SceneManager) orelse return;
if (frame.input.wasKeyPressed(.space))
mgr.requestLoad(self.next_scene.guid(), .single);
}
这与网格和材质使用的类型化资源引用机制相同,专门针对场景——设计人员无需接触代码即可配置场景切换。
加载模式
| 模式 | 行为 |
|---|---|
.single |
卸载所有非持久场景,然后加载新场景——经典的"下一关"切换。 |
.additive |
在已加载的场景旁叠加加载新场景。 |
持久场景(DontDestroyOnLoad)
mgr.setScenePersistent(handle, true) 将场景标记为在 .single 加载时保持存活。将你的摄像机、HUD 和控制器放在一个持久的引导场景中,它们将在每次关卡切换后继续存在——相当于 Unity 的 DontDestroyOnLoad 功能。(单个节点也可以通过 markDontDestroyOnLoad 移动到专用的持久场景中。)
延迟请求
脚本使用 requestLoad / requestUnload 请求场景切换。这些请求在帧末尾应用,绝不会在更新过程中执行——因此脚本可以安全地卸载它正在运行的场景。(loadScene / unloadScene 也可用于即时的宿主端控制。)
跟踪与查询
const active = mgr.getActiveScene(); // ?SceneHandle
var buf: [engine.SCENE_MANAGER_MAX_SCENES]engine.SceneHandle = undefined;
const loaded = mgr.getLoadedScenes(&buf); // []SceneHandle
const here = mgr.findById(LEVEL_A); // ?SceneHandle
const ok = mgr.isLoaded(handle);
生命周期事件
订阅以在场景状态改变时接收通知:
mgr.subscribe(onSceneEvent, ctx); // 触发 loaded / unloaded / activated / deactivated
异步加载
大型场景可以在工作线程上加载,不会阻塞帧:
const h = try mgr.loadSceneAsync(io, id, .single);
// ... 继续渲染加载画面 ...
if (mgr.isReady(h)) _ = mgr.finishAsync(io, h, .single);
错误处理
同步加载返回类型化错误(NoLoader、LoadFailed、TooManyScenes);对于异步加载,可通过 mgr.getError(handle) 获取每个场景的原因。
示例
请参阅仓库中的 examples/scene-management/ 目录,获取一个完整可运行的演示,包含引导 + 持久引导 + 叠加加载 + 运行时切换。