From 0fe31615c53563b0a3ecb32af99cc08087a36b06 Mon Sep 17 00:00:00 2001 From: chris bell Date: Mon, 24 Aug 2026 22:49:16 -0500 Subject: [PATCH] Main, pause, options, and in-game menu systems wired up; ready for gameplay --- assets/game-ui.rml | 11 +++ assets/globals.rcss | 42 +++++++++ assets/main-menu.rcss | 16 ++++ assets/main-menu.rml | 16 ++++ assets/main.rcss | 45 ---------- assets/options-menu.rml | 15 ++++ assets/pause-menu.rml | 18 ++++ src/Application.cpp | 29 +++---- src/Application.h | 3 +- src/Game.cpp | 187 ++++++++++++++++++++++++++++++++++++---- src/Game.h | 31 +++++-- src/main.cpp | 2 +- 12 files changed, 327 insertions(+), 88 deletions(-) create mode 100644 assets/game-ui.rml create mode 100644 assets/globals.rcss create mode 100644 assets/main-menu.rcss create mode 100644 assets/main-menu.rml delete mode 100644 assets/main.rcss create mode 100644 assets/options-menu.rml create mode 100644 assets/pause-menu.rml diff --git a/assets/game-ui.rml b/assets/game-ui.rml new file mode 100644 index 0000000..9fe074b --- /dev/null +++ b/assets/game-ui.rml @@ -0,0 +1,11 @@ + + + + game_ui + + + + +

Hello from RmlUi!

+ +
\ No newline at end of file diff --git a/assets/globals.rcss b/assets/globals.rcss new file mode 100644 index 0000000..43e34be --- /dev/null +++ b/assets/globals.rcss @@ -0,0 +1,42 @@ +body { + + --primary-color: #446043; + --primary-background-color: #2c3e2b; + --text-color: #ffffea; + + font-family: rmlui-debugger-font; + font-size: 16dp; + color: var(--text-color); + background-color: #ffffff00; +} + +h1 { + font-size: 24px; + margin-bottom: 10px; +} + +p { + margin-bottom: 15px; +} + +.hidden { + display: none; +} + +.menu-btn { + display: block; + padding: 8px; + width: 200dp; + text-align: center; + background-color: var(--primary-color); + border: 1px #ffffff00; + font-size: 22dp; +} + +.menu-btn:hover { + background-color: #4a7eb5; +} + +.menu-btn:active { + background-color: #2e4e70; +} \ No newline at end of file diff --git a/assets/main-menu.rcss b/assets/main-menu.rcss new file mode 100644 index 0000000..bcd030c --- /dev/null +++ b/assets/main-menu.rcss @@ -0,0 +1,16 @@ +body { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--primary-background-color); +} + +#start_menu { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + min-width: 80%; +} \ No newline at end of file diff --git a/assets/main-menu.rml b/assets/main-menu.rml new file mode 100644 index 0000000..9e89d33 --- /dev/null +++ b/assets/main-menu.rml @@ -0,0 +1,16 @@ + + + + gamejam + + + + + +
+

Curious Critters

+ + +
+ +
\ No newline at end of file diff --git a/assets/main.rcss b/assets/main.rcss deleted file mode 100644 index 9fed10b..0000000 --- a/assets/main.rcss +++ /dev/null @@ -1,45 +0,0 @@ -body { - font-family: rmlui-debugger-font; - font-size: 16px; - color: white; - width: 100vw; - height: 100vh; -} - -#panel { - position: absolute; - top: 40px; - left: 40px; - width: 300px; - padding: 20px; - background-color: rgba(20, 20, 20, 220); - border: 2px #888; -} - -h1 { - font-size: 24px; - color: #ffd35c; - margin-bottom: 10px; -} - -p { - margin-bottom: 15px; -} - -.hidden { - display: none; -} - -.menu-btn { - display: block; - width: 100px; - padding: 8px; - text-align: center; - background-color: #3a6ea5; - color: white; - border: 1px #5a8ec5; -} - -.menu-btn:hover { - background-color: #4a7eb5; -} \ No newline at end of file diff --git a/assets/options-menu.rml b/assets/options-menu.rml new file mode 100644 index 0000000..0ac7a51 --- /dev/null +++ b/assets/options-menu.rml @@ -0,0 +1,15 @@ + + + + options_menu + + + + + +
+

Options

+ +
+ +
\ No newline at end of file diff --git a/assets/pause-menu.rml b/assets/pause-menu.rml new file mode 100644 index 0000000..9dfafe2 --- /dev/null +++ b/assets/pause-menu.rml @@ -0,0 +1,18 @@ + + + + pause_menu + + + + + +
+

Pause

+ + + +
+ +
\ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index 3dc58ea..dbaeb23 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -1,20 +1,17 @@ #include "Application.h" -#include "Game.h" #include "backend/RaylibInput.h" #include -#include #include -#include #include -Game game; - -bool Application::Init(int width, int height, const char *title, - const char *documentPath) { +bool Application::Init(int width, int height, const char *title) { + SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(width, height, title); SetTargetFPS(60); + SetExitKey(KEY_NULL); + Rml::SetSystemInterface(&system_interface); Rml::SetRenderInterface(&render_interface); Rml::Initialise(); @@ -22,26 +19,18 @@ bool Application::Init(int width, int height, const char *title, context = Rml::CreateContext("main", Rml::Vector2i(width, height)); Rml::Debugger::Initialise(context); - // TODO: load a real font - - if (Rml::ElementDocument *document = context->LoadDocument(documentPath)) { - document->Show(); - } else { - TraceLog(LOG_WARNING, "Failed to load %s", documentPath); - } - if (!game.Init(context)) { TraceLog(LOG_ERROR, "Failed to initialize game instance."); return false; } Rml::DataModelHandle game_data_model_handle; - if (!game.setup_rml_data_binding(context, game_data_model_handle, - game.game_data)) { + if (!game.SetupRmlDataBinding(game_data_model_handle)) { TraceLog(LOG_ERROR, "Failed to initialize data bindings for 'game_data'."); return false; } + game.TrySetGameState(START_MENU); return true; } @@ -51,13 +40,17 @@ void Application::Frame() { return; } + if (IsWindowResized()) { + context->SetDimensions(Rml::Vector2i(GetScreenWidth(), GetScreenHeight())); + } + ForwardInputToRml(context); context->Update(); game.Update(); BeginDrawing(); - ClearBackground(DARKGRAY); + ClearBackground(BLACK); game.Draw2D(); diff --git a/src/Application.h b/src/Application.h index acea8c8..2f69646 100644 --- a/src/Application.h +++ b/src/Application.h @@ -6,7 +6,7 @@ class Application { public: - bool Init(int width, int height, const char *title, const char *documentPath); + bool Init(int width, int height, const char *title); void Frame(); void Shutdown(); @@ -16,5 +16,6 @@ private: RaylibSystemInterface system_interface; RaylibRenderInterface render_interface; Rml::Context *context = nullptr; + Game game; bool running = true; }; \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index ebd36e5..6ad5d18 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,51 +1,208 @@ #include "Game.h" -#include -#include #include -#include +#include #include -void Game::Draw2D() { return; } +namespace { -void Game::Update() { return; } +const char *DocumentPathFor(GameState state) { + switch (state) { + case START_MENU: + return "assets/main-menu.rml"; + case PAUSE_MENU: + return "assets/pause-menu.rml"; + case STATS_MENU: + return "assets/stats-menu.rml"; + case OPTIONS_MENU: + return "assets/options-menu.rml"; + case IN_GAME: + return "assets/game-ui.rml"; + } + return nullptr; +} + +bool IsMenuState(GameState state) { + return state == OPTIONS_MENU || state == STATS_MENU; +} + +} // namespace bool Game::Init(Rml::Context *context) { - if (!ctx) + if (!context) return false; - ctx = context; return true; } -bool Game::setup_rml_data_binding(Rml::Context *ctx, - Rml::DataModelHandle &model_handle, - GameData &game_data) { +bool Game::SetupRmlDataBinding(Rml::DataModelHandle &model_handle) { Rml::DataModelConstructor dmc = ctx->CreateDataModel("game_data"); if (!dmc) { - TraceLog(LOG_ERROR, "Failed to construct data model.."); + TraceLog(LOG_ERROR, "Failed to construct data model."); return false; } + // ---------- Member bindings ---------- + dmc.Bind("game_state", &game_data.game_state); + // ---------- Event callback bindings ---------- + + dmc.BindEventCallback("start_game", [this](Rml::DataModelHandle, Rml::Event &, + const Rml::VariantList &) { + TrySetGameState(IN_GAME); + }); + + dmc.BindEventCallback("pause_game", [this](Rml::DataModelHandle, Rml::Event &, + const Rml::VariantList &) { + TrySetGameState(PAUSE_MENU); + }); + + dmc.BindEventCallback( + "unpause_game", + [this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) { + TrySetGameState(IN_GAME); + }); + + dmc.BindEventCallback( + "open_options_menu", + [this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) { + TrySetGameState(OPTIONS_MENU); + }); + + dmc.BindEventCallback( + "end_current_game", + [this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) { + TrySetGameState(START_MENU); + }); + + dmc.BindEventCallback("go_back", + [this](Rml::DataModelHandle, Rml::Event &, + const Rml::VariantList &) { GoBack(); }); + + model_handle = dmc.GetModelHandle(); return true; } -void Game::ResetGame() {} -void Game::ShowMainMenu() {} +Rml::ElementDocument *Game::LoadOrGetDocument(GameState state) { + if (auto it = documents.find(state); it != documents.end()) + return it->second; + + const char *path = DocumentPathFor(state); + if (!path) + return nullptr; + + Rml::ElementDocument *doc = ctx->LoadDocument(path); + if (!doc) { + TraceLog(LOG_ERROR, "Failed to load document: %s", path); + return nullptr; + } + + documents[state] = doc; + return doc; +} + +void Game::ShowDocumentForState(GameState state) { + if (activeDocument) { + activeDocument->Hide(); + activeDocument = nullptr; + } + + if (Rml::ElementDocument *doc = LoadOrGetDocument(state)) { + doc->Show(); + activeDocument = doc; + } +} void Game::TrySetGameState(GameState newState) { - GameState currentState = game_data.game_state; + GameState currentState = CurrentState(); + + if (IsMenuState(newState) && !IsMenuState(currentState)) { + previousState = currentState; + } switch (newState) { case START_MENU: if (currentState == IN_GAME) ResetGame(); - ShowMainMenu(); + break; + case IN_GAME: + if (currentState == START_MENU) + StartNewGame(); break; default: break; } game_data.game_state = newState; + ShowDocumentForState(newState); +} + +void Game::GoBack() { + if (IsMenuState(CurrentState())) { + TrySetGameState(previousState); + } +} + +void Game::StartNewGame() { + // TODO + + // --- placeholder testing stuff --- + playerPos = {GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f}; +} + +void Game::ResetGame() { + // TODO +} + +void Game::Update() { + HandleInput(); + + if (CurrentState() != IN_GAME) + return; + // TODO + + // --- placeholder testing stuff --- + float dt = GetFrameTime(); + + if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) + playerPos.y -= playerSpeed * dt; + if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) + playerPos.y += playerSpeed * dt; + if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) + playerPos.x -= playerSpeed * dt; + if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) + playerPos.x += playerSpeed * dt; +} + +void Game::Draw2D() { + if (CurrentState() != IN_GAME) + return; + // TODO + + // --- placeholder testing stuff --- + // DrawText("Hello from raylib!", 50, 50, 20, RAYWHITE); + if (CurrentState() != IN_GAME) + return; + DrawCircleV(playerPos, 24.0f, BLUE); + DrawText("WASD/Arrows to move, ESC to pause", 20, 20, 20, RAYWHITE); +} + +void Game::HandleInput() { + if (!IsKeyReleased(KEY_ESCAPE)) + return; + + switch (CurrentState()) { + case IN_GAME: + TrySetGameState(PAUSE_MENU); + break; + case PAUSE_MENU: + TrySetGameState(IN_GAME); + break; + case OPTIONS_MENU: + case STATS_MENU: + GoBack(); + break; + default: + break; + } } \ No newline at end of file diff --git a/src/Game.h b/src/Game.h index 62f5819..c196177 100644 --- a/src/Game.h +++ b/src/Game.h @@ -2,10 +2,14 @@ #include #include +#include +#include +#include + enum GameState { START_MENU, PAUSE_MENU, STATS_MENU, OPTIONS_MENU, IN_GAME }; struct GameData { - GameState game_state = START_MENU; + int game_state = START_MENU; }; class Game { @@ -13,22 +17,33 @@ public: GameData game_data; bool Init(Rml::Context *context); - - bool setup_rml_data_binding(Rml::Context *ctx, - Rml::DataModelHandle &model_handle, - GameData &game_data); + bool SetupRmlDataBinding(Rml::DataModelHandle &model_handle); void Update(); void Draw2D(); void TrySetGameState(GameState newState); + void GoBack(); + + GameState CurrentState() const { + return static_cast(game_data.game_state); + } void StartNewGame(); void ResetGame(); - void ShowMainMenu(); - void ShowPauseMenu(); - private: + Rml::ElementDocument *LoadOrGetDocument(GameState state); + void ShowDocumentForState(GameState state); + void HandleInput(); + Rml::Context *ctx = nullptr; + std::unordered_map documents; + Rml::ElementDocument *activeDocument = nullptr; + + GameState previousState = START_MENU; + + // --- placeholder testing stuff --- + Vector2 playerPos{}; + float playerSpeed = 300.0f; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 01f05a0..4707f32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,7 @@ int main() { static Application app; g_app = &app; - app.Init(1280, 720, "gamejam", "assets/main.rml"); + app.Init(1280, 720, "gamejam"); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(MainLoopStep, 0, 1);