PIE: viewport-embedded playback with UE-style main toolbar

Play no longer switches to fullscreen direct rendering: PIE now keeps
the editor UI alive and renders the primary camera into the Viewport
panel (new SceneRenderer::SetGameView flag; kDisplayDirect stays a
manual fullscreen preview for later release form). Editor camera stops
being driven during PIE, and viewport click-pick is gated off while
playing (clicks are game input now).

The Play/Stop buttons move out of the menu bar into a dedicated main
toolbar row below it (centered, UE Main Toolbar semantics): green Play
when idle; Pause (yellow, highlighted while paused) + Stop (orange)
while playing. Ctrl+P hotkey kept. Stats "Pause Simulation" merged
into the toolbar Pause; viewport overlay shows PAUSED accordingly.
This commit is contained in:
2026-10-05 11:14:24 +08:00
parent 5ca5ac2a1e
commit 1a278d5945
10 changed files with 167 additions and 67 deletions
+111 -40
View File
@@ -40,8 +40,9 @@ Editor::~Editor() = default;
void Editor::Init(const EditorContext& ctx) void Editor::Init(const EditorContext& ctx)
{ {
m_Ctx = ctx; m_Ctx = ctx;
// PIE 运行态指针:指向本对象成员(ctx 拷贝后补接,面板只读) // PIE 运行/暂停态指针:指向本对象成员(ctx 拷贝后补接,面板只读)
m_Ctx.playState = &m_Playing; m_Ctx.playState = &m_Playing;
m_Ctx.pausedState = &m_Paused;
m_Hierarchy = std::make_unique<HierarchyPanel>(); m_Hierarchy = std::make_unique<HierarchyPanel>();
m_Inspector = std::make_unique<InspectorPanel>(); m_Inspector = std::make_unique<InspectorPanel>();
@@ -78,7 +79,9 @@ void Editor::Init(const EditorContext& ctx)
// ---- PIE(Play-In-Editor)---- // ---- PIE(Play-In-Editor)----
// Play:当前场景 → 内存 YAML 快照 → 切游戏渲染(primary 相机实体) // Play:当前场景 → 内存 YAML 快照 → 切 gameView(Viewport 面板内嵌
// primary 相机渲染,UE PIE 语义——编辑器 UI 保持可用,不切全屏;
// kDisplayDirect 全屏预览是后期发布形态,手动经 Stats 切换)
// → 解冻时间(物理/脚本/动画开始跑)。快照失败不进 Play(现场不动) // → 解冻时间(物理/脚本/动画开始跑)。快照失败不进 Play(现场不动)
void Editor::DoPlay() void Editor::DoPlay()
{ {
@@ -93,7 +96,9 @@ void Editor::DoPlay()
} }
m_Playing = true; m_Playing = true;
m_Ctx.renderer->SetDisplayMode(kDisplayDirect); m_Paused = false;
m_Gizmo.dragging = false; // 编辑态拖杆中点 Play:杆运行态不出,复位
m_Ctx.renderer->SetGameView(true);
Time::SetTimeScale(1.0f); Time::SetTimeScale(1.0f);
ENGINE_INFO("PIE: playing (scene snapshot {} bytes)", ENGINE_INFO("PIE: playing (scene snapshot {} bytes)",
m_PlaySnapshot.size()); m_PlaySnapshot.size());
@@ -117,7 +122,8 @@ void Editor::DoStop()
m_PlaySnapshot.clear(); m_PlaySnapshot.clear();
m_Playing = false; m_Playing = false;
m_Ctx.renderer->SetDisplayMode(kDisplayViewport); m_Paused = false;
m_Ctx.renderer->SetGameView(false);
Time::SetTimeScale(0.0f); Time::SetTimeScale(0.0f);
SelectEntity(entt::null); SelectEntity(entt::null);
m_Gizmo.dragging = false; m_Gizmo.dragging = false;
@@ -129,6 +135,14 @@ void Editor::TogglePlay()
m_Playing ? DoStop() : DoPlay(); m_Playing ? DoStop() : DoPlay();
} }
// PIE 暂停(UE Pause 语义):timeScale 0 ↔ 1。运行态才有意义
//(编辑器态本来就冻结,Pause 按钮不出现)
void Editor::TogglePause()
{
m_Paused = !m_Paused;
Time::SetTimeScale(m_Paused ? 0.0f : 1.0f);
}
void Editor::Draw() void Editor::Draw()
{ {
// ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等 // ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等
@@ -154,6 +168,7 @@ void Editor::Draw()
ImGui::PopStyleVar(3); ImGui::PopStyleVar(3);
DrawMenuBar(); DrawMenuBar();
DrawToolbar();
DrawDockSpace(); DrawDockSpace();
ImGui::End(); ImGui::End();
@@ -227,42 +242,6 @@ void Editor::DrawMenuBar()
return; return;
} }
// ---- PIE 工具栏(UE 主工具栏语义):Play/Stop 切换按钮 +
// Ctrl+P 全局热键。运行态按钮变 Stop(黄底提示在视口内)----
{
const ImGuiIO& io = ImGui::GetIO();
// Ctrl+P:文本输入中(改名/路径框)不触发,防打字误切 PIE
if (!m_Playing && io.KeyCtrl && !io.WantTextInput &&
ImGui::IsKeyPressed(ImGuiKey_P, false))
{
TogglePlay();
}
if (m_Playing)
{
ImGui::PushStyleColor(ImGuiCol_Button,
ImVec4(0.75f, 0.45f, 0.10f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.85f, 0.55f, 0.15f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.90f, 0.62f, 0.20f, 1.0f));
if (ImGui::SmallButton("Stop (Ctrl+P)"))
{
TogglePlay();
}
ImGui::PopStyleColor(3);
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.72f, 0.25f, 1.0f),
"PLAYING - editor input disabled");
}
else if (ImGui::SmallButton("Play (Ctrl+P)"))
{
TogglePlay();
}
ImGui::SameLine();
ImGui::Separator();
}
if (ImGui::BeginMenu("File")) if (ImGui::BeginMenu("File"))
{ {
// 运行态禁存取:Save 会把"玩出来的状态"当编辑结果落盘, // 运行态禁存取:Save 会把"玩出来的状态"当编辑结果落盘,
@@ -298,6 +277,98 @@ void Editor::DrawMenuBar()
ImGui::EndMenuBar(); ImGui::EndMenuBar();
} }
// 主工具栏(UE Main Toolbar 语义):菜单栏下方独立一行,Play 组水平
// 居中。编辑态 = [Play](绿);PIE 运行态 = [Pause][Stop](暂停高亮黄 /
// 停止橙红)。kDisplayDirect 全屏预览是发布形态预览,不在此处
void Editor::DrawToolbar()
{
const ImGuiIO& io = ImGui::GetIO();
// Ctrl+P:文本输入中(改名/路径框)不触发,防打字误切 PIE
if (!m_Playing && io.KeyCtrl && !io.WantTextInput &&
ImGui::IsKeyPressed(ImGuiKey_P, false))
{
TogglePlay();
}
// 按钮加大触感(工具栏行高 = 一个按钮高;DockSpace avail 自适应)
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 4.0f));
const ImGuiStyle& st = ImGui::GetStyle();
// Play 组宽度 → 水平居中(编辑态 1 钮 / 运行态 2 钮)
float groupW = 0.0f;
if (m_Playing)
{
groupW = ImGui::CalcTextSize("Pause").x + ImGui::CalcTextSize("Stop").x +
4.0f * st.FramePadding.x + st.ItemSpacing.x;
}
else
{
groupW = ImGui::CalcTextSize("Play").x + 2.0f * st.FramePadding.x;
}
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - groupW) * 0.5f);
if (m_Playing)
{
// Pause:暂停中亮黄常驻(UE 按下态),运行中暗黄
ImGui::PushStyleColor(ImGuiCol_Button,
m_Paused ? ImVec4(0.85f, 0.62f, 0.12f, 1.0f)
: ImVec4(0.55f, 0.42f, 0.10f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
m_Paused ? ImVec4(0.92f, 0.70f, 0.18f, 1.0f)
: ImVec4(0.65f, 0.50f, 0.14f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.95f, 0.76f, 0.24f, 1.0f));
if (ImGui::Button("Pause"))
{
TogglePause();
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip(m_Paused ? "Resume simulation"
: "Pause simulation");
}
ImGui::SameLine();
// Stop:橙红(沿用视口 PLAYING 提示色系)
ImGui::PushStyleColor(ImGuiCol_Button,
ImVec4(0.75f, 0.45f, 0.10f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.85f, 0.55f, 0.15f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.90f, 0.62f, 0.20f, 1.0f));
if (ImGui::Button("Stop"))
{
TogglePlay();
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Stop and restore editor scene (Ctrl+P)");
}
}
else
{
// Play:绿(UE Play 按钮语义)
ImGui::PushStyleColor(ImGuiCol_Button,
ImVec4(0.22f, 0.48f, 0.26f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.28f, 0.58f, 0.32f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(0.34f, 0.66f, 0.38f, 1.0f));
if (ImGui::Button("Play"))
{
TogglePlay();
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Play in editor (Ctrl+P)");
}
}
ImGui::PopStyleVar();
}
void Editor::DrawDockSpace() void Editor::DrawDockSpace()
{ {
const ImGuiID dockspaceId = ImGui::GetID("EditorDockSpace"); const ImGuiID dockspaceId = ImGui::GetID("EditorDockSpace");
+9 -3
View File
@@ -57,12 +57,16 @@ public:
// 请求退出(弹确认框;窗口关闭事件走这里) // 请求退出(弹确认框;窗口关闭事件走这里)
void RequestQuit() { m_ShowQuitConfirm = true; } void RequestQuit() { m_ShowQuitConfirm = true; }
// ---- PIE(Play-In-Editor,UE 工具栏语义)---- // ---- PIE(Play-In-Editor,UE 主工具栏语义)----
bool IsPlaying() const { return m_Playing; } bool IsPlaying() const { return m_Playing; }
// Play:内存快照当前场景 → 切游戏渲染(primary 相机)→ 解冻时间; bool IsPaused() const { return m_Paused; }
// Play:内存快照当前场景 → 切 gameView(Viewport 内嵌 primary 相机
// 渲染,UE PIE 语义;不切全屏)→ 解冻时间;
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。 // Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
// 编辑器态默认 timeScale=0(物理冻结 = UE 语义),Play 解冻 // 编辑器态默认 timeScale=0(物理冻结 = UE 语义),Play 解冻
void TogglePlay(); void TogglePlay();
// PIE 暂停切换(UE Pause 语义):timeScale 0 ↔ 1
void TogglePause();
private: private:
void DoPlay(); // TogglePlay 的 Play 半程(快照 + 切渲染 + 解冻) void DoPlay(); // TogglePlay 的 Play 半程(快照 + 切渲染 + 解冻)
@@ -86,7 +90,8 @@ private:
entt::entity GetSelected() const { return m_Selected; } entt::entity GetSelected() const { return m_Selected; }
private: private:
void DrawMenuBar(); // host 窗口内顶栏 void DrawMenuBar(); // host 窗口内顶栏(File/View 菜单)
void DrawToolbar(); // 菜单栏下方主工具栏(Play/Pause/Stop 居中)
void DrawDockSpace(); // 全屏 host + dockspace + 默认布局 void DrawDockSpace(); // 全屏 host + dockspace + 默认布局
void DrawQuitModal(); // 退出确认模态框 void DrawQuitModal(); // 退出确认模态框
void DrawSceneModals(); // 保存/加载路径模态框(共用一个路径输入) void DrawSceneModals(); // 保存/加载路径模态框(共用一个路径输入)
@@ -127,6 +132,7 @@ private:
// PIE 状态:Play 时场景 YAML 内存快照,Stop 时原子换血恢复 // PIE 状态:Play 时场景 YAML 内存快照,Stop 时原子换血恢复
bool m_Playing = false; bool m_Playing = false;
bool m_Paused = false; // PIE 暂停(timeScale=0;Stop 时复位)
std::string m_PlaySnapshot; std::string m_PlaySnapshot;
}; };
+2
View File
@@ -29,6 +29,8 @@ struct EditorContext {
// PIE 运行态(Play-In-Editor):Editor 持有,只读指针。 // PIE 运行态(Play-In-Editor):Editor 持有,只读指针。
// 面板门控用——运行态禁 Gizmo/编辑器热键,渲染源已是 primary 相机 // 面板门控用——运行态禁 Gizmo/编辑器热键,渲染源已是 primary 相机
const bool *playState = nullptr; const bool *playState = nullptr;
// PIE 暂停态(timeScale=0):Editor 持有,只读指针(视口 PAUSED 提示)
const bool *pausedState = nullptr;
}; };
#endif //HELLOVULKAN_EDITORCONTEXT_H #endif //HELLOVULKAN_EDITORCONTEXT_H
+15 -14
View File
@@ -25,6 +25,9 @@ void StatsPanel::Draw(const EditorContext& ctx)
// ---- View:看哪里(渲染目标)+ 相机位姿(跟随渲染源分派)---- // ---- View:看哪里(渲染目标)+ 相机位姿(跟随渲染源分派)----
if (ImGui::CollapsingHeader("View", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("View", ImGuiTreeNodeFlags_DefaultOpen))
{ {
// 渲染源分派与 SceneRenderer::BuildFrame 同规则:PIE 内嵌播放
// 或 kDisplayDirect 全屏预览 = primary 相机,否则编辑器相机
const bool playing = ctx.playState && *ctx.playState;
static const char* displayTarget[] = { static const char* displayTarget[] = {
"Fullscreen", "Fullscreen",
"Viewport", "Viewport",
@@ -35,13 +38,7 @@ void StatsPanel::Draw(const EditorContext& ctx)
{ {
renderer.SetDisplayMode(mode); renderer.SetDisplayMode(mode);
} }
if (renderer.GetDisplayMode() == kDisplayViewport && ctx.editorCamera) if ((playing || renderer.GetDisplayMode() == kDisplayDirect))
{
const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition();
ImGui::Text("Editor Camera Pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
}
else
{ {
Engine::ECS::TransformComponent* camT = nullptr; Engine::ECS::TransformComponent* camT = nullptr;
if (ctx.scene->GetPrimaryCamera(&camT) != nullptr && camT != nullptr) if (ctx.scene->GetPrimaryCamera(&camT) != nullptr && camT != nullptr)
@@ -56,17 +53,21 @@ void StatsPanel::Draw(const EditorContext& ctx)
ImGui::TextDisabled("(no primary camera)"); ImGui::TextDisabled("(no primary camera)");
} }
} }
else if (ctx.editorCamera)
{
const glm::vec3& pos = ctx.editorCamera->GetTransform().GetPosition();
ImGui::Text("Editor Camera Pos");
ImGui::Text("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
}
else
{
ImGui::TextDisabled("(no camera)");
}
} }
// ---- Scene:模拟控制 + 场景级设置 ---- // ---- Scene:模拟控制 + 场景级设置。PIE 暂停在主工具栏(UE 语义)----
if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
{ {
// 暂停模拟(引擎 Time 门面:动画/物理统一冻结)
bool paused = Time::TimeScale() == 0.0f;
if (ImGui::Checkbox("Pause Simulation", &paused))
{
Time::SetTimeScale(paused ? 0.0f : 1.0f);
}
ImGui::ColorEdit4("Clear Color", ImGui::ColorEdit4("Clear Color",
glm::value_ptr(ctx.scene->GetSettings().ClearColor)); glm::value_ptr(ctx.scene->GetSettings().ClearColor));
} }
+11 -6
View File
@@ -69,12 +69,14 @@ namespace
// 视图相机分派(必须与 SceneRenderer::BuildFrame 渲染源同一规则, // 视图相机分派(必须与 SceneRenderer::BuildFrame 渲染源同一规则,
// 否则 Gizmo 杆与画面错位):编辑器形态 = EditorCamera(Scene view // 否则 Gizmo 杆与画面错位):编辑器形态 = EditorCamera(Scene view
// 专用,与相机实体无关);游戏形态 = primary 相机实体。 // 专用,与相机实体无关);游戏形态(kDisplayDirect 全屏预览或 PIE
// 内嵌播放)= primary 相机实体。
// 返回 nullptr = 游戏形态且无 primary 相机(本帧无 Gizmo) // 返回 nullptr = 游戏形态且无 primary 相机(本帧无 Gizmo)
Engine::Camera* ViewCamera(const EditorContext& ctx, Engine::Camera* ViewCamera(const EditorContext& ctx,
ECS::TransformComponent*& outT) ECS::TransformComponent*& outT)
{ {
if (ctx.renderer->GetDisplayMode() == kDisplayViewport) const bool playing = ctx.playState && *ctx.playState;
if (!playing && ctx.renderer->GetDisplayMode() == kDisplayViewport)
{ {
outT = &ctx.editorCamera->GetTransform(); outT = &ctx.editorCamera->GetTransform();
return &ctx.editorCamera->GetController(); return &ctx.editorCamera->GetController();
@@ -281,12 +283,14 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
} }
// Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行) // Gizmo 状态提示(左上角 drawlist 叠加,原因同下:不占布局行)
// PIE 运行态 = 黄色 PLAYING 提示替代编辑状态 // PIE 运行态 = 黄色 PLAYING/PAUSED 提示替代编辑状态
{ {
char tip[64]; char tip[64];
if (playing) { if (playing) {
const bool paused = ctx.pausedState && *ctx.pausedState;
std::snprintf(tip, sizeof(tip), std::snprintf(tip, sizeof(tip),
"PLAYING - Ctrl+P to stop"); paused ? "PAUSED - Ctrl+P to stop"
: "PLAYING - Ctrl+P to stop");
} else { } else {
std::snprintf(tip, sizeof(tip), std::snprintf(tip, sizeof(tip),
"Space: %s [T] Snap: Ctrl", "Space: %s [T] Snap: Ctrl",
@@ -335,8 +339,9 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
// 点击拾取:单击 Image 且未命中 UI 遮挡(IsItemHovered 已含)。 // 点击拾取:单击 Image 且未命中 UI 遮挡(IsItemHovered 已含)。
// 抓杆时不出 pick(IsOver 悬停杆/IsUsing 拖拽中)——否则按在 // 抓杆时不出 pick(IsOver 悬停杆/IsUsing 拖拽中)——否则按在
// 操纵杆上的点击会穿透发拾取请求 // 操纵杆上的点击会穿透发拾取请求。PIE 运行态不出拾取:
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && // 点击 = 游戏输入(UE Play 语义,编辑操作冻结)
if (!playing && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) &&
!ImGuizmo::IsOver() && !ImGuizmo::IsUsing()) { !ImGuizmo::IsOver() && !ImGuizmo::IsUsing()) {
RequestPickAt(ctx, size); RequestPickAt(ctx, size);
} }
+4 -1
View File
@@ -65,8 +65,11 @@ namespace
{ {
// 编辑器形态:驱动编辑器相机(RMB 飞行 / MMB 焦点环绕 / // 编辑器形态:驱动编辑器相机(RMB 飞行 / MMB 焦点环绕 /
// 滚轮推拉)。暂停(timeScale=0)时仍可飞行——unscaled dt。 // 滚轮推拉)。暂停(timeScale=0)时仍可飞行——unscaled dt。
// PIE 内嵌播放:渲染源已切 primary 相机,编辑器相机停驱
//(否则与游戏脚本抢 WASD,Stop 后视角也被偷跑)。
// 游戏形态:相机实体由游戏逻辑驱动(本沙盒无逻辑 = 静止) // 游戏形态:相机实体由游戏逻辑驱动(本沙盒无逻辑 = 静止)
if (GetRenderer().GetDisplayMode() == kDisplayViewport) if (GetRenderer().GetDisplayMode() == kDisplayViewport &&
!m_Editor->IsPlaying())
{ {
GetRenderer().GetEditorCamera().Update(GetWindow(), GetRenderer().GetEditorCamera().Update(GetWindow(),
frameTime.UnscaledDt()); frameTime.UnscaledDt());
@@ -142,6 +142,7 @@ namespace Engine {
// ---- 显示模式(Editor 视口 ↔ 全屏直出;帧首同步生效)---- // ---- 显示模式(Editor 视口 ↔ 全屏直出;帧首同步生效)----
int GetDisplayMode() const { return m_DesiredDisplayMode; } int GetDisplayMode() const { return m_DesiredDisplayMode; }
void SetDisplayMode(int mode); // cpp 内定义(SceneRenderer 前置声明) void SetDisplayMode(int mode); // cpp 内定义(SceneRenderer 前置声明)
void SetGameView(bool gameView); // PIE 内嵌播放(cpp 转发 SceneRenderer)
// 编辑器相机(Scene view 恒用,与场景相机实体无关)。转发 // 编辑器相机(Scene view 恒用,与场景相机实体无关)。转发
// SceneRenderer 持有的实例:Application 驱动 Update,面板读写位姿 // SceneRenderer 持有的实例:Application 驱动 Update,面板读写位姿
EditorCamera& GetEditorCamera(); // cpp 内定义(SceneRenderer 完整类型) EditorCamera& GetEditorCamera(); // cpp 内定义(SceneRenderer 完整类型)
@@ -66,6 +66,10 @@ namespace Engine {
void SetLodEnabled(bool b) { m_LodEnabled = b; } void SetLodEnabled(bool b) { m_LodEnabled = b; }
void SetDebugView(int v) { m_DebugView = v; } void SetDebugView(int v) { m_DebugView = v; }
void SetDisplayMode(int m) { m_DisplayMode = m; } // kDisplayDirect / kDisplayViewport void SetDisplayMode(int m) { m_DisplayMode = m; } // kDisplayDirect / kDisplayViewport
// PIE 内嵌播放(UE Play-In-Editor 语义):viewport 模式下也用
// primary 相机渲染进 sceneTex(Play 后 Scene view 显示游戏画面,
// 编辑器 UI 保持可用)。kDisplayDirect 恒为游戏形态不受此影响
void SetGameView(bool g) { m_GameView = g; }
// 编辑器相机(视口 UI 状态,SceneRenderer 持有 = 渲染源在此分派); // 编辑器相机(视口 UI 状态,SceneRenderer 持有 = 渲染源在此分派);
// Application 驱动 Update,ViewportPanel/Inspector 经 Renderer 门面访问 // Application 驱动 Update,ViewportPanel/Inspector 经 Renderer 门面访问
EditorCamera& GetEditorCamera() { return m_EditorCamera; } EditorCamera& GetEditorCamera() { return m_EditorCamera; }
@@ -171,6 +175,7 @@ namespace Engine {
bool m_LodEnabled = true; // 关 = 全部强制 LOD0(选级跳过) bool m_LodEnabled = true; // 关 = 全部强制 LOD0(选级跳过)
int m_DebugView = 0; int m_DebugView = 0;
int m_DisplayMode = kDisplayViewport; // Editor 视口为默认形态 int m_DisplayMode = kDisplayViewport; // Editor 视口为默认形态
bool m_GameView = false; // PIE 内嵌播放(Editor Play/Stop 驱动)
}; };
} // namespace Engine } // namespace Engine
+5
View File
@@ -234,6 +234,11 @@ namespace Engine
m_SceneRenderer->SetDisplayMode(mode); m_SceneRenderer->SetDisplayMode(mode);
} }
void Renderer::SetGameView(bool gameView)
{
m_SceneRenderer->SetGameView(gameView);
}
EditorCamera& Renderer::GetEditorCamera() EditorCamera& Renderer::GetEditorCamera()
{ {
return m_SceneRenderer->GetEditorCamera(); return m_SceneRenderer->GetEditorCamera();
+4 -3
View File
@@ -269,12 +269,13 @@ namespace Engine
ubo.prevView = m_LastView; ubo.prevView = m_LastView;
ubo.prevProj = m_LastProjNoJitter; ubo.prevProj = m_LastProjNoJitter;
// ---- 1. 相机:渲染源按显示模式分派(UE 语义)。编辑器形态 // ---- 1. 相机:渲染源按形态分派(UE 语义)。编辑器形态
// Scene view 恒用编辑器相机(视口 UI 状态,空场景可用); // Scene view 恒用编辑器相机(视口 UI 状态,空场景可用);
// 游戏形态用场景 primary 相机实体(游戏逻辑所有)---- // 游戏形态(kDisplayDirect 全屏预览或 PIE 内嵌播放)用
// 场景 primary 相机实体(游戏逻辑所有)----
ECS::TransformComponent* camTransform = nullptr; ECS::TransformComponent* camTransform = nullptr;
const Camera* camera = nullptr; const Camera* camera = nullptr;
if (m_DisplayMode == kDisplayViewport) { if (m_DisplayMode == kDisplayViewport && !m_GameView) {
camTransform = &m_EditorCamera.GetTransform(); camTransform = &m_EditorCamera.GetTransform();
camera = &m_EditorCamera.GetController(); camera = &m_EditorCamera.GetController();
} else if (auto* cameraComp = scene.GetPrimaryCamera(&camTransform)) { } else if (auto* cameraComp = scene.GetPrimaryCamera(&camTransform)) {