Working on tileset-metadata editing.

This commit is contained in:
2025-06-16 22:58:42 -05:00
parent b8bed66928
commit 214f27fa21
5 changed files with 161 additions and 76 deletions

View File

@@ -252,6 +252,8 @@ public:
void DrawLevel(const Level* level) const;
bool IsMouseGridCellInBounds();
void Draw();
// TODO: Closing the app nominally doesn't work on Linux.

View File

@@ -25,6 +25,7 @@ struct Tile
std::string name;
Quad quad;
json::value metadata;
bool collides;
};
/// TODO: Only generate tile entry for tiles that are used in the level, or have been assigned custom metadata.
@@ -131,4 +132,18 @@ protected:
/// Fill the tile-id lookup table with their respective partial-sprite bounding boxes.
void ComputeQuads();
void FillTiles() {
// Generate some tiles
tiles.reserve(rows*cols);
for (int i = 0 ; i < this->rows*this->cols; i++)
{
Tile tile;
tile.id = i;
tile.quad = quads[i];
tile.name = std::format("tile_{}", i);
tile.metadata = json::value();
tiles.emplace(tiles.begin() + i, tile);
}
}
};

View File

@@ -221,8 +221,6 @@ void EditorApp::LoadLevel(const std::filesystem::path& level_meta_path)
for (auto layer : loaded_level->layers)
layer->Load();
layer_view->UpdateComponents(loaded_level);
data_ready = true;
}
@@ -443,6 +441,11 @@ bool EditorApp::Open()
layer_view->UpdateComponents(loaded_level);
for (auto tile : loaded_tileset->tiles) {
std::cout << tile.id << ", " << tile.name << std::endl;
}
return true;
}
@@ -763,6 +766,18 @@ void EditorApp::DrawLevel(const Level* level) const
}
}
bool EditorApp::IsMouseGridCellInBounds() {
Vector2i cell = GetGridCellFromMouse();
if (cell.x < 0) return false;
if (cell.y < 0) return false;
if (cell.x >= loaded_level->rows) return false;
if (cell.y >= loaded_level->cols) return false;
return true;
}
void EditorApp::Draw()
{
glClearColor(bg_color.RN(), bg_color.GN(), bg_color.BN(), 1);
@@ -788,6 +803,28 @@ void EditorApp::Draw()
DrawCellPointerOutline();
Tile selected_tile = loaded_tileset->tiles[selected_quad];
J2D::DrawString(Colors::White, std::format("Selected Tile: {}", selected_tile.name), 16, 48, 16);
Vector2i cell = GetGridCellFromMouse();
int hovered_tile_id = GetTile(cell);
if (hovered_tile_id > 0) {
Tile hovered_tile = loaded_tileset->tiles[hovered_tile_id];
J2D::DrawString(Colors::White, std::format("Hovered Tile: {}", hovered_tile.name), 16, 64, 16);
}
glPopMatrix();
auto maus = GetMouseCoordinates();
@@ -799,8 +836,6 @@ void EditorApp::Draw()
J2D::DrawPoint(Colors::White, bruhbruh, 4);
J2D::End();
scene->Draw();
}

View File

@@ -6,6 +6,9 @@ Tileset::Tileset():
tile_count(0), tiles_per_row(0)
{ }
Tileset::Tileset(const std::string& name, const std::filesystem::path& texture_path, int tex_width, int tex_height, int tile_width, int tile_height,
int tile_spacing)
{
@@ -29,27 +32,18 @@ Tileset::Tileset(const std::string& name, const std::filesystem::path& texture_p
ComputeQuads();
// Generate some tiles
for (int i = 0 ; i < this->rows*this->cols; i++)
{
Tile tile;
tile.id = i;
tile.quad = quads[i];
tile.name = std::format("tile_{}", i);
tile.metadata = json::value();
tiles.push_back(tile);
}
FillTiles();
}
Tileset::Tileset(const json::value& json)
{
Deserialize(json);
}
Tileset::Tileset(const std::filesystem::path& filePath)
{
auto [json, err] = json::parse(read_file_contents(filePath));
Deserialize(json);
}
@@ -66,20 +60,27 @@ void Tileset::Deserialize(const json::value& json)
this->tex_width = (int)json["texture-width"].number.value();
this->tex_height = (int)json["texture-height"].number.value();
this->rows = J3ML::Math::Floor(this->tex_width / this->tile_width);
this->cols = J3ML::Math::Floor(this->tex_height / this->tile_height);
ComputeQuads();
FillTiles();
if (json.as_object().contains("tiles"))
{
auto tilemeta = json["tiles"].as_array();
for (json::value& v : tilemeta)
{
// this->tiles.push_back(v);
int id = v["id"].number.value();
tiles[id].name = v["name"].as_string();
if (v.contains("collides"))
tiles[id].collides = v["collides"].boolean.value_or(true);
// this->tiles.push_back(v);
}
}
this->rows = J3ML::Math::Floor(this->tex_width / this->tile_width);
this->cols = J3ML::Math::Floor(this->tex_height / this->tile_height);
ComputeQuads();
}
json::value Tileset::Serialize() const
@@ -121,6 +122,7 @@ json::value Tileset::Serialize() const
tile_json["name"] = tile.name;
tile_json["id"] = (float)tile.id;
tile_json["metadata"] = tile.metadata;
tile_json["collides"] = json::boolean(tile.collides);
json::array quad;
quad.push_back((float)tile.quad.x);
quad.push_back((float)tile.quad.y);

View File

@@ -8,6 +8,7 @@
#include <JUI/Widgets/Window.hpp>
#include <SimpleAABBSolver.hpp>
#include "App/EditorCamera.hpp"
#include "JUI/Widgets/ListLayout.hpp"
#include "JUI/Widgets/Slider.hpp"
@@ -19,6 +20,7 @@ float acceleration = 1200;
JGL::Texture* player_tex;
// Implemented in next version of JUI.
class LabeledSlider : public JUI::Slider, public JUI::TextBase {
public:
LabeledSlider() : Slider(), TextBase() {
@@ -51,81 +53,83 @@ protected:
private:
};
class GameEntity
{
public:
GameEntity(const Vector2& spawn_pos) {
pos = spawn_pos;
next_pos = spawn_pos;
}
class GameEntity
{
public:
virtual void Update(float elapsed) = 0;
GameEntity(const Vector2& spawn_pos) {
pos = spawn_pos;
next_pos = spawn_pos;
}
virtual void Draw() = 0;
virtual void Update(float elapsed) = 0;
Vector2 pos;
Vector2 next_pos;
Vector2 bbox;
Vector2 velocity;
bool noclip = false;
bool on_ground = true;
};
virtual void Draw() = 0;
class Player : public GameEntity
{
public:
Player(const Vector2& spawn_pos) : GameEntity(spawn_pos) {
bbox = {16, 24};
}
Vector2 pos;
Vector2 next_pos;
Vector2 bbox;
Vector2 velocity;
bool noclip = false;
bool on_ground = true;
};
~Player() {
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;
}
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);
velocity.x *= 1 - (elapsed * air_resistance);
//velocity.y *= 1 - (elapsed * air_resistance);
//if (on_ground)
//if (on_ground)
//velocity.x *= friction;
velocity.y += (elapsed*gravity*mass);
velocity.y += (elapsed*gravity*mass);
next_pos += velocity * elapsed;
next_pos += velocity * elapsed;
if (Math::Abs(velocity.y) > 2)
on_ground = false;
if (Math::Abs(velocity.y) > 2)
on_ground = false;
if (InputService::IsKeyDown(Keys::A))
velocity.x -= acceleration * elapsed;
if (InputService::IsKeyDown(Keys::A))
velocity.x -= acceleration * elapsed;
if (InputService::IsKeyDown(Keys::D))
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::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);
}
};
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;
@@ -133,6 +137,8 @@ public:
JUI::Scene* scene = nullptr;
Player* player = nullptr;
bool data_ready = false;
std::vector<GameEntity*> entities;
@@ -151,8 +157,7 @@ public:
using namespace JUI::UDimLiterals;
scene = new JUI::Scene();
player_tex = new JGL::Texture("assets/player.png");
player_tex = new JGL::Texture("assets/player.png", FilteringMode::NEAREST);
//auto* fps_graph_window = new JUI::Window(scene);
//fps_graph_window->Size({500_px, 50_px});
@@ -220,8 +225,6 @@ public:
// 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.
@@ -267,8 +270,10 @@ public:
for (int i = 0; i < 100; i++) {
auto* plr = new Player({50, 50});
entities.emplace_back(plr);
player = plr;
}
camera.DefaultState();
return true;
}
@@ -357,13 +362,31 @@ public:
}
}
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);
@@ -419,6 +442,12 @@ public:
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);
@@ -428,6 +457,7 @@ public:
entity->Draw();
}
glPopMatrix();
J2D::End();
scene->Draw();
}
@@ -476,6 +506,7 @@ public:
};
int main()
{
ReWindow::Logger::Debug.EnableConsole(false);