Update "Entity-Management"

John McCardle 2025-11-29 23:32:35 +00:00
parent ac23c3b889
commit 466148a580
1 changed files with 315 additions and 0 deletions

315
Entity-Management.md Normal file

@ -0,0 +1,315 @@
# Entity Management
Entities are game objects that implement behavior and live on Grids. While Grids handle rendering and mediate interactions, Entities encapsulate game logic like movement, combat, and AI.
## Quick Reference
**Parent System:** [[Grid-System]]
**Key Types:**
- `mcrfpy.Entity` - Game entities on grids
- `mcrfpy.Grid` - Spatial container for entities
- `mcrfpy.EntityCollection` - Collection of entities on a grid
**Key Files:**
- `src/UIEntity.h` / `src/UIEntity.cpp`
- `src/UIEntityCollection.h` / `.cpp`
**Related Issues:**
- [#115](../../issues/115) - SpatialHash for fast queries (Open)
- [#117](../../issues/117) - Memory Pool for entities (Open)
---
## What Are Entities?
Entities are game objects that:
- **Live on a Grid** (0 or 1 grid at a time)
- **Have a sprite** for visual rendering
- **Have grid position** (integer cell coordinates)
- **Implement behavior** (movement, AI, combat, inventory)
**Key distinction:** Entities implement behavior. Grids mediate interaction between entities and render them to screen.
---
## Entity-Grid Relationship
The Entity-Grid relationship mirrors the UIDrawable parent-child pattern:
| Relationship | Property | Automatic Behavior |
|--------------|----------|-------------------|
| Entity → Grid | `entity.grid` | Set when added to `grid.entities` |
| Grid → Entities | `grid.entities` | Collection of all entities on grid |
```python
import mcrfpy
# Create grid and entity
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(400, 400))
player = mcrfpy.Entity(pos=(10, 10), sprite_index=0)
# Before adding: entity has no grid
print(player.grid) # None
# Add to grid
grid.entities.append(player)
# After adding: bidirectional link established
print(player.grid == grid) # True
print(player in grid.entities) # True
# Removing breaks the link
grid.entities.remove(player)
print(player.grid) # None
```
**Important:** An entity can only be on 0 or 1 grids at a time. Adding to a new grid automatically removes from the old one.
---
## Entity Properties
### Position
```python
# Grid coordinates (integer cells)
entity.x = 15
entity.y = 20
entity.pos = (15, 20) # Tuple form
# Draw position (float, for animation interpolation)
print(entity.draw_pos) # Actual render position
```
### Sprite
```python
entity.sprite_index = 5 # Index in texture sprite sheet
```
### Visibility
```python
entity.visible = True
entity.opacity = 0.8 # 0.0 to 1.0
```
### Grid Reference
```python
current_grid = entity.grid # Read-only, set by collection operations
```
---
## EntityCollection
`grid.entities` is an `EntityCollection` with list-like operations:
```python
# Add entities
grid.entities.append(entity)
grid.entities.extend([entity1, entity2, entity3])
grid.entities.insert(0, entity) # Insert at index
# Remove entities
grid.entities.remove(entity)
entity = grid.entities.pop() # Remove and return last
entity = grid.entities.pop(0) # Remove and return at index
# Query
count = len(grid.entities)
idx = grid.entities.index(entity)
n = grid.entities.count(entity)
found = grid.entities.find("entity_name") # Find by name
# Iteration
for entity in grid.entities:
print(entity.pos)
```
---
## Entity Lifecycle
### Creation
```python
# Basic creation
entity = mcrfpy.Entity(pos=(10, 10), sprite_index=0)
# With name for later lookup
entity = mcrfpy.Entity(pos=(10, 10), sprite_index=0, name="player")
```
### Adding to Grid
```python
grid.entities.append(entity)
# entity.grid is now set to grid
# Entity will be rendered with the grid
```
### Movement
```python
# Direct position change
entity.pos = (new_x, new_y)
# Animated movement
mcrfpy.Animation("x", target_x, 0.3, "easeOutQuad").start(entity)
mcrfpy.Animation("y", target_y, 0.3, "easeOutQuad").start(entity)
```
### Removal
```python
# Method 1: Remove from collection
grid.entities.remove(entity)
# Method 2: Entity.die() - removes from parent grid
entity.die()
# After removal: entity.grid is None
```
### Transfer Between Grids
```python
def transfer_entity(entity, to_grid, new_pos):
"""Move entity to a different grid."""
entity.die() # Remove from current grid
entity.pos = new_pos
to_grid.entities.append(entity)
```
---
## Common Patterns
### Player Entity
```python
class Player:
def __init__(self, grid, start_pos):
self.entity = mcrfpy.Entity(pos=start_pos, sprite_index=0, name="player")
grid.entities.append(self.entity)
def move(self, dx, dy):
new_x = self.entity.x + dx
new_y = self.entity.y + dy
# Check walkability via grid
point = self.entity.grid.at(new_x, new_y)
if point and point.walkable:
self.entity.pos = (new_x, new_y)
return True
return False
```
### Enemy Entity
```python
class Enemy:
def __init__(self, grid, pos, aggro_range=10):
self.entity = mcrfpy.Entity(pos=pos, sprite_index=1)
self.aggro_range = aggro_range
self.health = 100
grid.entities.append(self.entity)
def update(self, player_pos):
dx = player_pos[0] - self.entity.x
dy = player_pos[1] - self.entity.y
dist = (dx*dx + dy*dy) ** 0.5
if dist < self.aggro_range:
self.chase(player_pos)
else:
self.wander()
def chase(self, target):
# Use pathfinding
path = self.entity.path_to(target)
if path and len(path) > 1:
next_cell = path[1] # path[0] is current position
self.entity.pos = next_cell
def wander(self):
import random
dx = random.choice([-1, 0, 1])
dy = random.choice([-1, 0, 1])
new_pos = (self.entity.x + dx, self.entity.y + dy)
point = self.entity.grid.at(*new_pos)
if point and point.walkable:
self.entity.pos = new_pos
```
### Item Entity
```python
class Item:
def __init__(self, grid, pos, item_type):
self.entity = mcrfpy.Entity(pos=pos, sprite_index=10 + item_type)
self.item_type = item_type
grid.entities.append(self.entity)
def pickup(self, collector):
"""Called when another entity picks up this item."""
collector.inventory.append(self.item_type)
self.entity.die() # Remove from grid
```
For more interaction patterns (click handling, selection, context menus), see [[Grid-Interaction-Patterns]].
---
## Pathfinding
Entities have built-in pathfinding via libtcod:
```python
# A* pathfinding to target
path = entity.path_to((target_x, target_y))
# Returns list of (x, y) tuples, or empty if no path
if path:
next_step = path[1] # path[0] is current position
entity.pos = next_step
```
Pathfinding respects `GridPoint.walkable` properties set on the grid.
---
## Performance Considerations
**Current:** Entity queries are O(n):
```python
# Finding entities at position requires iteration
def entities_at(grid, x, y):
return [e for e in grid.entities if e.x == x and e.y == y]
```
**Workarounds:**
- Keep entity counts reasonable (< 200 for best performance)
- Use timer callbacks for AI updates, not per-frame
- Cache query results when possible
**Future:** [#115](../../issues/115) SpatialHash will provide O(1) position queries.
See [[Performance-and-Profiling]] for optimization guidance.
---
## Related Systems
- [[Grid-System]] - Spatial container for entities
- [[Grid-Interaction-Patterns]] - Click handling, selection, context menus
- [[Animation-System]] - Smooth entity movement
- [[Performance-and-Profiling]] - Entity performance metrics
---
*Last updated: 2025-11-29*