feat: add play / editor mode

This commit is contained in:
2026-10-22 01:25:53 +02:00
parent 3df0f89343
commit e5d37965a1
13 changed files with 304 additions and 22 deletions
+120
View File
@@ -7,7 +7,10 @@
#include <destrum/FS/AssetFS.h>
#include <destrum/Graphics/Resources/FontCache.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Scene/SceneManager.h>
#include <destrum/Serialization/SceneSerializer.h>
#include <destrum/Util/DeltaTime.h>
#include "imgui.h"
@@ -67,6 +70,8 @@ void App::init(const AppParams& params)
customInitStarted = true;
customInit();
playing = !params.startInEditMode;
SceneManager::GetInstance().SetPlaying(playing);
cleanedUp = false;
} catch (...) {
try {
@@ -179,6 +184,11 @@ if (!isRunning) break;
continue;
}
if (InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_F5))
{
playModeToggleRequested = true;
}
// Consume SDL events before updating the base camera so held and
// newly pressed inputs are applied in the same frame.
camera.Update(dt);
@@ -239,6 +249,14 @@ if (!isRunning) break;
imguiPass.endFrame();
}
if (playModeToggleRequested)
{
ZoneScopedN("Play Mode Toggle");
playModeToggleRequested = false;
applyPlayModeToggle();
accumulator = 0.0f;
}
{
ZoneScopedN("FixedUpdate");
@@ -406,9 +424,111 @@ void App::drawWindowsMenu()
ImGui::EndMenu();
}
drawPlayControls();
ImGui::EndMainMenuBar();
}
void App::drawPlayControls()
{
const char* state = playing ? "PLAYING" : "EDIT";
const char* action = playing ? "Stop (F5)" : "Play (F5)";
const ImGuiStyle& style = ImGui::GetStyle();
const float width = ImGui::CalcTextSize(state).x + ImGui::CalcTextSize(action).x +
style.ItemSpacing.x * 2.0f + style.FramePadding.x * 2.0f;
// Centered like Godot's run buttons, unless the menus already reach past the middle
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), (ImGui::GetWindowWidth() - width) * 0.5f));
ImGui::TextColored(playing ? ImVec4{0.4f, 0.9f, 0.4f, 1.0f} : ImVec4{0.7f, 0.7f, 0.7f, 1.0f}, "%s", state);
if (ImGui::MenuItem(action))
{
playModeToggleRequested = true;
}
}
void App::applyPlayModeToggle()
{
if (playing)
{
exitPlayMode();
}
else
{
enterPlayMode();
}
}
bool App::enterPlayMode()
{
auto& sceneManager = SceneManager::GetInstance();
if (sceneManager.GetActiveScenes().empty())
{
spdlog::error("Cannot enter play mode without an active scene");
return false;
}
const std::shared_ptr<Scene> scene = sceneManager.GetActiveScenes().front();
auto snapshot = SceneSerializer::Capture(*scene);
if (!snapshot)
{
spdlog::error("Cannot enter play mode, scene \"{}\" could not be saved", scene->GetName());
return false;
}
if (const GameObject* selected = scene->GetSelectedObject())
{
(*snapshot)["selected"] = selected->GetId();
}
// Play runs the scene as it would load from disk, so nothing the editor did to the live objects
// (started components, unsaved runtime state) leaks into the game
if (!SceneSerializer::LoadFromJson(*scene, *snapshot))
{
spdlog::error("Cannot enter play mode, scene \"{}\" did not load back", scene->GetName());
return false;
}
editSnapshot = std::move(snapshot);
editScene = scene;
playing = true;
sceneManager.SetPlaying(true);
spdlog::info("Entered play mode");
onPlayModeChanged(true);
return true;
}
void App::exitPlayMode()
{
auto& sceneManager = SceneManager::GetInstance();
playing = false;
sceneManager.SetPlaying(false);
InputManager::GetInstance().SetMouseCaptured(false);
const std::shared_ptr<Scene> scene = editScene.lock();
if (scene && editSnapshot)
{
const auto& scenes = sceneManager.GetScenes();
const auto it = std::find(scenes.begin(), scenes.end(), scene);
if (it != scenes.end() && (sceneManager.GetActiveScenes().empty() ||
sceneManager.GetActiveScenes().front() != scene))
{
sceneManager.SwitchScene(static_cast<int>(std::distance(scenes.begin(), it)));
}
if (!SceneSerializer::LoadFromJson(*scene, *editSnapshot))
{
spdlog::error("Could not restore scene \"{}\" to its state before play mode", scene->GetName());
}
}
else
{
spdlog::warn("The scene play mode started from no longer exists, nothing to restore");
}
editSnapshot.reset();
editScene.reset();
spdlog::info("Exited play mode");
onPlayModeChanged(false);
}
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
{
if (!ImGui::Begin("Physics", &m_DebugWindows.physics))