Delete page "Entity-Management.-"
parent
16d769748a
commit
fe7b626c14
|
|
@ -1,418 +0,0 @@
|
||||||
# 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
|
|
||||||
Loading…
Reference in New Issue