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