Table of Contents
- Grid TCOD Integration
Grid TCOD Integration
Overview
McRogueFace integrates with libtcod for FOV (field of view), A* pathfinding, and Dijkstra maps. The integration automatically synchronizes each grid's walkability and transparency properties with an internal TCODMap.
Parent Page: Grid-System
Related Pages:
- AI-and-Pathfinding - Using FOV and pathfinding for game AI
- Grid-Rendering-Pipeline - How FOV affects rendering overlays
- Entity-Management - Entity perspective and gridstate
Key Files:
src/UIGrid.cpp- TCODMap synchronization, FOV, pathfindingsrc/UIGrid.h- TCODMap, TCODPath, TCODDijkstra members
The World State Layer
Cell Properties as World Physics
Each grid cell (GridPoint) has properties that drive TCOD algorithms:
Visual Layer (ColorLayer/TileLayer) - What's displayed (colors, sprites)
|
World State Layer (GridPoint) - Physical properties (walkable, transparent)
|
Perspective Layer - Per-entity knowledge (FOV results)
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
cell = grid.at(10, 10)
cell.walkable = True # Affects pathfinding
cell.transparent = True # Affects FOV
cell.tilesprite = 0 # Visual tile index (legacy)
Automatic Synchronization: When you set cell.walkable or cell.transparent, the internal TCODMap is automatically updated. There is no manual sync step required.
Field of View (FOV)
Computing FOV
FOV determines which cells are visible from a given position:
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
# Make all cells transparent
for x in range(50):
for y in range(50):
grid.at(x, y).transparent = True
grid.at(x, y).walkable = True
# Add some walls
for x in range(20, 30):
grid.at(x, 15).transparent = False
grid.at(x, 15).walkable = False
# Compute FOV from position with radius
grid.compute_fov((25, 25), radius=10)
# Query visibility of specific cells
if grid.is_in_fov((25, 25)):
print("Origin is visible")
if not grid.is_in_fov((25, 5)):
print("Behind wall is not visible")
API:
grid.compute_fov((x, y), radius=N)- Compute FOV from positiongrid.is_in_fov((x, y))- Query if cell is currently visible
FOV with Fog Overlay
Use a ColorLayer to visualize FOV:
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[])
# Create fog overlay above entities
fog = mcrfpy.ColorLayer(name="fog", z_index=1)
grid.add_layer(fog)
fog.fill(mcrfpy.Color(0, 0, 0, 255)) # Start fully hidden
# After computing FOV, reveal visible cells
def update_fog(grid, fog, pos, radius=10):
grid.compute_fov(pos, radius=radius)
w, h = grid.grid_size
for x in range(w):
for y in range(h):
if grid.is_in_fov((x, y)):
fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Visible
else:
fog.set((x, y), mcrfpy.Color(0, 0, 0, 192)) # Dim
update_fog(grid, fog, (25, 25))
A* Pathfinding
Finding Paths
Find the shortest path between two walkable cells:
grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400))
for x in range(30):
for y in range(30):
grid.at(x, y).walkable = True
# Find path - returns AStarPath object
path = grid.find_path((5, 5), (25, 25))
if path is not None and len(path) > 0:
# Walk the path (consumes next step)
next_step = path.walk()
print(f"Next step: ({next_step.x}, {next_step.y})")
# Peek at next step without consuming
upcoming = path.peek()
# Check remaining steps
print(f"Remaining: {path.remaining}")
# Check endpoints
print(f"From: {path.origin}")
print(f"To: {path.destination}")
AStarPath Object
| Property/Method | Description |
|---|---|
len(path) |
Total steps in path |
path.walk() |
Get and consume next step (returns Vector) |
path.peek() |
View next step without consuming |
path.remaining |
Steps remaining |
path.origin |
Start position (Vector) |
path.destination |
End position (Vector) |
Moving Entities Along Paths
player = mcrfpy.Entity(grid_pos=(5, 5), sprite_index=0)
grid.entities.append(player)
# Find path to target
path = grid.find_path(
(int(player.grid_x), int(player.grid_y)),
(25, 25)
)
if path and len(path) > 0:
step = path.walk()
player.grid_x = int(step.x)
player.grid_y = int(step.y)
Dijkstra Maps
Computing Dijkstra Maps
Dijkstra maps compute distances from a goal to all reachable cells. Useful for multi-enemy AI where many entities path toward the same target:
grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400))
for x in range(30):
for y in range(30):
grid.at(x, y).walkable = True
# Create Dijkstra map from goal position
dm = grid.get_dijkstra_map((15, 15))
# Query distance from any cell to goal
d = dm.distance((0, 0))
print(f"Distance from (0,0) to goal: {d}")
# Get full path from any cell to goal
path = dm.path_from((0, 0))
print(f"Path length: {len(path)}")
# Get just the next step toward goal
next_step = dm.step_from((0, 0))
print(f"Next step: ({next_step.x}, {next_step.y})")
DijkstraMap Object
| Method | Description |
|---|---|
dm.distance((x, y)) |
Distance from cell to goal |
dm.path_from((x, y)) |
Full path from cell to goal |
dm.step_from((x, y)) |
Next step from cell toward goal |
Dijkstra vs A*
| Feature | A* (find_path) |
Dijkstra (get_dijkstra_map) |
|---|---|---|
| Goals | Single target | Single target, query from anywhere |
| Computation | One path at a time | One map, unlimited queries |
| Use case | Single entity, single target | Many entities, same target |
| Performance | Fast per query | O(n) once, then O(1) per query |
Rule of thumb: 1-5 entities -> A* per entity. 10+ entities with same goal -> Dijkstra map.
Entity Perspective System
Setting Grid Perspective
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0)
grid.entities.append(player)
# Assign perspective (property, not method)
grid.perspective = player
# Grid rendering now uses player's FOV for visibility
grid.compute_fov((int(player.grid_x), int(player.grid_y)), radius=10)
FOV Update on Movement
scene = mcrfpy.Scene("game")
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[])
fog = mcrfpy.ColorLayer(name="fog", z_index=1)
grid.add_layer(fog)
fog.fill(mcrfpy.Color(0, 0, 0, 255))
for x in range(50):
for y in range(50):
grid.at(x, y).transparent = True
grid.at(x, y).walkable = True
player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0)
grid.entities.append(player)
grid.perspective = player
scene.children.append(grid)
def update_fov():
"""Call after player moves"""
px, py = int(player.grid_x), int(player.grid_y)
grid.compute_fov((px, py), radius=10)
w, h = grid.grid_size
for x in range(w):
for y in range(h):
if grid.is_in_fov((x, y)):
fog.set((x, y), mcrfpy.Color(0, 0, 0, 0))
def on_key(key, action):
if action != mcrfpy.InputState.PRESSED:
return
dx, dy = 0, 0
if key == mcrfpy.Key.W: dy = -1
elif key == mcrfpy.Key.S: dy = 1
elif key == mcrfpy.Key.A: dx = -1
elif key == mcrfpy.Key.D: dx = 1
if dx or dy:
nx = int(player.grid_x) + dx
ny = int(player.grid_y) + dy
if grid.at(nx, ny).walkable:
player.grid_x = nx
player.grid_y = ny
update_fov()
scene.on_key = on_key
update_fov() # Initial FOV
Common Patterns
Opening a Door
def open_door(grid, door_x, door_y):
"""Open door - update world state (auto-syncs to TCOD)"""
cell = grid.at(door_x, door_y)
cell.walkable = True
cell.transparent = True
cell.tilesprite = 2 # Open door sprite
# Recompute FOV if player nearby
px, py = int(player.grid_x), int(player.grid_y)
grid.compute_fov((px, py), radius=10)
Dynamic Obstacle
def boulder_falls(grid, x, y):
"""Boulder blocks cell"""
cell = grid.at(x, y)
cell.walkable = False
cell.transparent = False
cell.tilesprite = 3 # Boulder sprite
# TCOD map auto-updated - paths through this cell now invalid
Chase AI with Dijkstra
def update_enemies(grid, player, enemies):
"""Move all enemies toward player using Dijkstra map"""
px, py = int(player.grid_x), int(player.grid_y)
dm = grid.get_dijkstra_map((px, py))
for enemy in enemies:
ex, ey = int(enemy.grid_x), int(enemy.grid_y)
next_step = dm.step_from((ex, ey))
if next_step is not None:
enemy.grid_x = int(next_step.x)
enemy.grid_y = int(next_step.y)
Spatial Queries
# Find entities near a position
nearby = grid.entities_in_radius((int(enemy.grid_x), int(enemy.grid_y)), 5.0)
for entity in nearby:
print(f"Nearby: {entity.name}")
Performance Considerations
FOV Cost
FOV computation time scales with radius and grid size. Only compute when the entity moves:
last_pos = [None]
def update_fov_if_moved():
px, py = int(player.grid_x), int(player.grid_y)
if last_pos[0] != (px, py):
grid.compute_fov((px, py), radius=10)
last_pos[0] = (px, py)
Pathfinding Cost
- Limit search distance for distant targets
- Use Dijkstra maps for many entities with same goal
- Cache paths and recompute only when grid changes
Cell Property Changes
Setting walkable or transparent auto-syncs to TCOD. For bulk changes, set all properties first, then compute FOV/paths:
# Set many cells, then compute once
for x in range(100):
for y in range(100):
grid.at(x, y).walkable = compute_walkable(x, y)
# Single FOV computation after all changes
grid.compute_fov((px, py), radius=10)
Troubleshooting
Issue: Pathfinding Returns None
Causes:
- Target is unreachable (blocked by walls)
- Start or end position is non-walkable
Debug:
path = grid.find_path((x1, y1), (x2, y2))
if path is None or len(path) == 0:
print(f"Start walkable: {grid.at(x1, y1).walkable}")
print(f"End walkable: {grid.at(x2, y2).walkable}")
Issue: FOV Doesn't Match Expected
Cause: Cell transparent property not set correctly.
Fix: Ensure walls have transparent = False:
cell = grid.at(x, y)
cell.walkable = False
cell.transparent = False # Must set both for walls
Issue: Entity Can See Through Glass
Glass cells should block movement but allow sight:
glass = grid.at(x, y)
glass.walkable = False # Can't walk through
glass.transparent = True # CAN see through
API Quick Reference
FOV:
grid.compute_fov((x, y), radius=N)- Compute FOV from positiongrid.is_in_fov((x, y))- Check if cell is visible
A Pathfinding:*
grid.find_path((x1, y1), (x2, y2))- Returns AStarPath object
Dijkstra Maps:
grid.get_dijkstra_map((x, y))- Returns DijkstraMap objectdm.distance((x, y))- Distance to goaldm.path_from((x, y))- Full path to goaldm.step_from((x, y))- Next step toward goal
Spatial Queries:
grid.entities_in_radius((x, y), radius)- Find nearby entities
Perspective:
grid.perspective = entity- Set FOV perspective entity
Cell Properties:
cell.walkable- Bool, affects pathfindingcell.transparent- Bool, affects FOV
Navigation:
- Grid-System - Parent page
- AI-and-Pathfinding - Using FOV and pathfinding for game AI
- Grid-Rendering-Pipeline - FOV overlay rendering
- Entity-Management - Entity gridstate and perspective