Skip to content

场景管理

场景就是你的游戏世界——所有东西都在里面活着、动着。

生命周期

一个场景从生到死会经历这些阶段:

创建 → init → update(每帧) → destroy → 销毁

出生:

cpp
auto scene = std::make_unique<Shit::Scene>("level1");
scene->init();

init() 帮你注册了三个默认系统:BehaviorSystem(跑脚本)、RenderSystem(画游戏世界)、UIRenderSystem(画 UI)。你要有自己的系统,就在这里注册。

💡 v1.3+scene->init() 现在是可选的。SceneManagerLoadScene 时若检测到场景还没有任何系统,会自动调用 init()——忘记调也不会得到空场景。init() 本身幂等,手动调用 + 自动调用不会重复注册系统。自定义 init() 覆写仍需调用父类 Shit::Scene::init() 以获得默认系统。

如果你想在场景初始化时做点自己的事,继承它就行:

cpp
class MyScene : public Shit::Scene {
    using Shit::Scene::Scene;

    void init() override {
        Shit::Scene::init();  // 别忘了先调父类,否则没有系统跑
        // 加载地图、生成敌人、播放 BGM……
    }
};

💡 v1.3+ 组件与系统解耦:组件不再查询"哪个系统驱动我"。BehaviorRendererComponentRigidBody2D 等组件挂载时通过 Scene 广播给所有系统,由系统用 dynamic_cast 认领自己关心的类型。因此先加组件、后注册系统也能正确挂接——系统注册时会重扫场景中未注册的组件。新增自定义系统时覆写 System::onComponentAttached/onComponentDetached 即可认领对应组件。

场景管理

SceneManager 持有当前活跃场景(单一场景模型,与 Unity/Godot 一致)。切换场景即销毁旧的、加载新的:

cpp
// 加载主菜单
Shit::SceneManager::LoadScene(std::move(menuScene));

// 玩家点击"开始游戏":切换场景(旧场景销毁)
Shit::SceneManager::LoadScene(std::move(gameScene));

LoadScene 同帧生效,会自动 init() 未初始化的场景。

⚠️ update 期间的 LoadScene 延迟生效:若在场景 update 的某帧内(如 Behavior::onUpdate、按钮回调里)调用 LoadScene,切换会推迟到本帧 update 结束后才执行——防止当前场景在更新中途自毁导致 use-after-free。行为上仍是"同帧完成",只是不中断正在执行的更新循环。

暂停

暂停不是场景操作——用全局暂停标志冻结游戏逻辑,UI 叠层照常响应:

cpp
// 游戏中按 ESC:暂停
Shit::Game::SetPaused(true);

// 打开一个 Canvas 当暂停菜单(UI 独立于游戏渲染,叠在游戏之上)

// 恢复
Shit::Game::SetPaused(false);

暂停时:

  • BehaviorSystem 冻结 onUpdateonStart 仍执行)
  • PhysicsSystem2D 冻结物理步进
  • RenderSystem / UIRenderSystem 照常——画面冻结在暂停瞬间,UI 菜单可交互

实际场景

主菜单 → 游戏中 → 暂停 → 恢复

cpp
auto menu = std::make_unique<MenuScene>("menu");
SceneManager::LoadScene(std::move(menu));
Game::Run();

// 玩家点了"开始":
SceneManager::LoadScene(std::make_unique<GameScene>("game"));

// 游戏中按 ESC:
Game::SetPaused(true);                     // 冻结游戏
// 显示暂停菜单 Canvas(UIRenderSystem 绘制在游戏之上)

// 恢复:
Game::SetPaused(false);                    // 游戏继续

系统(System)

系统按优先级每帧统一更新。优先级由 System 构造函数的参数决定,数字越小越先跑:

cpp
scene->registerSystem<BehaviorSystem>();   // 默认注册,优先级 0
scene->registerSystem<RenderSystem>();      // 默认注册,优先级 100
scene->registerSystem<UIRenderSystem>();    // 默认注册,优先级 200
scene->registerSystem<MyCustomSystem>();     // 你自己的系统

顺序是:BehaviorSystem(0)→ 你的系统 → RenderSystem(100)→ UIRenderSystem(200)。逻辑先跑、游戏世界渲染、UI 叠在最上面。

写一个自己的系统

继承 System,重写 updatedestroy

cpp
class PhysicsSystem : public Shit::System {
    using Shit::System::System;

    void update() override {
        // 每帧遍历场景对象,更新物理
        for (auto& obj : getScene()->getGameObjects()) {
            auto* physics = obj->getComponent<PhysicsComponent>();
            if (physics) physics->tick();
        }
    }

    void destroy() override {
        // 清理资源
    }
};

延迟操作

不要在迭代过程中增删对象——会崩。ShitEngine 把操作推迟到帧末统一处理:

cpp
scene->addGameObject(std::move(obj));       // 排入待添加队列
scene->removeGameObject(objPtr);            // 标记为待销毁(按指针)
scene->removeGameObjectByName("enemy");     // 按名字标记为待销毁
scene->unregisterSystem<T>();               // 排入待移除队列

这些操作不会立即生效,但在 Scene::update() 结尾会被统一处理。对你透明,对迭代器安全。

查询系统

已注册的系统可以随时查询:

cpp
auto* behaviorSys = scene->getSystem<Shit::BehaviorSystem>();
bool has = scene->hasSystem<Shit::RenderSystem>();

💬 评论