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