Documentation

This commit is contained in:
2025-05-28 02:47:11 -05:00
parent 5f607c5ae1
commit 4652a36348
8 changed files with 283 additions and 107 deletions

View File

@@ -8,6 +8,8 @@ if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed!")
endif()
set(CMAKE_BUILD_PARALLEL_LEVEL 8)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

View File

@@ -61,6 +61,20 @@ class EditHistoryLogWindow : public JUI::Window {
public:
};
class LayerView : public JUI::Window
{
public:
LayerView() : JUI::Window()
{
this->SetTitle("Layers");
Name("LayerView");
}
explicit LayerView(Widget* parent) : LayerView()
{
Parent(parent);
}
};
class EditorApp : public OpenGLWindow {
public:
EditorCamera camera;
@@ -70,6 +84,7 @@ public:
JUI::Window* tileset_viewer;
JUI::Rect* cell_indicator;
JGL::Texture* test_tilesheet;
LayerView* layer_view;
NewMapDialog* nmd = nullptr;
@@ -140,6 +155,7 @@ public:
/// This provides a mapping from a 1D flat array to a 2D grid.
/// @return The {x,y} coordinate of the grid-cell that maps to the given index.
Vector2i IndexToCell(int index, int width);
void ReplaceFill(int x, int y, int tileid);
void FloodFill(int x, int y, int tileid);
/// This provides a mapping from a 2D grid to a 1D flat array.

View File

@@ -1,8 +1,22 @@
//
// Created by dawsh on 5/12/25.
//
#pragma once
#ifndef ENTITY_HPP
#define ENTITY_HPP
#endif //ENTITY_HPP
/// @class Entity
/// @brief Represents an object or interactive element placed on the map, not necessarily tied to the tile grid.
/// This can include plater spawn points, enemies, pickups, triggers, boxes (my favorite), or any other
/// game specific object that needs to be placed and configured in the level.
class Entity
{
public:
#pragma region json properties
std::string name;
std::string type;
float x;
float y;
#pragma endregion
Entity();
Entity(const json::value& json);
};

View File

@@ -8,6 +8,11 @@
using namespace JJX;
enum class DataFormat
{
SignedInt32,
};
/// @class Layer
/// @brief Represents a single plane of tiles within a level.
///
@@ -22,117 +27,82 @@ using namespace JJX;
class Layer
{
public:
#pragma region json properties
/// The file path to the binary file containing this layer's tile data.
/// @note This path is typically relative to the main level's JSON file.
std::string binary_path;
/// The unique name of the layer (e.g., "Ground", "Decor", "Collisions").
std::string name;
/// The width of the layer in tiles.
int rows;
/// The height of the layer in tiles.
int cols;
/// The width of the individual cells of this layer, measured in pixels.
int cell_width;
/// The height of the individual cells of this layer, measured in pixels.
int cell_height;
int** cells;
float parallax_x;
float parallax_y;
bool visible;
float opacity;
#pragma endregion
#pragma region binary data storage
int** cells;
#pragma endregion
/// The default constructor for Layer initializes member variables to default, zero values.
Layer() {}
/// @note The tile-ID grid is initialized to a null-pointer, as well.
Layer();
/// This constructor creates an empty layer of a given size, and cell-size.
Layer(int rows, int cols, int cell_width, int cell_height)
{
this->rows = rows;
this->cols = cols;
this->cell_width = cell_width;
this->cell_height = cell_height;
InitGrid();
}
/// @note The tile-ID grid is initialized to a null-pointer.
Layer(int rows, int cols, int cell_width, int cell_height);
/// Constructs a Layer object by deserializing metadata from a JSON object.
/// The binary tile data will need to be loaded separately using `ReadFromFile()`
explicit Layer(const json::value& json)
explicit Layer(const json::value& json);
/// Destructor for Layer cleans up all dynamically allocated memory for the tile grid.
~Layer()
{
Deserialize(json);
DeleteGrid();
}
/// Destructor for Layer cleans up
~Layer() = default;
/// Constructs a layer object from the JSON contained in the file at the given path.
explicit Layer(const std::filesystem::path& path);
explicit Layer(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
Deserialize(json);
}
/// Serializes the current layer object's metadata into a JSON object.
/// This does *NOT* include the actual tile grid data, which is stored in a separate binary file.
json::value Serialize() const;
json::value Serialize() const
{
json::object data;
data["name"] = name;
data["binary-path"] = binary_path;
data["rows"] = (float)rows;
data["cols"] = (float)cols;
data["cell-width"] = (float)cell_width;
data["cell-height"] = (float)cell_height;
/// Deserializes a JSON object into the current Layer object's metadata.
/// This also allocates the internal tile grid based on the deserialized width and height,
/// but does NOT load the actual tile grid data; use ReadBinaryData() for that.
void Deserialize(const json::value& json);
void InitGrid();
void DeleteGrid();
return data;
}
void Deserialize(const json::value& json)
{
name = json["name"].as_string();
binary_path = json["binary-path"].as_string();
rows = json["rows"].number.value();
cols = json["cols"].number.value();
cell_width = json["cell-width"].number.value();
cell_height = json["cell-height"].number.value();
}
void InitGrid()
{
cells = new int*[rows];
for (int i = 0; i < rows; i++)
cells[i] = new int[cols];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
cells[x][y] = -1;
}
}
}
void DeleteGrid()
{
for (int i = 0; i < rows; i++)
delete[] cells[i];
delete[] cells;
}
/// Resizes the layer's dimensions and reinitializes the tile data.
/// Existing tile data will be lost or truncated.
/// Resizes the layer's dimensions and reinitialize the tile data.
///
void Resize(int newWidth, int newHeight)
{
}
/// This method creates a new internal tile grid with the specified dimensions.
/// Existing tile data is copied to the new grid, preserving data within the
/// overlapping region. Any data outside the new bounds is truncated, and
/// new cells are initialized to -1 (empty tile).
///
void Resize(int newWidth, int newHeight);
void LoadFromDataBuffer(const int* buffer)
{
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
int index = (x * cols + y);
cells[x][y] = buffer[index];
}
}
}
void LoadFromDataBuffer(const int* buffer);
void WriteToDataBuffer(int* buffer)
{
@@ -150,6 +120,7 @@ public:
/// Writes the current tile grid data to a specified raw binary file.
/// The file path should typically be stored in the layer's JSON metadata.
/// @param path
/// @return True if the binary data was loaded successfully, false otherwise.
bool WriteBinaryData(const std::filesystem::path& path)
{
int* buffer = new int[rows*cols];
@@ -187,6 +158,4 @@ public:
return true;
}
};
};

View File

@@ -20,6 +20,7 @@ public:
int cols;
std::vector<Layer> layers;
std::string tileset_path;
json::value metadata;
Level() {}

View File

@@ -49,7 +49,11 @@ int main()
Layer layer(128, 128, 16, 16);
layer.binary_path = "test.lvl";
layer.ReadBinaryData();
layer.Load();
layer.Resize(64, 64);
layer.Save();
level.layers.push_back(layer);

View File

@@ -20,6 +20,48 @@ Vector2i EditorApp::IndexToCell(int index, int width)
return {x, y};
}
void EditorApp::ReplaceFill(int x, int y, int tileid)
{
std::vector<Vector2i> matching_adjacent_cells;
int replace = GetTile(x, y);
SetTile(x, y, tileid);
int range = 1;
bool exhausted = false;
while (!exhausted)
{
int stopped = 0;
if (GetTile(x-range, y) == replace)
matching_adjacent_cells.push_back({x-range, y});
else
stopped++;
if (GetTile(x+range, y) == replace)
matching_adjacent_cells.push_back({x+range, y});
else
stopped++;
if (GetTile(x, y-range) == replace)
matching_adjacent_cells.push_back({x, y-range});
else
stopped++;
if (GetTile(x, y+range) == replace)
matching_adjacent_cells.push_back({x, y+range});
else
stopped++;
if (stopped == 4)
exhausted = true;
range++;
}
for (auto& coords : matching_adjacent_cells)
SetTile(coords, tileid);
}
void EditorApp::FloodFill(int x, int y, int tileid)
{
@@ -32,10 +74,6 @@ void EditorApp::FloodFill(int x, int y, int tileid)
if (GetTile(x, y-1) == -1) FloodFill(x, y-1, tileid);
if (GetTile(x, y+1) == -1) FloodFill(x, y+1, tileid);
}
}
int EditorApp::CellToIndex(Vector2i cell, int width)
@@ -94,7 +132,13 @@ void EditorApp::LoadTestFile()
//if (!std::filesystem::exists("test_level/metadata.json"))
Level test_level = Level(std::filesystem::path("level.json"));
grid_rows = test_level.layers[0].rows;
grid_cols = test_level.layers[0].cols;
PopulateQuads();
InitGrid();
//std::string level_metadata_text = read_file_contents("test_level/metadata.json");
std::ifstream input;
@@ -163,14 +207,14 @@ void EditorApp::LoadMisc()
tileset_width = tileset.rows;//texture_size.x / grid_pixel_width;
tileset_height = tileset.cols;//texture_size.y / grid_pixel_height;
PopulateQuads();
InitGrid();
if (std::filesystem::exists("test.lvl"))
LoadTestFile();
}
JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent)
@@ -279,6 +323,9 @@ void EditorApp::CreateWidgets()
nmd = new NewMapDialog(scene);
nmd->Close();
layer_view = new LayerView(scene);
layer_view->Open();
auto* topbar = new JUI::UtilityBar(scene);
auto* file = topbar->AddSubmenu("File");
@@ -424,10 +471,10 @@ int EditorApp::GetTile(const Vector2i& cell)
if (cell.y < 0) return -2;
// out of bounds horizontally
if (cell.x > grid_rows) return -2;
if (cell.x >= grid_rows) return -2;
// out of bounds vertically
if (cell.y > grid_cols) return -2;
if (cell.y >= grid_cols) return -2;
return grid[cell.x][cell.y];
}
@@ -443,10 +490,10 @@ void EditorApp::SetTile(const Vector2i& cell, int tileID)
if (cell.y < 0) return;
// out of bounds horizontally
if (cell.x > grid_rows) return;
if (cell.x >= grid_rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
if (cell.y >= grid_cols) return;
grid[cell.x][cell.y] = tileID;
}
@@ -467,6 +514,8 @@ void EditorApp::PlaceTileBrush(const Vector2i& cell)
if (IsKeyDown(Keys::B))
FloodFill(cell.x, cell.y, selected_quad);
else if (IsKeyDown(Keys::R))
ReplaceFill(cell.x, cell.y, selected_quad);
else
SetTile(cell, selected_quad);
}
@@ -548,7 +597,7 @@ void EditorApp::DrawGrid(const Color4& color)
float sw = GetWidth();
float sh = GetHeight();
DrawGrid({{0,0}, {sw, sh}}, color);
DrawGrid({{0,0}, {static_cast<float>(grid_rows*grid_pixel_width), static_cast<float>(grid_cols*grid_pixel_height)}}, color);
}
void EditorApp::DrawCellPointerOutline()
@@ -568,11 +617,16 @@ void EditorApp::DrawTiles()
auto quad_idx = grid[gx][gy];
Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height);
//if ((gx+gy) % 2 == 0)
//J2D::FillRect(Colors::Purples::Fuchsia, pos, {16, 16});
if (quad_idx < 0)
continue;
auto quad = quads[quad_idx];
Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height);
J2D::DrawPartialSprite(test_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}

View File

@@ -1 +1,117 @@
#include <Layer.hpp>
Layer::Layer(): rows(0), cols(0), cell_width(0), cell_height(0), parallax_x(0), parallax_y(0), cells(nullptr)
{ }
Layer::Layer(int rows, int cols, int cell_width, int cell_height): cells(nullptr), parallax_x(0), parallax_y(0)
{
this->rows = rows;
this->cols = cols;
this->cell_width = cell_width;
this->cell_height = cell_height;
}
Layer::Layer(const json::value& json)
{
Deserialize(json);
}
Layer::Layer(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
Deserialize(json);
}
json::value Layer::Serialize() const
{
json::object data;
data["name"] = name;
data["binary-path"] = binary_path;
data["rows"] = (float)rows;
data["cols"] = (float)cols;
data["cell-width"] = (float)cell_width;
data["cell-height"] = (float)cell_height;
return data;
}
void Layer::Deserialize(const json::value& json)
{
name = json["name"].as_string();
binary_path = json["binary-path"].as_string();
rows = json["rows"].number.value();
cols = json["cols"].number.value();
cell_width = json["cell-width"].number.value();
cell_height = json["cell-height"].number.value();
}
void Layer::InitGrid()
{
cells = new int*[rows];
for (int i = 0; i < rows; i++)
cells[i] = new int[cols];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
cells[x][y] = -1;
}
}
}
void Layer::DeleteGrid()
{
for (int i = 0; i < rows; i++)
delete[] cells[i];
delete[] cells;
}
void Layer::Resize(int newWidth, int newHeight) {
// If dimensions are unchanged, do nothing.
if (newWidth == rows && newHeight == cols)
return;
int best_fit_rows = J3ML::Math::Min(rows, newWidth);
int best_fit_cols = J3ML::Math::Min(cols, newHeight);
int old_data_length = rows*cols;
int* old_data_contiguous = new int[old_data_length];
for (int x = 0; x < best_fit_rows; x++) {
for (int y = 0; y < best_fit_cols; y++) {
int index = (x * best_fit_cols + y);
old_data_contiguous[index] = cells[x][y];
}
}
DeleteGrid();
this->rows = newWidth;
this->cols = newHeight;
InitGrid();
for (int x = 0; x < best_fit_rows; x++) {
for (int y = 0; y < best_fit_cols; y++) {
int index = (x * best_fit_cols + y);
cells[x][y] = old_data_contiguous[index];
}
}
delete[] old_data_contiguous;
}
void Layer::LoadFromDataBuffer(const int* buffer)
{
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
int index = (x * cols + y);
cells[x][y] = buffer[index];
}
}
}