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
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
\ 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);