From 10a8a846daf6b6cb9b95a13eaf90d6a7d1fbf0de Mon Sep 17 00:00:00 2001 From: dawsh Date: Tue, 17 Jun 2025 13:34:54 -0500 Subject: [PATCH] Refactoring testgame code. --- include/Data/Entity.hpp | 44 +-- include/TestGame/Crate.hpp | 7 + include/TestGame/GameEntity.hpp | 26 ++ include/TestGame/PhysicsTweaker.hpp | 3 + include/TestGame/Player.hpp | 54 ++++ include/TestGame/TestGameApp.hpp | 162 ++++++++++ src/Data/Entity.cpp | 41 ++- src/TestGame/TestGameApp.cpp | 282 +++++++++++++++++ testgame.cpp | 449 ---------------------------- 9 files changed, 577 insertions(+), 491 deletions(-) create mode 100644 include/TestGame/Crate.hpp create mode 100644 include/TestGame/GameEntity.hpp create mode 100644 include/TestGame/PhysicsTweaker.hpp create mode 100644 include/TestGame/Player.hpp create mode 100644 include/TestGame/TestGameApp.hpp create mode 100644 src/TestGame/TestGameApp.cpp diff --git a/include/Data/Entity.hpp b/include/Data/Entity.hpp index c1bff79..c4f2a61 100644 --- a/include/Data/Entity.hpp +++ b/include/Data/Entity.hpp @@ -35,48 +35,10 @@ class Entity json::value metadata; #pragma endregion Entity(); - Entity(const json::value& json) - { - Deserialize(json); - } + Entity(const json::value& json); - json::value Serialize() const - { - json::object data; - data["name"] = this->name; - data["type"] = this->type; - data["x"] = this->x; - data["y"] = this->y; - data["width"] = this->width; - data["height"] = this->height; - data["rotation"] = this->rotation; - data["flip-h"] = this->flip_h; - data["flip-v"] = this->flip_v; - data["z-index"] = (float)this->z_index; - data["color"] = JsonConversions::deparse_color_to_hex(overlay_color); - data["metadata"] = this->metadata; - return data; - } - - void Deserialize(const json::value& json) - { - name = json["name"].as_string(); - type = json["type"].as_string(); - x = json["x"].number.value(); - y = json["y"].number.value(); - width = json["width"].number.value_or(0); - height = json["height"].number.value_or(0); - rotation = json["rotation"].number.value_or(0); - //flip_h = json["flip-h"].boolean.value(); - //flip_v = json["flip-v"].boolean.value(); - z_index = json["z-index"].number.value(); - // TODO: sscanf is bad!!! - //overlay_color = JsonConversions::parse_color(json["color"]); - metadata = json["metadata"]; - - - } - + json::value Serialize() const; + void Deserialize(const json::value& json); }; \ No newline at end of file diff --git a/include/TestGame/Crate.hpp b/include/TestGame/Crate.hpp new file mode 100644 index 0000000..f247ef2 --- /dev/null +++ b/include/TestGame/Crate.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace TestGame { + class Crate : public GameEntity { + + }; +} \ No newline at end of file diff --git a/include/TestGame/GameEntity.hpp b/include/TestGame/GameEntity.hpp new file mode 100644 index 0000000..ae5a472 --- /dev/null +++ b/include/TestGame/GameEntity.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "Data/Entity.hpp" + + +namespace TestGame { + class GameEntity : public Entity + { + public: + + GameEntity(const Vector2& spawn_pos) { + pos = spawn_pos; + next_pos = spawn_pos; + } + + virtual void Update(float elapsed) = 0; + + virtual void Draw() = 0; + + Vector2 pos; + Vector2 next_pos; + Vector2 bbox; + Vector2 velocity; + bool noclip = false; + bool on_ground = true; + }; +} diff --git a/include/TestGame/PhysicsTweaker.hpp b/include/TestGame/PhysicsTweaker.hpp new file mode 100644 index 0000000..45dcbb0 --- /dev/null +++ b/include/TestGame/PhysicsTweaker.hpp @@ -0,0 +1,3 @@ +#pragma once + + diff --git a/include/TestGame/Player.hpp b/include/TestGame/Player.hpp new file mode 100644 index 0000000..fc0c0bd --- /dev/null +++ b/include/TestGame/Player.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "GameEntity.hpp" + + +namespace TestGame { + class Player : public GameEntity + { + public: + Player(const Vector2& spawn_pos) : GameEntity(spawn_pos) { + bbox = {16, 24}; + } + + ~Player() { + + } + + void Update(float elapsed) override { + // Update current position to our next_position, which was computed on the last frame. + // This means we can collision solve our next_pos in between calls to entity->Update(); + pos = next_pos; + + velocity.x *= 1 - (elapsed * air_resistance); + //velocity.y *= 1 - (elapsed * air_resistance); + + //if (on_ground) + //velocity.x *= friction; + + velocity.y += (elapsed*gravity*mass); + + next_pos += velocity * elapsed; + + if (Math::Abs(velocity.y) > 2) + on_ground = false; + + + if (InputService::IsKeyDown(Keys::A)) + velocity.x -= acceleration * elapsed; + + if (InputService::IsKeyDown(Keys::D)) + velocity.x += acceleration * elapsed; + + if (InputService::IsKeyDown(Keys::W)) + velocity.y -= acceleration * elapsed; + + if (InputService::IsKeyDown(Keys::S)) + velocity.y += acceleration * elapsed; + } + + void Draw() override { + J2D::FillRect(Colors::Blue, pos, bbox); + J2D::DrawPartialSprite(player_tex, pos, {0,0}, bbox); + } + }; +} diff --git a/include/TestGame/TestGameApp.hpp b/include/TestGame/TestGameApp.hpp new file mode 100644 index 0000000..3e01c3f --- /dev/null +++ b/include/TestGame/TestGameApp.hpp @@ -0,0 +1,162 @@ +#include "GameEntity.hpp" +#include "Player.hpp" +#include "App/EditorCamera.hpp" +#include "Data/Level.hpp" +#include "JUI/Widgets/Scene.hpp" +#include "ReWindow/types/Window.h" + +namespace TestGame +{ + constexpr int GL_MAJOR = 2; + constexpr int GL_MINOR = 1; + + constexpr int START_APP_WIDTH = 1024; + constexpr int START_APP_HEIGHT = 768; + static const std::string START_APP_TITLE = "Redacted Software 2D Level Editor - Map Testing Game"; + + class TestGameAppWindow : public ReWindow::OpenGLWindow + { + public: + EditorCamera camera; + Level* loaded_level = nullptr; + Tileset* loaded_tileset = nullptr; + JGL::Texture* loaded_tilesheet = nullptr; + + JUI::Scene* scene = nullptr; + + + Player* player = nullptr; + + bool data_ready = false; + + std::vector entities; + + TestGameAppWindow(); + + ~TestGameAppWindow() override; + + + /// Initializes and styles all the JUI widgets used by the application. + /// This function is called from Load(). + void CreateUI(); + + /// Load all resources. + /// This is called after the application window is initially opened, and the OpenGL context is created. + /// But is always called before the first pass of the gameloop (Update, Draw, Step, etc). + void Load(); + + bool Open() override; + + /// Performs collision testing and response between entities and tiles. + void CollisionSolve(float elapsed); + + void CameraUpdate(float elapsed); + + void Update(float elapsed); + + + void DrawLayer(const Layer* layer) const; + + + void ApplyOriginTransformation() { + glTranslatef(GetWidth() / 2.f, GetHeight() / 2.f, 0); + } + + void ApplyCameraTransformation() { + ApplyOriginTransformation(); + glRotatef(camera.rotation.current, 0, 0, 1); + glScalef(camera.scale.current, camera.scale.current, 1); + glTranslatef(-camera.translation.current.x, -camera.translation.current.y, 0); + } + + void DrawLevel(const Level* level) const + { + for (const auto* layer : level->layers) + { + DrawLayer(layer); + } + } + + void DrawWorldSpace() { + + if (!data_ready) return; // Don't try to draw the level if not loaded. + + DrawLevel(loaded_level); + + for (auto* entity : entities) { + entity->Draw(); + } + } + + void Render() { + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + J2D::Begin(); + { + glPushMatrix(); + { + ApplyCameraTransformation(); + DrawWorldSpace(); + } + glPopMatrix(); + } + + J2D::End(); + + scene->Draw(); + + } + +#pragma region ReWindow Overrides + void OnRefresh(float elapsed) override + { + Update(elapsed); + Render(); + SwapBuffers(); + } + + + enum JUI::MouseButton ToJUIEnum(const MouseButton& btn) { + if (btn == MouseButtons::Left) return JUI::MouseButton::Left; + if (btn == MouseButtons::Middle) return JUI::MouseButton::Middle; + if (btn == MouseButtons::Right) return JUI::MouseButton::Right; + + // Default condition. + return JUI::MouseButton::Left; + } + + void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &e) override { + auto btn = ToJUIEnum(e.Button); + + if (scene->ObserveMouseInput(btn, true)) return; + } + + void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &e) override { + auto btn = ToJUIEnum(e.Button); + if (scene->ObserveMouseInput(btn, false)) return; + } + + void OnMouseMove(const ReWindow::MouseMoveEvent &e) override { + Vector2 mposv2(e.Position.x, e.Position.y); + if (scene->ObserveMouseMovement(mposv2)) return; + } + + void OnKeyDown(const ReWindow::KeyDownEvent &e) override { + if (scene->ObserveKeyInput(e.key, true)) return; + } + + void OnKeyUp(const ReWindow::KeyUpEvent &e) override { + if (scene->ObserveKeyInput(e.key, false)) return; + } + + void OnFocusGain(const ReWindow::RWindowEvent &e) override { + focused = true; + } + + void OnFocusLost(const ReWindow::RWindowEvent &e) override { + focused = false; + } +#pragma endregion + }; + +} diff --git a/src/Data/Entity.cpp b/src/Data/Entity.cpp index 0a5f0ab..35c20e1 100644 --- a/src/Data/Entity.cpp +++ b/src/Data/Entity.cpp @@ -1 +1,40 @@ -#include \ No newline at end of file +#include + +Entity::Entity(const json::value &json) { + Deserialize(json); +} + +json::value Entity::Serialize() const { + json::object data; + data["name"] = this->name; + data["type"] = this->type; + data["x"] = this->x; + data["y"] = this->y; + data["width"] = this->width; + data["height"] = this->height; + data["rotation"] = this->rotation; + data["flip-h"] = this->flip_h; + data["flip-v"] = this->flip_v; + data["z-index"] = (float)this->z_index; + data["color"] = JsonConversions::deparse_color_to_hex(overlay_color); + data["metadata"] = this->metadata; + return data; +} + +void Entity::Deserialize(const json::value &json) { + name = json["name"].as_string(); + type = json["type"].as_string(); + x = json["x"].number.value(); + y = json["y"].number.value(); + width = json["width"].number.value_or(0); + height = json["height"].number.value_or(0); + rotation = json["rotation"].number.value_or(0); + //flip_h = json["flip-h"].boolean.value(); + //flip_v = json["flip-v"].boolean.value(); + z_index = json["z-index"].number.value(); + // TODO: sscanf is bad!!! + //overlay_color = JsonConversions::parse_color(json["color"]); + metadata = json["metadata"]; + + +} diff --git a/src/TestGame/TestGameApp.cpp b/src/TestGame/TestGameApp.cpp new file mode 100644 index 0000000..7fb696a --- /dev/null +++ b/src/TestGame/TestGameApp.cpp @@ -0,0 +1,282 @@ +#include + +#include "JUI/Widgets/FpsGraph.hpp" + + +TestGame::TestGameAppWindow::TestGameAppWindow(): + ReWindow::OpenGLWindow( + START_APP_TITLE, + START_APP_WIDTH, START_APP_HEIGHT, + GL_MAJOR, GL_MINOR), + camera() { +} + +TestGame::TestGameAppWindow::~TestGameAppWindow() { + +} + +void TestGame::TestGameAppWindow::CreateUI() { + using namespace JUI::UDimLiterals; + scene = new JUI::Scene(); + + auto* fps_graph = new JUI::FpsGraph(scene); + // TODO: Revise to use PseudoDockedElementAtBottomOfViewport coming in the next JUI release. + fps_graph->Size({100_percent, 50_px}); + fps_graph->AnchorPoint({1, 1}); + fps_graph->Position({100_percent, 100_percent}); + fps_graph->BGColor(Colors::Transparent); + fps_graph->BorderColor(Colors::Transparent); + + + auto* phys_params_slider_window = new JUI::Window(scene); + phys_params_slider_window->Title("Physics Parameters"); + + auto* layout = new JUI::VerticalListLayout(phys_params_slider_window->Content()); + + auto* grav_slider = new LabeledSlider(layout); + grav_slider->Size({100_percent, 20_px}); + grav_slider->Minimum(0); grav_slider->Maximum(1); grav_slider->Interval(1.f/10000.f); + grav_slider->CurrentValue(0.098f); + grav_slider->TextColor(Colors::Black); + grav_slider->Content(std::format("Gravity: {}", gravity)); + + grav_slider->ValueChanged += [&, grav_slider](float value) mutable { + gravity = value * 100.f; + grav_slider->Content(std::format("Gravity: {}", Math::Round(gravity, 3))); + }; + + auto* air_resist_slider = new LabeledSlider(layout); + air_resist_slider->Size({100_percent, 20_px}); + air_resist_slider->Minimum(0); grav_slider->Maximum(1); air_resist_slider->Interval(1.f/10000.f); + air_resist_slider->CurrentValue(0.12f); + air_resist_slider->Content(std::format("Air Resistance: {}", gravity)); + air_resist_slider->TextColor(Colors::Black); + + air_resist_slider->ValueChanged += [&, air_resist_slider](float value) mutable { + air_resistance = value * 100.f; + air_resist_slider->Content(std::format("Air Resistance: {}", Math::Floor(air_resistance))); + }; + + + auto* mass_slider = new LabeledSlider(layout); + mass_slider->Size({100_percent, 20_px}); + mass_slider->Minimum(0); mass_slider->Maximum(1); mass_slider->Interval(1/10000.f); + mass_slider->CurrentValue(0.08f); + mass_slider->TextColor(Colors::Black); + mass_slider->Content(std::format("Player Mass: {}", gravity)); + + mass_slider->ValueChanged += [&, mass_slider](float value) mutable { + mass = value * 100.f; + mass_slider->Content(std::format("Player Mass: {}", mass)); + }; + + auto* accel_slider = new LabeledSlider(layout); + accel_slider->Size({100_percent, 20_px}); + accel_slider->Minimum(0); accel_slider->Maximum(1); accel_slider->Interval(1/10000.f); + accel_slider->CurrentValue(0.12f); + accel_slider->TextColor(Colors::Black); + accel_slider->Content(std::format("Player Accel: {}", acceleration)); + + accel_slider->ValueChanged += [&, accel_slider](float value) mutable { + acceleration = value * 10000.f; + accel_slider->Content(std::format("Player Accel: {}", acceleration)); + }; +} + +void TestGame::TestGameAppWindow::Load() { + CreateUI(); + + player_tex = new JGL::Texture("assets/player.png", FilteringMode::NEAREST); + + + + + + // TODO: More sophisticated order-of-operations is required. + // 1. Initialize elements of widgets that are independent of the level itself / the level depends on. + // 2. Initialize the level. + // 3. Initialize widgets that are dependent on the level itself. + loaded_level = new Level(std::filesystem::path("level.json")); + + loaded_tileset = new Tileset(std::filesystem::path(loaded_level->tileset_path)); + + loaded_tilesheet = new JGL::Texture(loaded_tileset->texture_path, FilteringMode::NEAREST); + + for (auto layer : loaded_level->layers) + layer->Load(); + + data_ready = true; +} + +bool TestGame::TestGameAppWindow::Open() { + if (!OpenGLWindow::Open()) return false; + + auto size = GetSize(); + auto vec_size = Vector2i(size.x, size.y); + if (!JGL::Init(vec_size, 0.f, 1.f)) + return false; + JGL::Update(vec_size); + + + + glClearColor(0.f, 0.f, 0.f, 0.f); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + // TODO: Delete when we update to the next release of JGL + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!! + + Load(); + + scene->SetViewportSize(Vector2(vec_size)); + + + + for (int i = 0; i < 100; i++) { + auto* plr = new Player({50, 50}); + entities.emplace_back(plr); + player = plr; + } + + camera.DefaultState(); + + return true; +} + +void TestGame::TestGameAppWindow::CollisionSolve(float elapsed) { + for (auto* entity : entities) { + int coll_tests = 0; + int coll_hits = 0; + + for (auto* layer : loaded_level->layers) { + // TODO: if layer collides. + + int cell_width = layer->cell_width; + int cell_height = layer->cell_height; + + int ent_tile_tl_x = Math::Floor(entity->next_pos.x) / cell_width; + int ent_tile_tl_y = Math::Floor(entity->next_pos.y) / cell_height; + + int occupies_h_tiles = Math::Floor(entity->bbox.x) / cell_width; + int occupies_v_tiles = Math::Floor(entity->bbox.y) / cell_height; + + Vector2 cell_bbox = Vector2(layer->cell_width, layer->cell_height); + + + for (int x = -1; x <= occupies_h_tiles+1; x++) { + for (int y = -1; y <= occupies_v_tiles+1; y++) { + + int cell_x = ent_tile_tl_x + x; + int cell_y = ent_tile_tl_y + y; + + Vector2 cell_topleft = Vector2(cell_x*cell_width, cell_y*cell_height); + + if (cell_x < 0) continue; // Out of bounds to the left. + if (cell_y < 0) continue; // Out of bounds to the top. + + if (cell_x >= layer->rows) continue; // Out of bounds to the right. + if (cell_y >= layer->cols) continue;// Out of bounds to the bottom. + + auto cell = loaded_level->layers[0]->cells[cell_x][cell_y]; + + if (cell < 0) continue; // Empty cell. + + coll_tests++; + + auto cell_aabb = AABB2D(Vector2(cell_topleft), cell_bbox); + + Vector2 ent_halfbox = (entity->bbox / 2.f); + Vector2 ent_centroid = entity->next_pos + ent_halfbox; + + Vector2 tile_halfbox = cell_bbox / 2.f; + Vector2 tile_centroid = cell_topleft + tile_halfbox; + + if (Solver::AABB2Dvs(ent_centroid, ent_halfbox, tile_centroid, tile_halfbox)) { + coll_hits++; + + Vector2 separation = Solver::SolveAABB(ent_centroid, ent_halfbox, tile_centroid, tile_halfbox); + + Vector2 normal = Solver::GetNormalForAABB(separation, entity->velocity); + + + //if (normal.x == 0 && normal.y == 0) continue; // Why though? + + // Touched top. + if (normal.y == -1) { + entity->velocity.y = 0; + } + + // Touched bottom. + if (normal.y == 1) { + entity->velocity.y *= -0.5f; + } + + // Touched left, I think. + if (normal.x == -1) { } + + // Touched right, I think. + if (normal.x == -1) { } + + entity->next_pos += separation; + + } + } + } + } + } +} + +void TestGame::TestGameAppWindow::CameraUpdate(float elapsed) { + float move_rate = 120; + float zoom_rate = 0.2f; + + if (IsKeyDown(Keys::Minus)) + camera.scale.goal -= zoom_rate*elapsed; + if (IsKeyDown(Keys::Equals)) + camera.scale.goal += zoom_rate*elapsed; + + camera.Update(elapsed); + +} + +void TestGame::TestGameAppWindow::Update(float elapsed) { + CameraUpdate(elapsed); + camera.translation.goal = player->pos; + scene->Update(elapsed); + + Vector2 window_dimensions(GetWidth(), GetHeight()); + + scene->SetViewportSize(window_dimensions); + camera.screenSize = window_dimensions; + + JGL::Update(Vector2i(GetWidth(), GetHeight())); + + CollisionSolve(elapsed); + + for (auto* entity : entities) { + entity->Update(elapsed); + } + + if (!focused) + std::this_thread::sleep_for(std::chrono::milliseconds(48)); +} + +void TestGame::TestGameAppWindow::DrawLayer(const Layer *layer) const { + for (int gx = 0; gx < layer->rows; gx++) + { + for (int gy = 0; gy < layer->cols; gy++) + { + auto quad_idx = layer->cells[gx][gy]; + + Vector2 pos(gx*layer->cell_width, gy*layer->cell_height); + + if (quad_idx < 0) + continue; + + auto quad = loaded_tileset->quads[quad_idx]; + + J2D::DrawPartialSprite(loaded_tilesheet, pos, Vector2(quad.x, quad.y), Vector2(quad.w, quad.h)); + } + } +} diff --git a/testgame.cpp b/testgame.cpp index 747ee5a..1da6dbc 100644 --- a/testgame.cpp +++ b/testgame.cpp @@ -54,456 +54,7 @@ private: }; - class GameEntity - { - public: - GameEntity(const Vector2& spawn_pos) { - pos = spawn_pos; - next_pos = spawn_pos; - } - - virtual void Update(float elapsed) = 0; - - virtual void Draw() = 0; - - Vector2 pos; - Vector2 next_pos; - Vector2 bbox; - Vector2 velocity; - bool noclip = false; - bool on_ground = true; - }; - - class Player : public GameEntity - { - public: - Player(const Vector2& spawn_pos) : GameEntity(spawn_pos) { - bbox = {16, 24}; - } - - ~Player() { - - } - - void Update(float elapsed) override { - // Update current position to our next_position, which was computed on the last frame. - // This means we can collision solve our next_pos in between calls to entity->Update(); - pos = next_pos; - - velocity.x *= 1 - (elapsed * air_resistance); - //velocity.y *= 1 - (elapsed * air_resistance); - - //if (on_ground) - //velocity.x *= friction; - - velocity.y += (elapsed*gravity*mass); - - next_pos += velocity * elapsed; - - if (Math::Abs(velocity.y) > 2) - on_ground = false; - - - if (InputService::IsKeyDown(Keys::A)) - velocity.x -= acceleration * elapsed; - - if (InputService::IsKeyDown(Keys::D)) - velocity.x += acceleration * elapsed; - - if (InputService::IsKeyDown(Keys::W)) - velocity.y -= acceleration * elapsed; - - if (InputService::IsKeyDown(Keys::S)) - velocity.y += acceleration * elapsed; - } - - void Draw() override { - J2D::FillRect(Colors::Blue, pos, bbox); - J2D::DrawPartialSprite(player_tex, pos, {0,0}, bbox); - } - }; - - - -class TestGameAppWindow : public ReWindow::OpenGLWindow -{ -public: - EditorCamera camera; - Level* loaded_level = nullptr; - Tileset* loaded_tileset = nullptr; - JGL::Texture* loaded_tilesheet = nullptr; - - JUI::Scene* scene = nullptr; - - - Player* player = nullptr; - - bool data_ready = false; - - std::vector entities; - - TestGameAppWindow() : ReWindow::OpenGLWindow("TestGameAppWindow", 1024, 768, 2, 1) - { - - } - ~TestGameAppWindow() override - { - - } - - void Load() - { - using namespace JUI::UDimLiterals; - scene = new JUI::Scene(); - - player_tex = new JGL::Texture("assets/player.png", FilteringMode::NEAREST); - - auto* fps_graph = new JUI::FpsGraph(scene); - // TODO: Revise to use PseudoDockedElementAtBottomOfViewport coming in the next JUI release. - fps_graph->Size({100_percent, 50_px}); - fps_graph->AnchorPoint({1, 1}); - fps_graph->Position({100_percent, 100_percent}); - fps_graph->BGColor(Colors::Transparent); - fps_graph->BorderColor(Colors::Transparent); - - - auto* phys_params_slider_window = new JUI::Window(scene); - phys_params_slider_window->Title("Physics Parameters"); - - auto* layout = new JUI::VerticalListLayout(phys_params_slider_window->Content()); - - auto* grav_slider = new LabeledSlider(layout); - grav_slider->Size({100_percent, 20_px}); - grav_slider->Minimum(0); grav_slider->Maximum(1); grav_slider->Interval(1.f/10000.f); - grav_slider->CurrentValue(0.098f); - grav_slider->TextColor(Colors::Black); - grav_slider->Content(std::format("Gravity: {}", gravity)); - - grav_slider->ValueChanged += [&, grav_slider](float value) mutable { - gravity = value * 100.f; - grav_slider->Content(std::format("Gravity: {}", Math::Round(gravity, 3))); - }; - - auto* air_resist_slider = new LabeledSlider(layout); - air_resist_slider->Size({100_percent, 20_px}); - air_resist_slider->Minimum(0); grav_slider->Maximum(1); air_resist_slider->Interval(1.f/10000.f); - air_resist_slider->CurrentValue(0.12f); - air_resist_slider->Content(std::format("Air Resistance: {}", gravity)); - air_resist_slider->TextColor(Colors::Black); - - air_resist_slider->ValueChanged += [&, air_resist_slider](float value) mutable { - air_resistance = value * 100.f; - air_resist_slider->Content(std::format("Air Resistance: {}", Math::Floor(air_resistance))); - }; - - - auto* mass_slider = new LabeledSlider(layout); - mass_slider->Size({100_percent, 20_px}); - mass_slider->Minimum(0); mass_slider->Maximum(1); mass_slider->Interval(1/10000.f); - mass_slider->CurrentValue(0.08f); - mass_slider->TextColor(Colors::Black); - mass_slider->Content(std::format("Player Mass: {}", gravity)); - - mass_slider->ValueChanged += [&, mass_slider](float value) mutable { - mass = value * 100.f; - mass_slider->Content(std::format("Player Mass: {}", mass)); - }; - - auto* accel_slider = new LabeledSlider(layout); - accel_slider->Size({100_percent, 20_px}); - accel_slider->Minimum(0); accel_slider->Maximum(1); accel_slider->Interval(1/10000.f); - accel_slider->CurrentValue(0.12f); - accel_slider->TextColor(Colors::Black); - accel_slider->Content(std::format("Player Accel: {}", acceleration)); - - accel_slider->ValueChanged += [&, accel_slider](float value) mutable { - acceleration = value * 10000.f; - accel_slider->Content(std::format("Player Accel: {}", acceleration)); - }; - - - - // TODO: More sophisticated order-of-operations is required. - // 1. Initialize elements of widgets that are independent of the level itself / the level depends on. - // 2. Initialize the level. - // 3. Initialize widgets that are dependent on the level itself. - loaded_level = new Level(std::filesystem::path("level.json")); - - loaded_tileset = new Tileset(std::filesystem::path(loaded_level->tileset_path)); - - loaded_tilesheet = new JGL::Texture(loaded_tileset->texture_path, FilteringMode::NEAREST); - - for (auto layer : loaded_level->layers) - layer->Load(); - - data_ready = true; - } - - bool Open() override - { - if (!OpenGLWindow::Open()) return false; - - auto size = GetSize(); - auto vec_size = Vector2i(size.x, size.y); - if (!JGL::Init(vec_size, 0.f, 1.f)) - return false; - JGL::Update(vec_size); - - - - glClearColor(0.f, 0.f, 0.f, 0.f); - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - // TODO: Delete when we update to the next release of JGL - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!! - - Load(); - - scene->SetViewportSize(Vector2(vec_size)); - - - - for (int i = 0; i < 100; i++) { - auto* plr = new Player({50, 50}); - entities.emplace_back(plr); - player = plr; - } - - camera.DefaultState(); - - return true; - } - - - void CollisionSolve(float elapsed) { - for (auto* entity : entities) { - int coll_tests = 0; - int coll_hits = 0; - - for (auto* layer : loaded_level->layers) { - // TODO: if layer collides. - - int cell_width = layer->cell_width; - int cell_height = layer->cell_height; - - int ent_tile_tl_x = Math::Floor(entity->next_pos.x) / cell_width; - int ent_tile_tl_y = Math::Floor(entity->next_pos.y) / cell_height; - - int occupies_h_tiles = Math::Floor(entity->bbox.x) / cell_width; - int occupies_v_tiles = Math::Floor(entity->bbox.y) / cell_height; - - Vector2 cell_bbox = Vector2(layer->cell_width, layer->cell_height); - - - for (int x = -1; x <= occupies_h_tiles+1; x++) { - for (int y = -1; y <= occupies_v_tiles+1; y++) { - - int cell_x = ent_tile_tl_x + x; - int cell_y = ent_tile_tl_y + y; - - Vector2 cell_topleft = Vector2(cell_x*cell_width, cell_y*cell_height); - - if (cell_x < 0) continue; // Out of bounds to the left. - if (cell_y < 0) continue; // Out of bounds to the top. - - if (cell_x >= layer->rows) continue; // Out of bounds to the right. - if (cell_y >= layer->cols) continue;// Out of bounds to the bottom. - - auto cell = loaded_level->layers[0]->cells[cell_x][cell_y]; - - if (cell < 0) continue; // Empty cell. - - coll_tests++; - - auto cell_aabb = AABB2D(Vector2(cell_topleft), cell_bbox); - - Vector2 ent_halfbox = (entity->bbox / 2.f); - Vector2 ent_centroid = entity->next_pos + ent_halfbox; - - Vector2 tile_halfbox = cell_bbox / 2.f; - Vector2 tile_centroid = cell_topleft + tile_halfbox; - - if (Solver::AABB2Dvs(ent_centroid, ent_halfbox, tile_centroid, tile_halfbox)) { - coll_hits++; - - Vector2 separation = Solver::SolveAABB(ent_centroid, ent_halfbox, tile_centroid, tile_halfbox); - - Vector2 normal = Solver::GetNormalForAABB(separation, entity->velocity); - - - //if (normal.x == 0 && normal.y == 0) continue; // Why though? - - // Touched top. - if (normal.y == -1) { - entity->velocity.y = 0; - } - - // Touched bottom. - if (normal.y == 1) { - entity->velocity.y *= -0.5f; - } - - // Touched left, I think. - if (normal.x == -1) { } - - // Touched right, I think. - if (normal.x == -1) { } - - entity->next_pos += separation; - - } - } - } - } - } - } - - void CameraUpdate(float elapsed) - { - float move_rate = 120; - float zoom_rate = 0.2f; - - if (IsKeyDown(Keys::Minus)) - camera.scale.goal -= zoom_rate*elapsed; - if (IsKeyDown(Keys::Equals)) - camera.scale.goal += zoom_rate*elapsed; - - camera.Update(elapsed); - - } - - void Update(float elapsed) - { - CameraUpdate(elapsed); - camera.translation.goal = player->pos; - scene->Update(elapsed); - - Vector2 window_dimensions(GetWidth(), GetHeight()); - - scene->SetViewportSize(window_dimensions); - camera.screenSize = window_dimensions; - - JGL::Update(Vector2i(GetWidth(), GetHeight())); - - CollisionSolve(elapsed); - - for (auto* entity : entities) { - entity->Update(elapsed); - } - - if (!focused) - std::this_thread::sleep_for(std::chrono::milliseconds(48)); - } - - - void OnFocusGain(const ReWindow::RWindowEvent &e) override { - focused = true; - } - - void OnFocusLost(const ReWindow::RWindowEvent &e) override { - focused = false; - } - - void DrawLayer(const Layer* layer) const - { - for (int gx = 0; gx < layer->rows; gx++) - { - for (int gy = 0; gy < layer->cols; gy++) - { - auto quad_idx = layer->cells[gx][gy]; - - Vector2 pos(gx*layer->cell_width, gy*layer->cell_height); - - if (quad_idx < 0) - continue; - - auto quad = loaded_tileset->quads[quad_idx]; - - J2D::DrawPartialSprite(loaded_tilesheet, pos, Vector2(quad.x, quad.y), Vector2(quad.w, quad.h)); - } - } - } - - void DrawLevel(const Level* level) const - { - for (const auto* layer : level->layers) - { - DrawLayer(layer); - } - } - - void Draw() - { - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - J2D::Begin(); - glPushMatrix(); - glTranslatef(GetWidth() / 2.f, GetHeight() / 2.f, 0); - - glRotatef(camera.rotation.current, 0, 0, 1); - glScalef(camera.scale.current, camera.scale.current, 1); - glTranslatef(-camera.translation.current.x, -camera.translation.current.y, 0); - - if (data_ready) - DrawLevel(loaded_level); - - - for (auto* entity : entities) { - entity->Draw(); - } - - glPopMatrix(); - J2D::End(); - scene->Draw(); - } - - - void OnRefresh(float elapsed) override - { - Update(elapsed); - Draw(); - SwapBuffers(); - } - - - enum JUI::MouseButton ToJUIEnum(const MouseButton& btn) { - if (btn == MouseButtons::Left) return JUI::MouseButton::Left; - if (btn == MouseButtons::Middle) return JUI::MouseButton::Middle; - if (btn == MouseButtons::Right) return JUI::MouseButton::Right; - - // Default condition. - return JUI::MouseButton::Left; - } - - void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &e) override { - auto btn = ToJUIEnum(e.Button); - - if (scene->ObserveMouseInput(btn, true)) return; - } - - void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &e) override { - auto btn = ToJUIEnum(e.Button); - if (scene->ObserveMouseInput(btn, false)) return; - } - - void OnMouseMove(const ReWindow::MouseMoveEvent &e) override { - Vector2 mposv2(e.Position.x, e.Position.y); - if (scene->ObserveMouseMovement(mposv2)) return; - } - - void OnKeyDown(const ReWindow::KeyDownEvent &e) override { - if (scene->ObserveKeyInput(e.key, true)) return; - } - - void OnKeyUp(const ReWindow::KeyUpEvent &e) override { - if (scene->ObserveKeyInput(e.key, false)) return; - } - -}; int main()