329 lines
7.1 KiB
C++
329 lines
7.1 KiB
C++
#include <ReWindow/types/Window.h>
|
|
|
|
#include "ReWindow/Logger.h"
|
|
#include <Data/Level.hpp>
|
|
#include <JGL/JGL.h>
|
|
#include <JUI/Widgets/FpsGraph.hpp>
|
|
#include <JUI/Widgets/Scene.hpp>
|
|
#include <JUI/Widgets/Window.hpp>
|
|
#include <SimpleAABBSolver.hpp>
|
|
|
|
|
|
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;
|
|
};
|
|
|
|
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;
|
|
|
|
const float gravity = 9.8f;
|
|
const float mass = 1.f;
|
|
|
|
velocity.y += (elapsed*gravity*mass);
|
|
|
|
float constant_move_spd = 40;
|
|
|
|
if (InputService::IsKeyDown(Keys::A))
|
|
next_pos.x -= constant_move_spd * elapsed;
|
|
|
|
if (InputService::IsKeyDown(Keys::D))
|
|
next_pos.x += constant_move_spd * elapsed;
|
|
|
|
if (InputService::IsKeyDown(Keys::W))
|
|
next_pos.y -= constant_move_spd * elapsed;
|
|
|
|
if (InputService::IsKeyDown(Keys::S))
|
|
next_pos.y += constant_move_spd * elapsed;
|
|
}
|
|
|
|
void Draw() override {
|
|
J2D::FillRect(Colors::Blue, pos, bbox);
|
|
}
|
|
};
|
|
|
|
class TestGameAppWindow : public ReWindow::OpenGLWindow
|
|
{
|
|
public:
|
|
Level* loaded_level = nullptr;
|
|
Tileset* loaded_tileset = nullptr;
|
|
JGL::Texture* loaded_tilesheet = nullptr;
|
|
|
|
JUI::Scene* scene = nullptr;
|
|
|
|
|
|
bool data_ready = false;
|
|
|
|
std::vector<GameEntity*> entities;
|
|
|
|
TestGameAppWindow() : ReWindow::OpenGLWindow("TestGameAppWindow", 1024, 768, 2, 1)
|
|
{
|
|
|
|
}
|
|
~TestGameAppWindow() override
|
|
{
|
|
|
|
}
|
|
|
|
void Load()
|
|
{
|
|
using namespace JUI::UDimLiterals;
|
|
scene = new JUI::Scene();
|
|
|
|
|
|
auto* fps_graph_window = new JUI::Window(scene);
|
|
fps_graph_window->Size({500_px, 50_px});
|
|
fps_graph_window->AnchorPoint({1, 0.f});
|
|
fps_graph_window->Position({100_percent, 0_percent});
|
|
auto* fps_graph = new JUI::FpsGraph(fps_graph_window->Content());
|
|
fps_graph->Size({500_px, 50_px});
|
|
|
|
|
|
// 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));
|
|
|
|
|
|
|
|
auto* plr = new Player({50, 50});
|
|
entities.emplace_back(plr);
|
|
|
|
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) { }
|
|
|
|
// Touched bottom.
|
|
if (normal.y == 1) { }
|
|
|
|
// Touched left, I think.
|
|
if (normal.x == -1) { }
|
|
|
|
// Touched right, I think.
|
|
if (normal.x == -1) { }
|
|
|
|
entity->next_pos += separation;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update(float elapsed)
|
|
{
|
|
scene->Update(elapsed);
|
|
|
|
Vector2 window_dimensions(GetWidth(), GetHeight());
|
|
|
|
scene->SetViewportSize(window_dimensions);
|
|
JGL::Update(Vector2i(GetWidth(), GetHeight()));
|
|
|
|
CollisionSolve(elapsed);
|
|
|
|
for (auto* entity : entities) {
|
|
entity->Update(elapsed);
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
if (data_ready)
|
|
DrawLevel(loaded_level);
|
|
|
|
|
|
for (auto* entity : entities) {
|
|
entity->Draw();
|
|
}
|
|
|
|
J2D::End();
|
|
scene->Draw();
|
|
}
|
|
|
|
|
|
void OnRefresh(float elapsed) override
|
|
{
|
|
Update(elapsed);
|
|
Draw();
|
|
SwapBuffers();
|
|
}
|
|
};
|
|
|
|
int main()
|
|
{
|
|
ReWindow::Logger::Debug.EnableConsole(false);
|
|
|
|
TestGameAppWindow* app = new TestGameAppWindow();
|
|
|
|
bool success = app->Open();
|
|
|
|
if (!success)
|
|
return -1;
|
|
|
|
while (app->IsOpen())
|
|
app->ManagedRefresh();
|
|
|
|
app->Close();
|
|
|
|
return 0;
|
|
|
|
} |