// // Created by atdunbg on 2026/9/20. // // SandboxApp:基于 Application 框架的编辑器宿主程序。 // // main 的职责边界: // - 定义 Application 派生类并运行它 // - 编辑器形态驱动 EditorCamera(Scene view 专用相机,非场景对象) // - 异步环境加载、全局热键、输入归属门控 // - UI 全部委托 App/editor/ 的 docking Editor(本文件不再出现 // 任何 ImGui 控件) // // 场景内容(实体/组件)由编辑器 File 菜单 Save/Load 场景文件管理, // 相机实体属于场景数据、只在游戏形态参与渲染(UE 语义)。 // // 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理。 // #include #include #include // SetInputCapture(Application.h 只前置声明) #include #include #include #include #include #include // editor/game 编译期分离(HELLOVULKAN_EDITOR CMake option):editor 形态 // 挂接 docking Editor;game 形态 = Direct 直渲 + primary 相机,零编辑器代码 #ifdef HELLO_EDITOR #include "Editor.h" #include "EditorContext.h" #endif using namespace Engine; namespace { class SandboxApp final : public Application { public: SandboxApp() : Application("HelloVulkan", 1920, 1080) { } ~SandboxApp() override { } protected: // ---- 生命周期 ---- void OnInit() override { Renderer& renderer = GetRenderer(); #ifdef HELLO_EDITOR renderer.SetDisplayMode(kDisplayViewport); // ImGui 叠加层(框架只做管线桥接;UI 内容 = docking Editor) EnableImGui(); // Editor 注入:引擎门面 + 沙盒能力回调(环境异步切换等) InitEditor(); #else // game 形态:全窗口 Direct 直渲(Renderer 默认即 kDisplayDirect), // 相机由场景 primary camera 提供,无 UI。 // 游戏流:Stopped → Loading(OnUpdate 泵场景加载)→ Running GetGameFlow().RequestStart(); #endif } void OnUpdate(const FrameTime& frameTime) override { #ifdef HELLO_EDITOR // 编辑器形态:驱动编辑器相机(RMB 飞行 / MMB 焦点环绕 / // 滚轮推拉)。暂停(timeScale=0)时仍可飞行——unscaled dt。 // PIE 内嵌播放:渲染源已切 primary 相机,编辑器相机停驱 //(否则与游戏脚本抢 WASD,Stop 后视角也被偷跑)。 // 游戏形态:相机实体由游戏逻辑驱动(本沙盒无逻辑 = 静止) if (GetRenderer().GetDisplayMode() == kDisplayViewport && !m_Editor->IsPlaying()) { GetRenderer().GetEditorCamera().Update(frameTime.UnscaledDt()); } #else // Loading 泵(同步示例):场景文件存在则加载,不存在 = // 空场景照常进 Running(引擎冒烟路径)。加载完成后必须 // CompleteLoading,否则物理/脚本门控永远跳过(卡死 Loading) if (GetGameFlow().IsIn(GameFlowState::Loading)) { if (std::filesystem::exists(m_ScenePath)) { if (Serialization::SceneSerializer::Deserialize( GetScene(), GetAssets(), m_ScenePath)) { ENGINE_INFO("Game: scene loaded from {}", m_ScenePath); } else { ENGINE_ERROR("Game: scene load failed: {}", m_ScenePath); } } else { ENGINE_WARN("Game: scene file not found ({}), " "starting empty", m_ScenePath); } GetGameFlow().CompleteLoading(); } #endif } // ---- UI(全部委托 docking Editor;本文件零 ImGui 控件)---- void OnImGuiRender() override { #ifdef HELLO_EDITOR m_Editor->Draw(); #endif } // ---- 事件 ---- #ifdef HELLO_EDITOR void OnWindowClose(const Event& event) override { // 退出确认走 Editor 的模态框(ImGui 必须可见才有 UI) SetImGuiEnabled(true); m_Editor->RequestQuit(); } #endif void OnKeyPress(const Event& event) override { // 键码统一走 Input::Key(App 层零 GLFW 依赖) #ifdef HELLO_EDITOR if (event.key == static_cast(Input::Key::GraveAccent)) { SetImGuiEnabled(!GetImGuiEnabled()); } #endif } void OnWindowResize(const Event& event) override { ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); } private: #ifdef HELLO_EDITOR // Editor 注入:门面指针 + 反向能力回调(面板 → 沙盒) void InitEditor() { EditorContext ctx; ctx.renderer = &GetRenderer(); ctx.scene = &GetScene(); ctx.assets = &GetAssets(); ctx.app = this; ctx.flow = &GetGameFlow(); ctx.editorCamera = &GetRenderer().GetEditorCamera(); // 编辑器态默认冻结:GameFlow 构造即 timeScale=0(Stopped 语义), // timeScale 权威在 GameFlow,此处不再手设 m_Editor->Init(ctx); // 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词 //(全屏 dock host 让它恒真,输入全死)。改为 Editor 按帧 // 判定——Viewport hover/focus 拥有输入(Hazel/UE 语义); // UI 隐藏(反引号)时 Draw 不再执行,直接全放行 GetWindow().SetInputCapture( [this] { return GetImGuiEnabled() && m_Editor->BlockingKeyboard(); }, [this] { return GetImGuiEnabled() && m_Editor->BlockingMouse(); }); } private: std::unique_ptr m_Editor = std::make_unique(); #else // game 形态:启动场景路径(CWD = bin;编辑器 Save 的产物位置) const char* m_ScenePath = "assets/scenes/sandbox.scene"; #endif }; } int main() { Logger::Initialize(); { SandboxApp app; try { app.Run(); } catch (const std::exception& e) { ENGINE_FATAL("Exception: {}", e.what()); Logger::Shutdown(); return -1; } } // app 析构:成员(Device/Renderer/Window…)全部销毁完毕 Logger::Shutdown(); return 0; }