refactor: Remove layer-related GridPoint properties, fix layer z-index
- Remove color, color_overlay, tilesprite, tile_overlay, uisprite from UIGridPoint - these are now accessed through named layers - Keep only walkable and transparent as protected GridPoint properties - Update isProtectedLayerName() to only protect walkable/transparent - Fix default layer z-index to -1 (below entities) instead of 0 - Remove dead rendering code from GridChunk (layers handle rendering) - Update cos_level.py demo to use explicit layer definitions - Update UITestScene.cpp to use layer API instead of GridPoint properties Part of #150 - Grid layer system migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a258613faa
commit
42fcd3417e
|
|
@ -14,7 +14,7 @@ GridChunk::GridChunk(int chunk_x, int chunk_y, int width, int height,
|
||||||
width(width), height(height),
|
width(width), height(height),
|
||||||
world_x(world_x), world_y(world_y),
|
world_x(world_x), world_y(world_y),
|
||||||
cells(width * height),
|
cells(width * height),
|
||||||
dirty(true), texture_initialized(false),
|
dirty(true),
|
||||||
parent_grid(parent)
|
parent_grid(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
@ -30,60 +30,8 @@ void GridChunk::markDirty() {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GridChunk::ensureTexture(int cell_width, int cell_height) {
|
// #150 - Removed ensureTexture/renderToTexture - base layer rendering removed
|
||||||
unsigned int required_width = width * cell_width;
|
// GridChunk now only provides data storage for GridPoints
|
||||||
unsigned int required_height = height * cell_height;
|
|
||||||
|
|
||||||
if (texture_initialized &&
|
|
||||||
cached_texture.getSize().x == required_width &&
|
|
||||||
cached_texture.getSize().y == required_height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cached_texture.create(required_width, required_height)) {
|
|
||||||
texture_initialized = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
texture_initialized = true;
|
|
||||||
dirty = true; // Force re-render after resize
|
|
||||||
cached_sprite.setTexture(cached_texture.getTexture());
|
|
||||||
}
|
|
||||||
|
|
||||||
void GridChunk::renderToTexture(int cell_width, int cell_height,
|
|
||||||
std::shared_ptr<PyTexture> texture) {
|
|
||||||
ensureTexture(cell_width, cell_height);
|
|
||||||
if (!texture_initialized) return;
|
|
||||||
|
|
||||||
cached_texture.clear(sf::Color::Transparent);
|
|
||||||
|
|
||||||
sf::RectangleShape rect;
|
|
||||||
rect.setSize(sf::Vector2f(cell_width, cell_height));
|
|
||||||
rect.setOutlineThickness(0);
|
|
||||||
|
|
||||||
// Render all cells in this chunk
|
|
||||||
for (int y = 0; y < height; ++y) {
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
const auto& cell = at(x, y);
|
|
||||||
sf::Vector2f pixel_pos(x * cell_width, y * cell_height);
|
|
||||||
|
|
||||||
// Draw background color
|
|
||||||
rect.setPosition(pixel_pos);
|
|
||||||
rect.setFillColor(cell.color);
|
|
||||||
cached_texture.draw(rect);
|
|
||||||
|
|
||||||
// Draw tile sprite if available
|
|
||||||
if (texture && cell.tilesprite != -1) {
|
|
||||||
sf::Sprite sprite = texture->sprite(cell.tilesprite, pixel_pos,
|
|
||||||
sf::Vector2f(1.0f, 1.0f));
|
|
||||||
cached_texture.draw(sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cached_texture.display();
|
|
||||||
dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sf::FloatRect GridChunk::getWorldBounds(int cell_width, int cell_height) const {
|
sf::FloatRect GridChunk::getWorldBounds(int cell_width, int cell_height) const {
|
||||||
return sf::FloatRect(
|
return sf::FloatRect(
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ class UIGrid;
|
||||||
class PyTexture;
|
class PyTexture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #123 - Grid chunk for sub-grid rendering system
|
* #123 - Grid chunk for sub-grid data storage
|
||||||
|
* #150 - Rendering removed; layers now handle all rendering
|
||||||
*
|
*
|
||||||
* Each chunk represents a CHUNK_SIZE x CHUNK_SIZE portion of the grid.
|
* Each chunk represents a CHUNK_SIZE x CHUNK_SIZE portion of the grid.
|
||||||
* Chunks have their own RenderTexture and dirty flag for efficient
|
* Chunks store GridPoint data for pathfinding and game logic.
|
||||||
* incremental rendering - only dirty chunks are re-rendered.
|
|
||||||
*/
|
*/
|
||||||
class GridChunk {
|
class GridChunk {
|
||||||
public:
|
public:
|
||||||
|
|
@ -30,16 +30,13 @@ public:
|
||||||
// World position (in cell coordinates)
|
// World position (in cell coordinates)
|
||||||
int world_x, world_y;
|
int world_x, world_y;
|
||||||
|
|
||||||
// Cell data for this chunk
|
// Cell data for this chunk (pathfinding properties only)
|
||||||
std::vector<UIGridPoint> cells;
|
std::vector<UIGridPoint> cells;
|
||||||
|
|
||||||
// Cached rendering
|
// Dirty flag (for layer sync if needed)
|
||||||
sf::RenderTexture cached_texture;
|
|
||||||
sf::Sprite cached_sprite;
|
|
||||||
bool dirty;
|
bool dirty;
|
||||||
bool texture_initialized;
|
|
||||||
|
|
||||||
// Parent grid reference (for texture access)
|
// Parent grid reference
|
||||||
UIGrid* parent_grid;
|
UIGrid* parent_grid;
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
|
|
@ -50,16 +47,9 @@ public:
|
||||||
UIGridPoint& at(int local_x, int local_y);
|
UIGridPoint& at(int local_x, int local_y);
|
||||||
const UIGridPoint& at(int local_x, int local_y) const;
|
const UIGridPoint& at(int local_x, int local_y) const;
|
||||||
|
|
||||||
// Mark chunk as needing re-render
|
// Mark chunk as dirty
|
||||||
void markDirty();
|
void markDirty();
|
||||||
|
|
||||||
// Ensure texture is properly sized
|
|
||||||
void ensureTexture(int cell_width, int cell_height);
|
|
||||||
|
|
||||||
// Render chunk content to cached texture
|
|
||||||
void renderToTexture(int cell_width, int cell_height,
|
|
||||||
std::shared_ptr<PyTexture> texture);
|
|
||||||
|
|
||||||
// Get pixel bounds of this chunk in world coordinates
|
// Get pixel bounds of this chunk in world coordinates
|
||||||
sf::FloatRect getWorldBounds(int cell_width, int cell_height) const;
|
sf::FloatRect getWorldBounds(int cell_width, int cell_height) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -407,9 +407,9 @@ std::shared_ptr<GridLayer> UIGrid::getLayerByName(const std::string& name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UIGrid::isProtectedLayerName(const std::string& name) {
|
bool UIGrid::isProtectedLayerName(const std::string& name) {
|
||||||
// #150 - These names are reserved for GridPoint properties
|
// #150 - These names are reserved for GridPoint pathfinding properties
|
||||||
static const std::vector<std::string> protected_names = {
|
static const std::vector<std::string> protected_names = {
|
||||||
"walkable", "transparent", "color", "color_overlay"
|
"walkable", "transparent"
|
||||||
};
|
};
|
||||||
for (const auto& pn : protected_names) {
|
for (const auto& pn : protected_names) {
|
||||||
if (name == pn) return true;
|
if (name == pn) return true;
|
||||||
|
|
@ -896,8 +896,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
// Default: {"tilesprite": "tile"} when layers not provided
|
// Default: {"tilesprite": "tile"} when layers not provided
|
||||||
// Empty dict: no rendering layers (entity storage + pathfinding only)
|
// Empty dict: no rendering layers (entity storage + pathfinding only)
|
||||||
if (layers_obj == nullptr) {
|
if (layers_obj == nullptr) {
|
||||||
// Default layer: single TileLayer named "tilesprite"
|
// Default layer: single TileLayer named "tilesprite" (z_index -1 = below entities)
|
||||||
self->data->addTileLayer(0, texture_ptr, "tilesprite");
|
self->data->addTileLayer(-1, texture_ptr, "tilesprite");
|
||||||
} else if (layers_obj != Py_None) {
|
} else if (layers_obj != Py_None) {
|
||||||
if (!PyDict_Check(layers_obj)) {
|
if (!PyDict_Check(layers_obj)) {
|
||||||
PyErr_SetString(PyExc_TypeError, "layers must be a dict mapping names to types ('color' or 'tile')");
|
PyErr_SetString(PyExc_TypeError, "layers must be a dict mapping names to types ('color' or 'tile')");
|
||||||
|
|
@ -907,7 +907,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
PyObject* key;
|
PyObject* key;
|
||||||
PyObject* value;
|
PyObject* value;
|
||||||
Py_ssize_t pos = 0;
|
Py_ssize_t pos = 0;
|
||||||
int layer_z = 0; // Auto-increment z_index for each layer
|
int layer_z = -1; // Start at -1 (below entities), decrement for each layer
|
||||||
|
|
||||||
while (PyDict_Next(layers_obj, &pos, &key, &value)) {
|
while (PyDict_Next(layers_obj, &pos, &key, &value)) {
|
||||||
if (!PyUnicode_Check(key)) {
|
if (!PyUnicode_Check(key)) {
|
||||||
|
|
@ -929,9 +929,9 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(layer_type, "color") == 0) {
|
if (strcmp(layer_type, "color") == 0) {
|
||||||
self->data->addColorLayer(layer_z++, layer_name);
|
self->data->addColorLayer(layer_z--, layer_name);
|
||||||
} else if (strcmp(layer_type, "tile") == 0) {
|
} else if (strcmp(layer_type, "tile") == 0) {
|
||||||
self->data->addTileLayer(layer_z++, texture_ptr, layer_name);
|
self->data->addTileLayer(layer_z--, texture_ptr, layer_name);
|
||||||
} else {
|
} else {
|
||||||
PyErr_Format(PyExc_ValueError, "Unknown layer type '%s' (expected 'color' or 'tile')", layer_type);
|
PyErr_Format(PyExc_ValueError, "Unknown layer type '%s' (expected 'color' or 'tile')", layer_type);
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
#include <cstring> // #150 - for strcmp
|
#include <cstring> // #150 - for strcmp
|
||||||
|
|
||||||
UIGridPoint::UIGridPoint()
|
UIGridPoint::UIGridPoint()
|
||||||
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
|
: walkable(false), transparent(false), grid_x(-1), grid_y(-1), parent_grid(nullptr)
|
||||||
tilesprite(-1), tile_overlay(-1), uisprite(-1), grid_x(-1), grid_y(-1), parent_grid(nullptr)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// Utility function to convert sf::Color to PyObject*
|
// Utility function to convert sf::Color to PyObject*
|
||||||
|
|
@ -53,28 +52,7 @@ sf::Color PyObject_to_sfColor(PyObject* obj) {
|
||||||
return sf::Color(r, g, b, a);
|
return sf::Color(r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGridPoint::get_color(PyUIGridPointObject* self, void* closure) {
|
// #150 - Removed get_color/set_color - now handled by layers
|
||||||
if (reinterpret_cast<long>(closure) == 0) { // color
|
|
||||||
return sfColor_to_PyObject(self->data->color);
|
|
||||||
} else { // color_overlay
|
|
||||||
return sfColor_to_PyObject(self->data->color_overlay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
|
||||||
sf::Color color = PyObject_to_sfColor(value);
|
|
||||||
// Check if an error occurred during conversion
|
|
||||||
if (PyErr_Occurred()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reinterpret_cast<long>(closure) == 0) { // color
|
|
||||||
self->data->color = color;
|
|
||||||
} else { // color_overlay
|
|
||||||
self->data->color_overlay = color;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
|
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
|
||||||
if (reinterpret_cast<long>(closure) == 0) { // walkable
|
if (reinterpret_cast<long>(closure) == 0) { // walkable
|
||||||
|
|
@ -110,36 +88,11 @@ int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, voi
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGridPoint::get_int_member(PyUIGridPointObject* self, void* closure) {
|
// #150 - Removed get_int_member/set_int_member - now handled by layers
|
||||||
switch(reinterpret_cast<long>(closure)) {
|
|
||||||
case 0: return PyLong_FromLong(self->data->tilesprite);
|
|
||||||
case 1: return PyLong_FromLong(self->data->tile_overlay);
|
|
||||||
case 2: return PyLong_FromLong(self->data->uisprite);
|
|
||||||
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int UIGridPoint::set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
|
||||||
long val = PyLong_AsLong(value);
|
|
||||||
if (PyErr_Occurred()) return -1;
|
|
||||||
|
|
||||||
switch(reinterpret_cast<long>(closure)) {
|
|
||||||
case 0: self->data->tilesprite = val; break;
|
|
||||||
case 1: self->data->tile_overlay = val; break;
|
|
||||||
case 2: self->data->uisprite = val; break;
|
|
||||||
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyGetSetDef UIGridPoint::getsetters[] = {
|
PyGetSetDef UIGridPoint::getsetters[] = {
|
||||||
{"color", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color", (void*)0},
|
|
||||||
{"color_overlay", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color overlay", (void*)1},
|
|
||||||
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
|
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
|
||||||
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
|
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
|
||||||
{"tilesprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile sprite index", (void*)0},
|
|
||||||
{"tile_overlay", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile overlay sprite index", (void*)1},
|
|
||||||
{"uisprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "UI sprite index", (void*)2},
|
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -148,9 +101,9 @@ PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
|
||||||
if (!self->data) ss << "<GridPoint (invalid internal object)>";
|
if (!self->data) ss << "<GridPoint (invalid internal object)>";
|
||||||
else {
|
else {
|
||||||
auto gp = self->data;
|
auto gp = self->data;
|
||||||
ss << "<GridPoint (walkable=" << (gp->walkable ? "True" : "False") << ", transparent=" << (gp->transparent ? "True" : "False") <<
|
ss << "<GridPoint (walkable=" << (gp->walkable ? "True" : "False")
|
||||||
", tilesprite=" << gp->tilesprite << ", tile_overlay=" << gp->tile_overlay << ", uisprite=" << gp->uisprite <<
|
<< ", transparent=" << (gp->transparent ? "True" : "False")
|
||||||
")>";
|
<< ") at (" << gp->grid_x << ", " << gp->grid_y << ")>";
|
||||||
}
|
}
|
||||||
std::string repr_str = ss.str();
|
std::string repr_str = ss.str();
|
||||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||||
|
|
|
||||||
|
|
@ -33,24 +33,20 @@ typedef struct {
|
||||||
std::shared_ptr<UIEntity> entity;
|
std::shared_ptr<UIEntity> entity;
|
||||||
} PyUIGridPointStateObject;
|
} PyUIGridPointStateObject;
|
||||||
|
|
||||||
// UIGridPoint - revised grid data for each point
|
// UIGridPoint - grid cell data for pathfinding and layer access
|
||||||
|
// #150 - Layer-related properties (color, tilesprite, etc.) removed; now handled by layers
|
||||||
class UIGridPoint
|
class UIGridPoint
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
sf::Color color, color_overlay;
|
bool walkable, transparent; // Pathfinding/FOV properties
|
||||||
bool walkable, transparent;
|
int grid_x, grid_y; // Position in parent grid
|
||||||
int tilesprite, tile_overlay, uisprite;
|
UIGrid* parent_grid; // Parent grid reference for TCOD sync
|
||||||
int grid_x, grid_y; // Position in parent grid
|
|
||||||
UIGrid* parent_grid; // Parent grid reference for TCOD sync
|
|
||||||
UIGridPoint();
|
UIGridPoint();
|
||||||
|
|
||||||
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
// Built-in property accessors (walkable, transparent only)
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyObject* get_color(PyUIGridPointObject* self, void* closure);
|
|
||||||
static PyObject* get_int_member(PyUIGridPointObject* self, void* closure);
|
|
||||||
static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
||||||
static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure);
|
|
||||||
static PyObject* repr(PyUIGridPointObject* self);
|
static PyObject* repr(PyUIGridPointObject* self);
|
||||||
|
|
||||||
// #150 - Dynamic property access for named layers
|
// #150 - Dynamic property access for named layers
|
||||||
|
|
|
||||||
|
|
@ -105,16 +105,16 @@ UITestScene::UITestScene(GameEngine* g) : Scene(g)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// UIGrid test: (in grid cells) ( in screen pixels )
|
// UIGrid test: (in grid cells) ( in screen pixels )
|
||||||
// constructor args: w h texture x y w h
|
// constructor args: w h texture x y w h
|
||||||
auto e5 = std::make_shared<UIGrid>(4, 4, ptex, sf::Vector2f(550, 150), sf::Vector2f(200, 200));
|
auto e5 = std::make_shared<UIGrid>(4, 4, ptex, sf::Vector2f(550, 150), sf::Vector2f(200, 200));
|
||||||
e5->zoom=2.0;
|
e5->zoom=2.0;
|
||||||
e5->points[0].color = sf::Color(255, 0, 0);
|
|
||||||
e5->points[1].tilesprite = 1;
|
// #150 - GridPoint no longer has color/tilesprite properties
|
||||||
e5->points[5].color = sf::Color(0, 255, 0);
|
// Use layers for visual rendering; GridPoint only has walkable/transparent
|
||||||
e5->points[6].tilesprite = 2;
|
// The default "tilesprite" TileLayer is created automatically
|
||||||
e5->points[10].color = sf::Color(0, 0, 255);
|
// Example: e5->layers[0]->at(x, y) = tile_index for TileLayer
|
||||||
e5->points[11].tilesprite = 3;
|
e5->points[0].walkable = true;
|
||||||
e5->points[15].color = sf::Color(255, 255, 255);
|
e5->points[0].transparent = true;
|
||||||
|
|
||||||
ui_elements->push_back(e5);
|
ui_elements->push_back(e5);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,9 @@ class Level:
|
||||||
self.height = height
|
self.height = height
|
||||||
#self.graph = [(0, 0, width, height)]
|
#self.graph = [(0, 0, width, height)]
|
||||||
self.graph = RoomGraph( (0, 0, width, height) )
|
self.graph = RoomGraph( (0, 0, width, height) )
|
||||||
self.grid = mcrfpy.Grid(grid_size=(width, height), texture=t, pos=(10, 5), size=(1014, 700))
|
# #150 - Create grid with explicit layers for color and tilesprite
|
||||||
|
self.grid = mcrfpy.Grid(grid_size=(width, height), texture=t, pos=(10, 5), size=(1014, 700),
|
||||||
|
layers={"color": "color", "tilesprite": "tile"})
|
||||||
self.highlighted = -1 #debug view feature
|
self.highlighted = -1 #debug view feature
|
||||||
self.walled_rooms = [] # for tracking "hallway rooms" vs "walled rooms"
|
self.walled_rooms = [] # for tracking "hallway rooms" vs "walled rooms"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue