Add "Entity-Management"

John McCardle 2025-10-25 22:39:01 +00:00
parent ea76c22954
commit ef13428826
1 changed files with 418 additions and 0 deletions

418
Entity-Management.-.md Normal file

@ -0,0 +1,418 @@
# Entity Management
Creating, positioning, and managing game entities that live on grids.
## Quick Reference
**System:** [[Grid-System]]
**Key Types:**
- `mcrfpy.Entity` - Game entities on grids
- `mcrfpy.Grid` - Spatial container for entities
- `grid.entities` - EntityCollection
**API Reference:** [mcrfpy.Entity](../../docs/api_reference_dynamic.html#Entity)
---
## What are Entities?
Entities are game objects that:
- Live on a Grid (0 or 1 grid per entity)
- Have a sprite for rendering
- Have grid position (integer coordinates)
- Can have AI, pathfinding, field of view
- Represent players, enemies, items, etc.
**Files:**
- `src/UIEntity.h` / `src/UIEntity.cpp`
- `src/UIEntityCollection.h` / `.cpp`
---
## Creating Entities
### Basic Entity Creation
```python
import mcrfpy
# Create grid first
grid = mcrfpy.Grid(50, 50, 16, 16)
grid.texture = mcrfpy.createTexture("tiles.png")
# Create entity at position (10, 10) with sprite 0
player = mcrfpy.Entity(10, 10, 0)
player.sprite.texture = mcrfpy.createTexture("entities.png")
# Add entity to grid
grid.entities.append(player)
# Grid is now set automatically
assert player.grid == grid
```
### Entity Properties
```python
# Position (grid coordinates)
player.x = 15
player.y = 20
player.pos = (15, 20) # Tuple alternative
# Sprite (what to display)
player.sprite_index = 5 # Index in texture
player.sprite.scale_x = 1.5
player.sprite.scale_y = 1.5
# Visibility
player.visible = True
player.sprite.opacity = 0.8
# Grid association
current_grid = player.grid # Read-only
```
---
## Entity Lifecycle
### Adding to Grid
```python
grid.entities.append(entity)
# Automatically:
# - entity.grid = grid
# - Entity rendered with grid
# - Entity included in spatial queries
```
### Removing from Grid
```python
# Method 1: Direct removal
grid.entities.remove(entity)
# Method 2: Entity.die() (planned #30)
# entity.die() # Will call grid.entities.remove(entity)
# Automatically:
# - entity.grid = None
# - Entity no longer rendered
# - Entity excluded from queries
```
### Moving Entities
```python
# Method 1: Direct assignment
entity.x = new_x
entity.y = new_y
# Method 2: Tuple assignment
entity.pos = (new_x, new_y)
# Method 3: Animation (smooth movement)
mcrfpy.animate(entity, "x", target_x, 500, "ease_in_out_quad")
mcrfpy.animate(entity, "y", target_y, 500, "ease_in_out_quad")
```
**Collision Detection:** Manual - check grid cells before moving
---
## Entity Collections
### Iteration
```python
# Iterate all entities on grid
for entity in grid.entities:
print(f"Entity at ({entity.x}, {entity.y})")
# Count entities
num_entities = len(grid.entities)
# Index access
first_entity = grid.entities[0]
last_entity = grid.entities[-1]
```
### Querying
**Current:** O(n) iteration - no spatial indexing
```python
# Find entities at position
def entities_at(grid, x, y):
result = []
for entity in grid.entities:
if entity.x == x and entity.y == y:
result.append(entity)
return result
# Find entities in radius
def entities_in_radius(grid, center_x, center_y, radius):
result = []
for entity in grid.entities:
dx = entity.x - center_x
dy = entity.y - center_y
if (dx*dx + dy*dy) <= radius*radius:
result.append(entity)
return result
```
**Optimization:** [#115](../../issues/115) - SpatialHash for O(1) queries
---
## Common Patterns
### Pattern 1: Player Entity
```python
import mcrfpy
# Create player
player = mcrfpy.Entity(25, 25, 0)
player.sprite.texture = player_texture
player.sprite_index = 0
# Add to grid
game_grid.entities.append(player)
# Movement with collision
def move_player(dx, dy):
new_x = player.x + dx
new_y = player.y + dy
# Check walkable
if game_grid.walkable((new_x, new_y)):
# Check for other entities
if not entities_at(game_grid, new_x, new_y):
player.pos = (new_x, new_y)
return True
return False
# Input handling
def on_keypress(key, pressed):
if pressed:
if key == mcrfpy.Key.Up:
move_player(0, -1)
elif key == mcrfpy.Key.Down:
move_player(0, 1)
elif key == mcrfpy.Key.Left:
move_player(-1, 0)
elif key == mcrfpy.Key.Right:
move_player(1, 0)
```
### Pattern 2: Enemy Entities
```python
import random
class Enemy:
def __init__(self, x, y):
self.entity = mcrfpy.Entity(x, y, 1)
self.entity.sprite.texture = enemy_texture
self.health = 100
self.aggro_range = 10
def update(self, player):
# Calculate distance to player
dx = player.x - self.entity.x
dy = player.y - self.entity.y
dist = (dx*dx + dy*dy) ** 0.5
if dist < self.aggro_range:
# Chase player
self.move_toward(player.x, player.y)
else:
# Wander randomly
self.wander()
def move_toward(self, target_x, target_y):
# Simple movement (pathfinding in [[AI-and-Pathfinding]])
if target_x > self.entity.x:
self.entity.x += 1
elif target_x < self.entity.x:
self.entity.x -= 1
elif target_y > self.entity.y:
self.entity.y += 1
elif target_y < self.entity.y:
self.entity.y -= 1
def wander(self):
# Random movement
dx = random.choice([-1, 0, 1])
dy = random.choice([-1, 0, 1])
new_x = self.entity.x + dx
new_y = self.entity.y + dy
if game_grid.walkable((new_x, new_y)):
self.entity.pos = (new_x, new_y)
# Spawn enemies
enemies = []
for i in range(10):
enemy = Enemy(random.randint(0, 49), random.randint(0, 49))
game_grid.entities.append(enemy.entity)
enemies.append(enemy)
# Update all enemies
def update_enemies():
for enemy in enemies:
enemy.update(player)
mcrfpy.setTimer("enemy_ai", lambda ms: update_enemies(), 500) # Every 0.5s
```
### Pattern 3: Item Entities
```python
class Item:
def __init__(self, x, y, item_type):
self.entity = mcrfpy.Entity(x, y, 10 + item_type)
self.entity.sprite.texture = item_texture
self.type = item_type # 0=potion, 1=key, 2=coin, etc
self.pickupable = True
def on_pickup(self, player):
# Add to inventory
player.inventory.append(self.type)
# Remove from grid
if self.entity.grid:
self.entity.grid.entities.remove(self.entity)
# Check for item pickup
def check_pickup(player):
items_here = entities_at(game_grid, player.x, player.y)
for entity in items_here:
# Find corresponding Item object
for item in all_items:
if item.entity == entity and item.pickupable:
item.on_pickup(player)
```
---
## Entity-Grid Relationship
### Lifecycle
```
1. Entity created (not on grid)
entity.grid == None
2. Entity added to grid
grid.entities.append(entity)
→ entity.grid = grid
3. Entity removed from grid
grid.entities.remove(entity)
→ entity.grid = None
4. Entity destroyed
Python GC handles cleanup
```
**Important:** Entities can only be on 0 or 1 grids at a time.
### Multiple Grids
```python
# Move entity between grids
def transfer_entity(entity, from_grid, to_grid, new_x, new_y):
# Remove from old grid
if entity.grid:
entity.grid.entities.remove(entity)
# Update position
entity.pos = (new_x, new_y)
# Add to new grid
to_grid.entities.append(entity)
```
---
## Performance Considerations
### Current Limitations
**Entity Iteration:** O(n) for spatial queries
- Finding entities at position: Must check all entities
- Finding entities in radius: Must check all entities
- Bottleneck: 500+ entities
**Workarounds:**
- Limit entity count (< 200 for best performance)
- Update entities in batches
- Use timer callbacks instead of per-frame updates
### Planned Optimizations
**[#115](../../issues/115): SpatialHash**
- O(1) entity lookup by position
- O(k) radius queries (k = nearby entities)
- Expected: 100x+ improvement
**[#117](../../issues/117): Memory Pool**
- Reuse entity objects instead of allocate/destroy
- Reduced memory fragmentation
- Faster spawning
**[#116](../../issues/116): Dirty Flags**
- Only re-render grid when entities move
- Significant improvement for static entities
---
## Integration with Other Systems
### With Pathfinding
See [[AI-and-Pathfinding]] for:
- `entity.path_to(x, y)` - A* pathfinding
- Dijkstra maps for AI
- Path following
### With FOV
See [[AI-and-Pathfinding]] for:
- Per-entity field of view
- `grid.perspective` property
- Fog of war
### With Animation
See [[Animation-System]] for:
- Smooth entity movement
- Sprite animation
- Visual effects
```python
# Animate entity movement
mcrfpy.animate(entity, "x", target_x, 300, "ease_out_quad")
mcrfpy.animate(entity, "y", target_y, 300, "ease_out_quad")
```
---
## Related Documentation
- [[Grid-System]] - Entity spatial container
- [[AI-and-Pathfinding]] - Entity AI and pathfinding
- [[Animation-System]] - Animating entity movement
- [[UI-Component-Hierarchy]] - Entity as UIDrawable
**Tutorial:** [McRogueFace Does The Entire Roguelike Tutorial](../../roguelike_tutorial/) - Parts 2-6 cover entity management
**Open Issues:**
- [#30](../../issues/30) - Entity.die() method
- [#115](../../issues/115) - SpatialHash for fast queries
- [#117](../../issues/117) - Memory pool for entities