Documentation
This commit is contained in:
@@ -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")
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
||||
|
||||
};
|
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
@@ -20,6 +20,7 @@ public:
|
||||
int cols;
|
||||
std::vector<Layer> layers;
|
||||
std::string tileset_path;
|
||||
json::value metadata;
|
||||
|
||||
Level() {}
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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));
|
||||
}
|
||||
|
116
src/Layer.cpp
116
src/Layer.cpp
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user