Files
Editor2D/src/DemoGame/TestGameApp.cpp

356 lines
11 KiB
C++

#include <DemoGame/TestGameApp.hpp>
#include "JUI/Widgets/FpsGraph.hpp"
#include <JUI/Widgets/ListLayout.hpp>
#include "SimpleAABBSolver.hpp"
TestGame::TestGameAppWindow::TestGameAppWindow():
ReWindow::OpenGLWindow(
START_APP_TITLE,
START_APP_WIDTH, START_APP_HEIGHT,
GL_MAJOR, GL_MINOR),
camera() {
}
TestGame::TestGameAppWindow::~TestGameAppWindow() {
}
template <typename T>
struct Range {
T min;
T max;
};
template <typename T>
T map(T value, T in_min, T in_max, T out_min, T out_max) {
return (value - in_min) / (in_max - in_min) * (out_max - out_min) + out_min;
}
template <typename T>
T map(T value, const Range<T>& in, const Range<T>& out) {
return map(value, in.min, in.max, out.min, out.max);
}
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->Size({300_px, 100_px});
phys_params_slider_window->Position({95_percent - 300_px, 95_percent-200_px});
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("Gravity: 9.82");
grav_slider->ValueChanged += [&, grav_slider](float value) mutable {
player->gravity = value * 100.f;
grav_slider->Content(std::format("Gravity: {}", Math::Round(player->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: {}", 8));
air_resist_slider->TextColor(Colors::Black);
air_resist_slider->ValueChanged += [&, air_resist_slider](float value) mutable {
player->air_resistance = value * 100.f;
air_resist_slider->Content(std::format("Air Resistance: {}", Math::Floor(player->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: {}", 12));
mass_slider->ValueChanged += [&, mass_slider](float value) mutable {
player->mass = value * 100.f;
mass_slider->Content(std::format("Player Mass: {}", player->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: {}", 1200));
accel_slider->ValueChanged += [&, accel_slider](float value) mutable {
player->acceleration = value * 10000.f;
accel_slider->Content(std::format("Player Accel: {}", player->acceleration));
};
}
void TestGame::TestGameAppWindow::Load() {
Player::sprite = new JGL::Texture("assets/player.png", FilteringMode::NEAREST);
CreateUI();
// 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.
// Non-colliding tile.
if (!loaded_tileset->tiles[cell].collides) continue;
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));
}
}
}
void TestGame::TestGameAppWindow::ApplyOriginTransformation() {
glTranslatef(GetWidth() / 2.f, GetHeight() / 2.f, 0);
}
void TestGame::TestGameAppWindow::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 TestGame::TestGameAppWindow::DrawLevel(const Level *level) const {
/// Draw top layers last.
for (const auto* layer : std::ranges::views::reverse(level->layers))
{
DrawLayer(layer);
}
}
void TestGame::TestGameAppWindow::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 TestGame::TestGameAppWindow::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();
}