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