Update "Entity-Management"
parent
ac23c3b889
commit
466148a580
|
|
@ -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*
|
||||||
Loading…
Reference in New Issue