Update Grid System

John McCardle 2025-12-02 03:10:29 +00:00
parent 0816bb4b98
commit beb9158a05
1 changed files with 244 additions and 244 deletions

@ -1,245 +1,245 @@
# Grid System
The Grid System is McRogueFace's core spatial container for roguelike game maps. It provides tilemap rendering, entity management, FOV (field of view), and pathfinding integration with libtcod.
## Quick Reference
**Related Issues:**
- [#113](../../issues/113) - Batch Operations for Grid (Open - Tier 1)
- [#124](../../issues/124) - Grid Point Animation (Open - Tier 1)
- [#150](../../issues/150) - User-driven Layer Rendering (Closed - Implemented)
- [#148](../../issues/148) - Dirty Flag RenderTexture Caching (Closed - Implemented)
- [#147](../../issues/147) - Dynamic Layer System (Closed - Implemented)
- [#123](../../issues/123) - Chunk-based Grid Rendering (Closed - Implemented)
**Key Files:**
- `src/UIGrid.h` / `src/UIGrid.cpp` - Main grid implementation
- `src/GridLayers.h` / `src/GridLayers.cpp` - ColorLayer and TileLayer
- `src/UIGridPoint.h` - Individual grid cell (walkability, transparency)
- `src/UIGridPointState.h` - Per-entity perspective/knowledge
- `src/UIEntity.h` / `src/UIEntity.cpp` - Entity system (lives on grid)
**API Reference:**
- See [mcrfpy.Grid](../../docs/api_reference_dynamic.html#Grid) in generated API docs
- See [mcrfpy.Entity](../../docs/api_reference_dynamic.html#Entity) in generated API docs
## Architecture Overview
### Three-Layer Design
The Grid System uses a three-layer architecture for sophisticated roguelike features:
1. **Visual Layer** (Rendering Layers)
- What's displayed: tile sprites, colors, overlays
- Implemented via `ColorLayer` and `TileLayer` objects
- Multiple layers per grid with z_index ordering
- Files: `src/GridLayers.h`, `src/GridLayers.cpp`
2. **World State Layer** (`TCODMap`)
- Physical properties: walkable, transparent, cost
- Used for pathfinding and FOV calculations
- Integration: libtcod via `src/UIGrid.cpp`
3. **Perspective Layer** (`UIGridPointState`)
- Per-entity knowledge: what each entity has seen/explored
- Enables fog of war, asymmetric information
- File: `src/UIGridPointState.h`
- **Note:** Python access currently limited - see Known Issues
This architecture follows proven patterns from Caves of Qud, Cogmind, and DCSS.
### Dynamic Layer System
Grids support multiple rendering layers that can be added/removed at runtime:
| Layer Type | Purpose | Methods |
|------------|---------|---------|
| `ColorLayer` | Solid colors per cell (fog, highlights) | `set(x, y, color)`, `at(x, y)`, `fill(color)` |
| `TileLayer` | Texture sprites per cell (terrain, items) | `set(x, y, sprite_index)`, `at(x, y)`, `fill(index)` |
**z_index semantics:**
- Negative z_index: Renders **below** entities
- Zero or positive z_index: Renders **above** entities
- Lower z_index renders first (behind higher z_index)
```python
# Create grid with no default layers
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(100, 100), size=(400, 400), layers={})
# Add layers explicitly
background = grid.add_layer("color", z_index=-2) # Furthest back
tiles = grid.add_layer("tile", z_index=-1) # Above background, below entities
fog_overlay = grid.add_layer("color", z_index=1) # Above entities (fog of war)
# Access existing layers
for layer in grid.layers:
print(f"{type(layer).__name__} at z={layer.z_index}")
# Remove a layer
grid.remove_layer(fog_overlay)
```
### Grid → Entity Relationship
**Entity Lifecycle:**
- Entities live on exactly 0 or 1 grids
- `grid.entities.append(entity)` sets `entity.grid = grid`
- `grid.entities.remove(entity)` sets `entity.grid = None`
- Entity removal handled automatically on grid destruction
**Spatial Queries:**
- Currently: Linear iteration through entity list
- Planned: SpatialHash for O(1) lookups (see [#115](../../issues/115))
## Sub-Pages
- [[Grid-Rendering-Pipeline]] - How grid renders each frame (chunks, dirty flags, caching)
- [[Grid-TCOD-Integration]] - FOV, pathfinding, walkability
- [[Grid-Entity-Lifecycle]] - Entity creation, movement, removal
## Common Tasks
### Creating a Grid
```python
import mcrfpy
# Create grid with default tile layer
grid = mcrfpy.Grid(
grid_size=(50, 50), # 50x50 cells
pos=(100, 100), # Screen position
size=(400, 400), # Viewport size in pixels
texture=my_texture # Texture for default TileLayer
)
# Or create with specific layer configuration
grid = mcrfpy.Grid(
grid_size=(50, 50),
pos=(100, 100),
size=(400, 400),
layers={"terrain": "tile", "highlights": "color"}
)
# Or start empty and add layers manually
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(100, 100), size=(400, 400), layers={})
terrain = grid.add_layer("tile", z_index=-1, texture=my_texture)
# Add to scene
mcrfpy.sceneUI("game").append(grid)
```
### Setting Tile Properties
```python
# Access layers for visual properties
tile_layer = grid.layers[0] # First layer (usually tiles)
tile_layer.set(x, y, 42) # Set sprite index at cell
color_layer = grid.add_layer("color", z_index=-2)
color_layer.set(x, y, mcrfpy.Color(64, 64, 128)) # Blue tint
# Access grid point for world properties
point = grid.at(x, y)
point.walkable = True # Can entities walk here?
point.transparent = True # Can see through for FOV?
```
### FOV and Pathfinding
```python
# Compute field of view from position
grid.compute_fov(entity.grid_x, entity.grid_y, radius=10)
# Check if cell is visible
if grid.is_in_fov(target_x, target_y):
print("Target is visible!")
# A* pathfinding
path = grid.find_path(start_x, start_y, end_x, end_y)
# Dijkstra maps for AI
grid.compute_dijkstra([(goal_x, goal_y)])
distance = grid.get_dijkstra_distance(entity.grid_x, entity.grid_y)
path_to_goal = grid.get_dijkstra_path(entity.grid_x, entity.grid_y)
```
### Mouse Events
Grids support mouse interaction at both the grid level and cell level:
```python
# Grid-level events (screen coordinates)
def on_grid_click(x, y, button):
print(f"Grid clicked at pixel ({x}, {y}), button {button}")
grid.on_click = on_grid_click
grid.on_enter = lambda: print("Mouse entered grid")
grid.on_exit = lambda: print("Mouse left grid")
grid.on_move = lambda x, y: print(f"Mouse at ({x}, {y})")
# Cell-level events (grid coordinates)
def on_cell_click(grid_x, grid_y):
print(f"Cell ({grid_x}, {grid_y}) clicked!")
point = grid.at(grid_x, grid_y)
# Do something with the cell...
grid.on_cell_click = on_cell_click
grid.on_cell_enter = lambda x, y: print(f"Entered cell ({x}, {y})")
grid.on_cell_exit = lambda x, y: print(f"Left cell ({x}, {y})")
# Query currently hovered cell
if grid.hovered_cell:
hx, hy = grid.hovered_cell
print(f"Hovering over ({hx}, {hy})")
```
### Camera Control
```python
# Center viewport on a position (pixel coordinates within grid space)
grid.center = (player.grid_x * 16 + 8, player.grid_y * 16 + 8)
# Or set components individually
grid.center_x = player.grid_x * 16 + 8
grid.center_y = player.grid_y * 16 + 8
# Zoom (1.0 = normal, 2.0 = 2x zoom in, 0.5 = zoom out)
grid.zoom = 1.5
```
## Performance Characteristics
**Implemented Optimizations:**
- **Chunk-based rendering** ([#123](../../issues/123)): Large grids divided into chunks, only visible chunks rendered
- **Dirty flag system** ([#148](../../issues/148)): Layers track changes, skip redraw when unchanged
- **RenderTexture caching**: Each chunk cached to texture, reused until dirty
- **Viewport culling**: Only cells within viewport are processed
**Current Performance:**
- Grids of 1000x1000+ cells render efficiently
- Static scenes near-zero CPU (cached textures reused)
- Entity rendering: O(visible entities)
**Profiling:** Use the F3 overlay or `mcrfpy.setTimer()` with `mcrfpy.getMetrics()` to measure grid performance. See [[Performance-and-Profiling]].
## Known Issues & Limitations
**Current Limitations:**
- **FOV Python access limited:** `compute_fov()` works but fog-of-war overlay must be managed manually via color layers. Per-entity perspective (`UIGridPointState`) not yet exposed to Python. See [#113 discussion](../../issues/113).
- No tile-level animation yet (planned: [#124](../../issues/124))
- Entity spatial queries are O(n) (planned: SpatialHash [#115](../../issues/115))
**Workarounds:**
- For fog of war: Use a `ColorLayer` with positive z_index, update cell colors based on `is_in_fov()` results
- For entity queries: Use list comprehension filtering on `grid.entities`
## Related Systems
- [[UI-Component-Hierarchy]] - Grid inherits from UIDrawable
- [[Animation-System]] - Grid properties are animatable (pos, zoom, center, etc.)
- [[Performance-and-Profiling]] - Grid rendering instrumented with metrics
- [[Entity-Management]] - Entities live within Grid containers
---
# Grid System
The Grid System is McRogueFace's core spatial container for roguelike game maps. It provides tilemap rendering, entity management, FOV (field of view), and pathfinding integration with libtcod.
## Quick Reference
**Related Issues:**
- [#113](../../issues/113) - Batch Operations for Grid (Open - Tier 1)
- [#124](../../issues/124) - Grid Point Animation (Open - Tier 1)
- [#150](../../issues/150) - User-driven Layer Rendering (Closed - Implemented)
- [#148](../../issues/148) - Dirty Flag RenderTexture Caching (Closed - Implemented)
- [#147](../../issues/147) - Dynamic Layer System (Closed - Implemented)
- [#123](../../issues/123) - Chunk-based Grid Rendering (Closed - Implemented)
**Key Files:**
- `src/UIGrid.h` / `src/UIGrid.cpp` - Main grid implementation
- `src/GridLayers.h` / `src/GridLayers.cpp` - ColorLayer and TileLayer
- `src/UIGridPoint.h` - Individual grid cell (walkability, transparency)
- `src/UIGridPointState.h` - Per-entity perspective/knowledge
- `src/UIEntity.h` / `src/UIEntity.cpp` - Entity system (lives on grid)
**API Reference:**
- See [mcrfpy.Grid](../../docs/api_reference_dynamic.html#Grid) in generated API docs
- See [mcrfpy.Entity](../../docs/api_reference_dynamic.html#Entity) in generated API docs
## Architecture Overview
### Three-Layer Design
The Grid System uses a three-layer architecture for sophisticated roguelike features:
1. **Visual Layer** (Rendering Layers)
- What's displayed: tile sprites, colors, overlays
- Implemented via `ColorLayer` and `TileLayer` objects
- Multiple layers per grid with z_index ordering
- Files: `src/GridLayers.h`, `src/GridLayers.cpp`
2. **World State Layer** (`TCODMap`)
- Physical properties: walkable, transparent, cost
- Used for pathfinding and FOV calculations
- Integration: libtcod via `src/UIGrid.cpp`
3. **Perspective Layer** (`UIGridPointState`)
- Per-entity knowledge: what each entity has seen/explored
- Enables fog of war, asymmetric information
- File: `src/UIGridPointState.h`
- **Note:** Python access currently limited - see Known Issues
This architecture follows proven patterns from Caves of Qud, Cogmind, and DCSS.
### Dynamic Layer System
Grids support multiple rendering layers that can be added/removed at runtime:
| Layer Type | Purpose | Methods |
|------------|---------|---------|
| `ColorLayer` | Solid colors per cell (fog, highlights) | `set(x, y, color)`, `at(x, y)`, `fill(color)` |
| `TileLayer` | Texture sprites per cell (terrain, items) | `set(x, y, sprite_index)`, `at(x, y)`, `fill(index)` |
**z_index semantics:**
- Negative z_index: Renders **below** entities
- Zero or positive z_index: Renders **above** entities
- Lower z_index renders first (behind higher z_index)
```python
# Create grid with no default layers
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(100, 100), size=(400, 400), layers={})
# Add layers explicitly
background = grid.add_layer("color", z_index=-2) # Furthest back
tiles = grid.add_layer("tile", z_index=-1) # Above background, below entities
fog_overlay = grid.add_layer("color", z_index=1) # Above entities (fog of war)
# Access existing layers
for layer in grid.layers:
print(f"{type(layer).__name__} at z={layer.z_index}")
# Remove a layer
grid.remove_layer(fog_overlay)
```
### Grid → Entity Relationship
**Entity Lifecycle:**
- Entities live on exactly 0 or 1 grids
- `grid.entities.append(entity)` sets `entity.grid = grid`
- `grid.entities.remove(entity)` sets `entity.grid = None`
- Entity removal handled automatically on grid destruction
**Spatial Queries:**
- Currently: Linear iteration through entity list
- Planned: SpatialHash for O(1) lookups (see [#115](../../issues/115))
## Sub-Pages
- [[Grid-Rendering-Pipeline]] - How grid renders each frame (chunks, dirty flags, caching)
- [[Grid-TCOD-Integration]] - FOV, pathfinding, walkability
- [[Grid-Entity-Lifecycle]] - Entity creation, movement, removal
## Common Tasks
### Creating a Grid
```python
import mcrfpy
# Create grid with default tile layer
grid = mcrfpy.Grid(
grid_size=(50, 50), # 50x50 cells
pos=(100, 100), # Screen position
size=(400, 400), # Viewport size in pixels
texture=my_texture # Texture for default TileLayer
)
# Or create with specific layer configuration
grid = mcrfpy.Grid(
grid_size=(50, 50),
pos=(100, 100),
size=(400, 400),
layers={"terrain": "tile", "highlights": "color"}
)
# Or start empty and add layers manually
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(100, 100), size=(400, 400), layers={})
terrain = grid.add_layer("tile", z_index=-1, texture=my_texture)
# Add to scene
mcrfpy.sceneUI("game").append(grid)
```
### Setting Tile Properties
```python
# Access layers for visual properties
tile_layer = grid.layers[0] # First layer (usually tiles)
tile_layer.set(x, y, 42) # Set sprite index at cell
color_layer = grid.add_layer("color", z_index=-2)
color_layer.set(x, y, mcrfpy.Color(64, 64, 128)) # Blue tint
# Access grid point for world properties
point = grid.at(x, y)
point.walkable = True # Can entities walk here?
point.transparent = True # Can see through for FOV?
```
### FOV and Pathfinding
```python
# Compute field of view from position
grid.compute_fov(entity.grid_x, entity.grid_y, radius=10)
# Check if cell is visible
if grid.is_in_fov(target_x, target_y):
print("Target is visible!")
# A* pathfinding
path = grid.find_path(start_x, start_y, end_x, end_y)
# Dijkstra maps for AI
grid.compute_dijkstra([(goal_x, goal_y)])
distance = grid.get_dijkstra_distance(entity.grid_x, entity.grid_y)
path_to_goal = grid.get_dijkstra_path(entity.grid_x, entity.grid_y)
```
### Mouse Events
Grids support mouse interaction at both the grid level and cell level:
```python
# Grid-level events (screen coordinates)
def on_grid_click(x, y, button):
print(f"Grid clicked at pixel ({x}, {y}), button {button}")
grid.on_click = on_grid_click
grid.on_enter = lambda: print("Mouse entered grid")
grid.on_exit = lambda: print("Mouse left grid")
grid.on_move = lambda x, y: print(f"Mouse at ({x}, {y})")
# Cell-level events (grid coordinates)
def on_cell_click(grid_x, grid_y):
print(f"Cell ({grid_x}, {grid_y}) clicked!")
point = grid.at(grid_x, grid_y)
# Do something with the cell...
grid.on_cell_click = on_cell_click
grid.on_cell_enter = lambda x, y: print(f"Entered cell ({x}, {y})")
grid.on_cell_exit = lambda x, y: print(f"Left cell ({x}, {y})")
# Query currently hovered cell
if grid.hovered_cell:
hx, hy = grid.hovered_cell
print(f"Hovering over ({hx}, {hy})")
```
### Camera Control
```python
# Center viewport on a position (pixel coordinates within grid space)
grid.center = (player.grid_x * 16 + 8, player.grid_y * 16 + 8)
# Or set components individually
grid.center_x = player.grid_x * 16 + 8
grid.center_y = player.grid_y * 16 + 8
# Zoom (1.0 = normal, 2.0 = 2x zoom in, 0.5 = zoom out)
grid.zoom = 1.5
```
## Performance Characteristics
**Implemented Optimizations:**
- **Chunk-based rendering** ([#123](../../issues/123)): Large grids divided into chunks, only visible chunks rendered
- **Dirty flag system** ([#148](../../issues/148)): Layers track changes, skip redraw when unchanged
- **RenderTexture caching**: Each chunk cached to texture, reused until dirty
- **Viewport culling**: Only cells within viewport are processed
**Current Performance:**
- Grids of 1000x1000+ cells render efficiently
- Static scenes near-zero CPU (cached textures reused)
- Entity rendering: O(visible entities)
**Profiling:** Use the F3 overlay or `mcrfpy.setTimer()` with `mcrfpy.getMetrics()` to measure grid performance. See [[Performance-and-Profiling]].
## Known Issues & Limitations
**Current Limitations:**
- **FOV Python access limited:** `compute_fov()` works but fog-of-war overlay must be managed manually via color layers. Per-entity perspective (`UIGridPointState`) not yet exposed to Python. See [#113 discussion](../../issues/113).
- No tile-level animation yet (planned: [#124](../../issues/124))
- Entity spatial queries are O(n) (planned: SpatialHash [#115](../../issues/115))
**Workarounds:**
- For fog of war: Use a `ColorLayer` with positive z_index, update cell colors based on `is_in_fov()` results
- For entity queries: Use list comprehension filtering on `grid.entities`
## Related Systems
- [[UI-Component-Hierarchy]] - Grid inherits from UIDrawable
- [[Animation-System]] - Grid properties are animatable (pos, zoom, center, etc.)
- [[Performance-and-Profiling]] - Grid rendering instrumented with metrics
- [[Entity-Management]] - Entities live within Grid containers
---
*Last updated: 2025-11-29*