feat: Play-In-Editor (PIE) with in-memory scene snapshot
Editor toolbar gains Play/Stop (Ctrl+P) with UE semantics: - Play: serialize current scene to an in-memory YAML snapshot via new SceneSerializer::SerializeToString, switch display to kDisplayDirect (primary camera entity renders), unfreeze time (timeScale 0 -> 1) - Stop: DeserializeFromString atomically rebuilds the pre-play scene, back to kDisplayViewport (editor camera), refreeze time; selection cleared and gizmo drag reset since all entity handles dangle - SceneSerializer refactored: SerializeDoc/ParseRoot shared by file and memory variants; file Save/Load behavior unchanged - Running state disables gizmo + editor hotkeys in the viewport (input goes to game scripts), draws yellow PLAYING hint and a no-camera warning; File->Save/Load disabled while playing (UE behavior) Physics bodies/constraints/characters and script instances are reconciled automatically by the existing per-frame sync on restore.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "StatsPanel.h"
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/Core/Time.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -36,6 +37,8 @@ Editor::~Editor() = default;
|
||||
void Editor::Init(const EditorContext& ctx)
|
||||
{
|
||||
m_Ctx = ctx;
|
||||
// PIE 运行态指针:指向本对象成员(ctx 拷贝后补接,面板只读)
|
||||
m_Ctx.playState = &m_Playing;
|
||||
|
||||
m_Hierarchy = std::make_unique<HierarchyPanel>();
|
||||
m_Inspector = std::make_unique<InspectorPanel>();
|
||||
@@ -44,6 +47,60 @@ void Editor::Init(const EditorContext& ctx)
|
||||
m_AssetBrowser = std::make_unique<AssetBrowserPanel>();
|
||||
}
|
||||
|
||||
// ---- PIE(Play-In-Editor)----
|
||||
|
||||
// Play:当前场景 → 内存 YAML 快照 → 切游戏渲染(primary 相机实体)
|
||||
// → 解冻时间(物理/脚本/动画开始跑)。快照失败不进 Play(现场不动)
|
||||
void Editor::DoPlay()
|
||||
{
|
||||
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||
|
||||
m_PlaySnapshot = Serialization::SceneSerializer::SerializeToString(
|
||||
*m_Ctx.scene, *m_Ctx.assets, "PlaySnapshot");
|
||||
if (m_PlaySnapshot.empty())
|
||||
{
|
||||
ENGINE_ERROR("PIE: snapshot failed, play aborted");
|
||||
return;
|
||||
}
|
||||
|
||||
m_Playing = true;
|
||||
m_Ctx.renderer->SetDisplayMode(kDisplayDirect);
|
||||
Time::SetTimeScale(1.0f);
|
||||
ENGINE_INFO("PIE: playing (scene snapshot {} bytes)",
|
||||
m_PlaySnapshot.size());
|
||||
}
|
||||
|
||||
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
|
||||
// 换血后所有 Entity 句柄悬空:清选中(描边同步清)、Gizmo 拖拽态
|
||||
// 复位。物理 body/约束/角色由对账系统随实体重建/清理,无需干预。
|
||||
// 恢复失败(快照损坏,理论罕见)也必须回编辑态,现场留给用户
|
||||
// File→Load 或退出前 Save
|
||||
void Editor::DoStop()
|
||||
{
|
||||
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||
|
||||
std::string envPath;
|
||||
if (!Serialization::SceneSerializer::DeserializeFromString(
|
||||
*m_Ctx.scene, *m_Ctx.assets, m_PlaySnapshot, envPath))
|
||||
{
|
||||
ENGINE_ERROR("PIE: snapshot restore failed (scene is now the "
|
||||
"played-out state)");
|
||||
}
|
||||
m_PlaySnapshot.clear();
|
||||
|
||||
m_Playing = false;
|
||||
m_Ctx.renderer->SetDisplayMode(kDisplayViewport);
|
||||
Time::SetTimeScale(0.0f);
|
||||
SelectEntity(entt::null);
|
||||
m_Gizmo.dragging = false;
|
||||
ENGINE_INFO("PIE: stopped, editor scene restored");
|
||||
}
|
||||
|
||||
void Editor::TogglePlay()
|
||||
{
|
||||
m_Playing ? DoStop() : DoPlay();
|
||||
}
|
||||
|
||||
void Editor::Draw()
|
||||
{
|
||||
// ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等
|
||||
@@ -133,8 +190,47 @@ void Editor::DrawMenuBar()
|
||||
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"))
|
||||
{
|
||||
// 运行态禁存取:Save 会把"玩出来的状态"当编辑结果落盘,
|
||||
// Load 会撕掉快照恢复链(UE 运行态同样禁)
|
||||
ImGui::BeginDisabled(m_Playing);
|
||||
if (ImGui::MenuItem("Save Scene...", nullptr))
|
||||
{
|
||||
m_ShowSaveModal = true;
|
||||
@@ -143,6 +239,7 @@ void Editor::DrawMenuBar()
|
||||
{
|
||||
m_ShowLoadModal = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Quit", "Alt+F4"))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user