Pause menu

This commit is contained in:
2026-05-14 00:23:53 +03:00
parent ef49506515
commit eb0d05a577
5 changed files with 329 additions and 73 deletions
+2
View File
@@ -35,6 +35,7 @@ set(EDITSCENE_SOURCES
systems/RoomLayoutSystem.cpp
systems/FurnitureLibrary.cpp
systems/StartupMenuSystem.cpp
systems/PauseMenuSystem.cpp
systems/PlayerControllerSystem.cpp
systems/CharacterSlotSystem.cpp
systems/CharacterRegistry.cpp
@@ -205,6 +206,7 @@ set(EDITSCENE_HEADERS
ui/CharacterClassDatabaseEditor.hpp
systems/CharacterClassSystem.hpp
systems/StartupMenuSystem.hpp
systems/PauseMenuSystem.hpp
systems/PlayerControllerSystem.hpp
systems/EditorUISystem.hpp
systems/CellGridSystem.hpp
+101 -73
View File
@@ -29,6 +29,7 @@
#include "systems/NormalDebugSystem.hpp"
#include "systems/RoomLayoutSystem.hpp"
#include "systems/StartupMenuSystem.hpp"
#include "systems/PauseMenuSystem.hpp"
#include "systems/DialogueSystem.hpp"
#include "systems/CharacterClassSystem.hpp"
#include "systems/PregnancySystem.hpp"
@@ -151,6 +152,13 @@ void ImGuiRenderListener::preViewportUpdate(
sms->update(m_deltaTime);
}
// Render pause menu in game mode (inside ImGui frame scope)
if (m_editorApp &&
m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
m_editorApp->getGamePlayState() == EditorApp::GamePlayState::Paused) {
PauseMenuSystem::getInstance().update(m_deltaTime);
}
// Render dialogue box (game mode or editor preview)
DialogueSystem::getInstance().update(m_deltaTime);
@@ -455,6 +463,7 @@ void EditorApp::setup()
m_world, m_sceneMgr, this);
DialogueSystem::getInstance().init(this);
DialogueSystem::getInstance().loadSettings("dialogue.json");
PauseMenuSystem::getInstance().init(this);
m_characterClassSystem =
std::make_unique<CharacterClassSystem>(
@@ -494,6 +503,7 @@ void EditorApp::setup()
if (m_startupMenuSystem)
m_startupMenuSystem->prepareFont();
DialogueSystem::getInstance().prepareFont();
PauseMenuSystem::getInstance().prepareFont();
}
// Now show the overlay — font atlas will be built with our font
@@ -635,6 +645,8 @@ void EditorApp::setGamePlayState(GamePlayState state)
setWindowGrab(true);
} else if (state == GamePlayState::Menu) {
setWindowGrab(false);
} else if (state == GamePlayState::Paused) {
setWindowGrab(false);
}
}
}
@@ -900,6 +912,8 @@ void EditorApp::createAxes()
bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
bool paused = (m_gamePlayState == GamePlayState::Paused);
if (m_gameMode == GameMode::Editor) {
// Update camera
if (m_camera) {
@@ -919,81 +933,83 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
m_characterSlotSystem->update();
}
/* --- Animation / procedural generation --- */
if (m_animationTreeSystem) {
m_animationTreeSystem->update(evt.timeSinceLastFrame);
if (m_behaviorTreeSystem)
m_behaviorTreeSystem->update(evt.timeSinceLastFrame);
}
if (m_pathFollowingSystem) {
m_pathFollowingSystem->update(evt.timeSinceLastFrame);
}
if (m_proceduralMeshSystem) {
m_proceduralMeshSystem->update();
}
/* --- Static world generation (meshes + physics) --- */
if (m_roomLayoutSystem) {
m_roomLayoutSystem->update();
}
if (m_cellGridSystem) {
m_cellGridSystem->update();
}
/* --- Normal debug visualization (after geometry is built) --- */
if (m_normalDebugSystem) {
m_normalDebugSystem->update();
}
/* --- NavMesh builds after static geometry is ready --- */
if (m_navMeshSystem) {
m_navMeshSystem->update(evt.timeSinceLastFrame);
}
/* --- Smart Object system (AI navigation to smart objects) --- */
if (m_smartObjectSystem) {
m_smartObjectSystem->update(evt.timeSinceLastFrame);
}
/* --- GOAP Planner system (plan generation) --- */
if (m_goapPlannerSystem) {
m_goapPlannerSystem->update(evt.timeSinceLastFrame);
}
/* --- GOAP Runner system (plan execution) --- */
if (m_goapRunnerSystem) {
m_goapRunnerSystem->update(evt.timeSinceLastFrame);
}
/* --- Actuator system (player interaction prompts) --- */
if (m_actuatorSystem) {
m_actuatorSystem->update(evt.timeSinceLastFrame);
}
/* --- Event Handler system (event-driven BTs) --- */
if (m_eventHandlerSystem) {
m_eventHandlerSystem->update(evt.timeSinceLastFrame);
}
/* --- Dynamic physics (characters after static world) --- */
if (m_characterSystem) {
m_characterSystem->update(evt.timeSinceLastFrame);
}
/* --- Buoyancy system (before physics so impulse is integrated) --- */
if (m_buoyancySystem) {
// Update camera position for water detection area
if (m_camera) {
Ogre::Vector3 cameraPos = m_camera->getPosition();
m_buoyancySystem->setCameraPosition(cameraPos);
/* --- Gameplay systems (paused when in Paused state) --- */
if (!paused) {
/* --- Animation / procedural generation --- */
if (m_animationTreeSystem) {
m_animationTreeSystem->update(evt.timeSinceLastFrame);
if (m_behaviorTreeSystem)
m_behaviorTreeSystem->update(evt.timeSinceLastFrame);
}
if (m_pathFollowingSystem) {
m_pathFollowingSystem->update(evt.timeSinceLastFrame);
}
if (m_proceduralMeshSystem) {
m_proceduralMeshSystem->update();
}
m_buoyancySystem->update(evt.timeSinceLastFrame);
}
/* --- Main physics step --- */
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);
/* --- Static world generation (meshes + physics) --- */
if (m_roomLayoutSystem) {
m_roomLayoutSystem->update();
}
if (m_cellGridSystem) {
m_cellGridSystem->update();
}
/* --- Normal debug visualization (after geometry is built) --- */
if (m_normalDebugSystem) {
m_normalDebugSystem->update();
}
/* --- NavMesh builds after static geometry is ready --- */
if (m_navMeshSystem) {
m_navMeshSystem->update(evt.timeSinceLastFrame);
}
/* --- Smart Object system (AI navigation to smart objects) --- */
if (m_smartObjectSystem) {
m_smartObjectSystem->update(evt.timeSinceLastFrame);
}
/* --- GOAP Planner system (plan generation) --- */
if (m_goapPlannerSystem) {
m_goapPlannerSystem->update(evt.timeSinceLastFrame);
}
/* --- GOAP Runner system (plan execution) --- */
if (m_goapRunnerSystem) {
m_goapRunnerSystem->update(evt.timeSinceLastFrame);
}
/* --- Actuator system (player interaction prompts) --- */
if (m_actuatorSystem) {
m_actuatorSystem->update(evt.timeSinceLastFrame);
}
/* --- Event Handler system (event-driven BTs) --- */
if (m_eventHandlerSystem) {
m_eventHandlerSystem->update(evt.timeSinceLastFrame);
}
/* --- Dynamic physics (characters after static world) --- */
if (m_characterSystem) {
m_characterSystem->update(evt.timeSinceLastFrame);
}
/* --- Buoyancy system (before physics so impulse is integrated) --- */
if (m_buoyancySystem) {
// Update camera position for water detection area
if (m_camera) {
Ogre::Vector3 cameraPos = m_camera->getPosition();
m_buoyancySystem->setCameraPosition(cameraPos);
}
m_buoyancySystem->update(evt.timeSinceLastFrame);
}
/* --- Main physics step --- */
if (m_physicsSystem) {
m_physicsSystem->update(evt.timeSinceLastFrame);
}
}
/* --- Rendering support systems --- */
@@ -1127,6 +1143,13 @@ bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
m_currentModifiers = evt.keysym.mod;
if (m_gameMode == GameMode::Game) {
if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
if (m_gamePlayState == GamePlayState::Playing)
setGamePlayState(GamePlayState::Paused);
else if (m_gamePlayState == GamePlayState::Paused)
setGamePlayState(GamePlayState::Playing);
return true;
}
bool pressed = true;
switch (evt.keysym.sym) {
case 'w':
@@ -1242,6 +1265,11 @@ flecs::entity EditorApp::getSelectedEntity() const
return flecs::entity::null();
}
PauseMenuSystem *EditorApp::getPauseMenuSystem() const
{
return &PauseMenuSystem::getInstance();
}
DialogueSystem *EditorApp::getDialogueSystem() const
{
return &DialogueSystem::getInstance();
+2
View File
@@ -30,6 +30,7 @@ class CharacterSystem;
class CellGridSystem;
class RoomLayoutSystem;
class StartupMenuSystem;
class PauseMenuSystem;
class DialogueSystem;
class PlayerControllerSystem;
class BuoyancySystem;
@@ -192,6 +193,7 @@ public:
{
return m_startupMenuSystem.get();
}
PauseMenuSystem *getPauseMenuSystem() const;
DialogueSystem *getDialogueSystem() const;
ActuatorSystem *getActuatorSystem() const
{
@@ -0,0 +1,181 @@
#include "PauseMenuSystem.hpp"
#include "../EditorApp.hpp"
#include "../components/StartupMenu.hpp"
#include <flecs.h>
#include <functional>
#include <imgui.h>
#include <OgreFontManager.h>
#include <OgreImGuiOverlay.h>
#include <OgreLogManager.h>
#include <OgreOverlayManager.h>
#include <OgreRoot.h>
#include <vector>
PauseMenuSystem::PauseMenuSystem()
: m_editorApp(nullptr)
{
}
PauseMenuSystem::~PauseMenuSystem() = default;
PauseMenuSystem &PauseMenuSystem::getInstance()
{
static PauseMenuSystem instance;
return instance;
}
void PauseMenuSystem::init(EditorApp *editorApp)
{
m_editorApp = editorApp;
}
void PauseMenuSystem::ensureFontLoaded(const Ogre::String &fontName,
float fontSize)
{
if (m_fontLoaded && m_currentFontName == fontName &&
m_currentFontSize == fontSize)
return;
Ogre::ImGuiOverlay *overlay = m_editorApp->getImGuiOverlay();
if (!overlay)
return;
Ogre::FontPtr font;
try {
if (Ogre::FontManager::getSingleton().resourceExists(
"PauseMenuFont", "General")) {
Ogre::FontManager::getSingleton().remove("PauseMenuFont",
"General");
}
font = Ogre::FontManager::getSingleton().create("PauseMenuFont",
"General");
font->setType(Ogre::FontType::FT_TRUETYPE);
font->setSource(fontName);
font->setTrueTypeSize(fontSize);
font->setTrueTypeResolution(75);
font->addCodePointRange(Ogre::Font::CodePointRange(32, 255));
font->load();
} catch (...) {
Ogre::LogManager::getSingleton().logMessage(
"PauseMenuSystem: Failed to load font " + fontName);
m_menuFont = nullptr;
m_fontLoaded = false;
return;
}
m_menuFont = overlay->addFont("PauseMenuFont", "General");
m_currentFontName = fontName;
m_currentFontSize = fontSize;
m_fontLoaded = true;
}
void PauseMenuSystem::prepareFont()
{
if (!m_editorApp)
return;
flecs::world *world = m_editorApp->getWorld();
if (!world)
return;
flecs::entity menuEntity = flecs::entity::null();
world->query<StartupMenuComponent>().each(
[&](flecs::entity e, StartupMenuComponent &) {
if (!menuEntity.is_alive())
menuEntity = e;
});
if (menuEntity.is_alive()) {
auto &sm = menuEntity.get<StartupMenuComponent>();
Ogre::LogManager::getSingleton().logMessage(
"PauseMenuSystem: Using font=" + sm.fontName +
", size=" + Ogre::StringConverter::toString(sm.fontSize));
ensureFontLoaded(sm.fontName, sm.fontSize);
} else {
Ogre::LogManager::getSingleton().logMessage(
"PauseMenuSystem: No StartupMenuComponent found, using defaults");
ensureFontLoaded("Kenney Bold.ttf", 36.0f);
}
}
void PauseMenuSystem::update(float deltaTime)
{
(void)deltaTime;
if (!m_editorApp || m_editorApp->getGamePlayState() !=
EditorApp::GamePlayState::Paused)
return;
renderMenu();
}
void PauseMenuSystem::renderMenu()
{
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_Always);
ImVec4 bgColor = ImVec4(0.0f, 0.0f, 0.0f, 0.85f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, bgColor);
ImGui::Begin("PauseMenu", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing);
if (m_menuFont)
ImGui::PushFont(m_menuFont);
struct ButtonData {
const char *label;
std::function<void()> action;
};
std::vector<ButtonData> buttons;
buttons.push_back({ "RETURN TO GAME", [&]() {
m_editorApp->setGamePlayState(
EditorApp::GamePlayState::Playing);
} });
buttons.push_back({ "SAVE GAME", [&]() {
Ogre::LogManager::getSingleton().logMessage(
"Save game not implemented");
} });
buttons.push_back({ "OPTIONS", [&]() {
Ogre::LogManager::getSingleton().logMessage(
"Options not implemented");
} });
buttons.push_back({ "QUIT", [&]() {
Ogre::Root::getSingleton().queueEndRendering();
} });
float buttonWidth = 0.0f;
float buttonsHeight = 0.0f;
float extraPixels = 20.0f;
for (const auto &b : buttons) {
ImVec2 textSize = ImGui::CalcTextSize(b.label);
float bwidth = textSize.x +
(ImGui::GetStyle().FramePadding.x * 2.0f) +
extraPixels;
float bheight =
textSize.y + (ImGui::GetStyle().FramePadding.y * 2.0f);
if (buttonWidth < bwidth)
buttonWidth = bwidth;
buttonsHeight += bheight + ImGui::GetStyle().ItemSpacing.y;
}
if (!buttons.empty())
buttonsHeight -= ImGui::GetStyle().ItemSpacing.y;
ImGui::SetCursorPosY((size.y - buttonsHeight) * 0.5f);
for (const auto &b : buttons) {
ImGui::SetCursorPosX((size.x - buttonWidth) * 0.5f);
if (ImGui::Button(b.label, ImVec2(buttonWidth, 0)))
b.action();
}
if (m_menuFont)
ImGui::PopFont();
ImGui::End();
ImGui::PopStyleColor();
}
@@ -0,0 +1,43 @@
#ifndef EDITSCENE_PAUSEMENUSYSTEM_HPP
#define EDITSCENE_PAUSEMENUSYSTEM_HPP
#pragma once
#include <Ogre.h>
#include <imgui.h>
class EditorApp;
/**
* Singleton pause menu system.
*
* Renders a full-screen pause menu in game mode when EditorApp
* is in GamePlayState::Paused. Font settings are copied from
* the StartupMenuComponent configured in the scene.
*/
class PauseMenuSystem {
public:
static PauseMenuSystem &getInstance();
void init(EditorApp *editorApp);
void prepareFont();
void update(float deltaTime);
private:
PauseMenuSystem();
~PauseMenuSystem();
PauseMenuSystem(const PauseMenuSystem &) = delete;
PauseMenuSystem &operator=(const PauseMenuSystem &) = delete;
void renderMenu();
void ensureFontLoaded(const Ogre::String &fontName, float fontSize);
EditorApp *m_editorApp = nullptr;
bool m_fontLoaded = false;
Ogre::String m_currentFontName;
float m_currentFontSize = 0.0f;
ImFont *m_menuFont = nullptr;
};
#endif // EDITSCENE_PAUSEMENUSYSTEM_HPP