feat: add pauzing of the running game

This commit is contained in:
2026-10-22 18:10:23 +02:00
parent e5d37965a1
commit dde2c7daf9
6 changed files with 69 additions and 10 deletions
+4 -1
View File
@@ -55,7 +55,9 @@ public:
virtual void onPlayModeChanged(bool) {} virtual void onPlayModeChanged(bool) {}
[[nodiscard]] bool isPlaying() const { return playing; } [[nodiscard]] bool isPlaying() const { return playing; }
[[nodiscard]] bool isPaused() const { return paused; }
void requestPlayModeToggle() { playModeToggleRequested = true; } void requestPlayModeToggle() { playModeToggleRequested = true; }
void setPaused(bool pause);
virtual void customWindowsMenu() {} virtual void customWindowsMenu() {}
@@ -75,7 +77,6 @@ protected:
bool cleanedUp{false}; bool cleanedUp{false};
bool cleaningUp{false}; bool cleaningUp{false};
bool customInitStarted{false}; bool customInitStarted{false};
bool gamePaused{false};
bool frameLimit{false}; bool frameLimit{false};
float frameTime{0.f}; float frameTime{0.f};
@@ -110,6 +111,8 @@ private:
void exitPlayMode(); void exitPlayMode();
bool playing{true}; bool playing{true};
bool paused{false};
bool mouseCapturedBeforePause{false};
bool playModeToggleRequested{false}; bool playModeToggleRequested{false};
std::optional<nlohmann::json> editSnapshot; std::optional<nlohmann::json> editSnapshot;
std::weak_ptr<Scene> editScene; std::weak_ptr<Scene> editScene;
@@ -20,6 +20,8 @@ public:
void SetPlaying(bool playing) { m_playing = playing; } void SetPlaying(bool playing) { m_playing = playing; }
[[nodiscard]] bool IsPlaying() const { return m_playing; } [[nodiscard]] bool IsPlaying() const { return m_playing; }
void SetPaused(bool paused) { m_paused = paused; }
[[nodiscard]] bool IsPaused() const { return m_paused; }
void Update(float dt); void Update(float dt);
void FixedUpdate(float dt); void FixedUpdate(float dt);
@@ -55,7 +57,11 @@ private:
std::vector<std::shared_ptr<Scene>> m_activeScenes; std::vector<std::shared_ptr<Scene>> m_activeScenes;
std::vector<std::shared_ptr<Scene>> m_scenes; std::vector<std::shared_ptr<Scene>> m_scenes;
[[nodiscard]] bool IsTicking() const { return m_playing && !m_paused; }
bool m_playing{true}; bool m_playing{true};
bool m_paused{false};
}; };
#endif //SCENEMANAGER_H #endif //SCENEMANAGER_H
+45 -3
View File
@@ -188,6 +188,10 @@ if (!isRunning) break;
{ {
playModeToggleRequested = true; playModeToggleRequested = true;
} }
if (playing && InputManager::GetInstance().WasKeyPressed(SDL_SCANCODE_F7))
{
setPaused(!paused);
}
// Consume SDL events before updating the base camera so held and // Consume SDL events before updating the base camera so held and
// newly pressed inputs are applied in the same frame. // newly pressed inputs are applied in the same frame.
@@ -431,21 +435,57 @@ void App::drawWindowsMenu()
void App::drawPlayControls() void App::drawPlayControls()
{ {
const char* state = playing ? "PLAYING" : "EDIT"; const char* state = !playing ? "EDIT" : paused ? "PAUSED" : "PLAYING";
const char* action = playing ? "Stop (F5)" : "Play (F5)"; const char* action = playing ? "Stop (F5)" : "Play (F5)";
const char* pauseAction = paused ? "Resume (F7)" : "Pause (F7)";
const ImGuiStyle& style = ImGui::GetStyle(); const ImGuiStyle& style = ImGui::GetStyle();
const float width = ImGui::CalcTextSize(state).x + ImGui::CalcTextSize(action).x + float width = ImGui::CalcTextSize(state).x + ImGui::CalcTextSize(action).x +
style.ItemSpacing.x * 2.0f + style.FramePadding.x * 2.0f; style.ItemSpacing.x * 2.0f + style.FramePadding.x * 2.0f;
if (playing)
{
width += ImGui::CalcTextSize(pauseAction).x + style.ItemSpacing.x + style.FramePadding.x * 2.0f;
}
// Centered like Godot's run buttons, unless the menus already reach past the middle // 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::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); const ImVec4 stateColor = !playing ? ImVec4{0.7f, 0.7f, 0.7f, 1.0f}
: paused ? ImVec4{0.95f, 0.75f, 0.3f, 1.0f}
: ImVec4{0.4f, 0.9f, 0.4f, 1.0f};
ImGui::TextColored(stateColor, "%s", state);
if (playing && ImGui::MenuItem(pauseAction))
{
setPaused(!paused);
}
if (ImGui::MenuItem(action)) if (ImGui::MenuItem(action))
{ {
playModeToggleRequested = true; playModeToggleRequested = true;
} }
} }
void App::setPaused(bool pause)
{
if (!playing || pause == paused)
{
return;
}
paused = pause;
SceneManager::GetInstance().SetPaused(paused);
// Hand the mouse to the editor while paused, give it back to the game on resume
auto& input = InputManager::GetInstance();
if (paused)
{
mouseCapturedBeforePause = input.IsMouseCaptured();
input.SetMouseCaptured(false);
}
else
{
input.SetMouseCaptured(mouseCapturedBeforePause);
}
spdlog::info(paused ? "Paused play mode" : "Resumed play mode");
}
void App::applyPlayModeToggle() void App::applyPlayModeToggle()
{ {
if (playing) if (playing)
@@ -500,7 +540,9 @@ void App::exitPlayMode()
{ {
auto& sceneManager = SceneManager::GetInstance(); auto& sceneManager = SceneManager::GetInstance();
playing = false; playing = false;
paused = false;
sceneManager.SetPlaying(false); sceneManager.SetPlaying(false);
sceneManager.SetPaused(false);
InputManager::GetInstance().SetMouseCaptured(false); InputManager::GetInstance().SetMouseCaptured(false);
const std::shared_ptr<Scene> scene = editScene.lock(); const std::shared_ptr<Scene> scene = editScene.lock();
+3 -3
View File
@@ -16,7 +16,7 @@ Scene& SceneManager::GetCurrentScene() const {
void SceneManager::Update(float dt) { void SceneManager::Update(float dt) {
for (const auto& scene : m_activeScenes) { for (const auto& scene : m_activeScenes) {
if (m_playing) { if (IsTicking()) {
scene->Update(dt); scene->Update(dt);
} else { } else {
scene->CommitPendingAdditions(); scene->CommitPendingAdditions();
@@ -25,7 +25,7 @@ void SceneManager::Update(float dt) {
} }
void SceneManager::FixedUpdate(float dt) { void SceneManager::FixedUpdate(float dt) {
if (!m_playing) { if (!IsTicking()) {
return; return;
} }
for (const auto& scene : m_activeScenes) { for (const auto& scene : m_activeScenes) {
@@ -34,7 +34,7 @@ void SceneManager::FixedUpdate(float dt) {
} }
void SceneManager::LateUpdate(float dt) { void SceneManager::LateUpdate(float dt) {
if (!m_playing) { if (!IsTicking()) {
return; return;
} }
for (const auto& scene : m_activeScenes) { for (const auto& scene : m_activeScenes) {
+2 -2
View File
@@ -83,7 +83,7 @@ void LightKeeper::updateViewMode()
auto& input = InputManager::GetInstance(); auto& input = InputManager::GetInstance();
FirstPersonController* player = mainScene.playerController; FirstPersonController* player = mainScene.playerController;
if (isPlaying() && player != nullptr && input.WasKeyPressed(SDL_SCANCODE_F6)) if (isPlaying() && !isPaused() && player != nullptr && input.WasKeyPressed(SDL_SCANCODE_F6))
{ {
player->SetPossessed(!player->IsPossessed()); player->SetPossessed(!player->IsPossessed());
} }
@@ -100,7 +100,7 @@ void LightKeeper::updateViewMode()
bool LightKeeper::isPlayerViewActive() const bool LightKeeper::isPlayerViewActive() const
{ {
return isPlaying() && mainScene.playerController != nullptr && mainScene.playerController->IsPossessed(); return isPlaying() && !isPaused() && mainScene.playerController != nullptr && mainScene.playerController->IsPossessed();
} }
CameraComponent* LightKeeper::getSceneCamera() const CameraComponent* LightKeeper::getSceneCamera() const
+8
View File
@@ -304,6 +304,14 @@ namespace {
check(counter->updates == 1 && counter->fixedUpdates == 1 && counter->lateUpdates == 1, check(counter->updates == 1 && counter->fixedUpdates == 1 && counter->lateUpdates == 1,
"play mode must tick components"); "play mode must tick components");
sceneManager.SetPaused(true);
sceneManager.Update(0.1f);
sceneManager.FixedUpdate(0.1f);
sceneManager.LateUpdate(0.1f);
sceneManager.SetPaused(false);
check(counter->updates == 1 && counter->fixedUpdates == 1 && counter->lateUpdates == 1,
"a paused game must not tick components");
sceneManager.Destroy(); sceneManager.Destroy();
} }
} }