6.6 KiB
Ottimizzazione Sistema di Collisioni con NumPy
Sommario
Il sistema di collisioni del gioco è stato ottimizzato per gestire oltre 200 unità simultanee mantenendo performance elevate (50+ FPS).
Problema Originale
Analisi del Vecchio Sistema
-
Metodo Rat.collisions(): O(n²) nel caso peggiore
- Ogni ratto controllava tutte le unità nelle sue celle
- Controllo AABB manuale per ogni coppia
- Con molti ratti nella stessa cella, diventava O(n²)
-
Calcoli bbox ridondanti
- bbox calcolata in
draw()ma usata anche incollisions() - Nessun caching
- bbox calcolata in
-
Esplosioni bombe: Iterazioni multiple sulle stesse posizioni
- Loop annidati per ogni direzione dell'esplosione
- Controllo manuale di
unit_positionseunit_positions_before
-
Gas: Controllo vittime a ogni frame anche quando non necessario
Soluzione Implementata
Nuovo Sistema: CollisionSystem (engine/collision_system.py)
Caratteristiche Principali
-
Approccio Ibrido
- < 10 candidati: Metodo semplice senza overhead NumPy
- ≥ 10 candidati: Operazioni vettorizzate con NumPy
- Ottimale per tutti gli scenari
-
Spatial Hashing
- Dizionari
spatial_gridespatial_grid_before - Lookup O(1) per posizioni
- Solo candidati nella stessa cella vengono controllati
- Dizionari
-
Pre-allocazione Array NumPy
- Arrays pre-allocati con capacità iniziale di 100
- Raddoppio dinamico quando necessario
- Riduce overhead di
vstack/append
-
Collision Layers
- Matrice di collisione 6x6 per filtrare interazioni non necessarie
- Layers: RAT, BOMB, GAS, MINE, POINT, EXPLOSION
- Controllo O(1) se due layer possono collidere
-
AABB Vettorizzato
- Controllo collisioni bbox per N unità in una sola operazione
- Broadcasting NumPy per calcoli paralleli
Struttura del Sistema
class CollisionSystem:
- register_unit() # Registra unità nel frame corrente
- get_collisions_for_unit() # Trova tutte le collisioni per un'unità
- get_units_in_area() # Ottiene unità in più celle (esplosioni)
- check_aabb_collision_vectorized() # AABB vettorizzato
- _simple_collision_check() # Metodo semplice per pochi candidati
Modifiche alle Unità
1. Unit (units/unit.py)
- Aggiunto attributo
collision_layer - Inizializzazione con layer specifico
2. Rat (units/rat.py)
- Usa
CollisionSystem.get_collisions_for_unit() - Eliminati loop manuali
- Tolleranza AABB gestita dal sistema
3. Bomb (units/bomb.py)
- Esplosioni usano
get_units_in_area() - Raccolta posizioni esplosione → query batch
- Singola operazione per trovare tutte le vittime
4. Gas (units/gas.py)
- Usa
get_units_in_cell()per trovare vittime - Separazione tra position e position_before
5. Mine (units/mine.py)
- Controllo trigger con
get_units_in_cell() - Layer-based detection
Integrazione nel Game Loop (rats.py)
# Inizializzazione
self.collision_system = CollisionSystem(
self.cell_size, self.map.width, self.map.height
)
# Update loop (3 passaggi)
1. Move: Tutte le unità si muovono
2. Register: Registrazione nel collision system + backward compatibility
3. Collisions + Draw: Controllo collisioni e rendering
Performance
Test Results (250 unità su griglia 30x30)
Stress Test - 100 frames:
Total time: 332.41ms
Average per frame: 3.32ms
FPS capacity: 300.8 FPS
Target (50 FPS): ✓ PASS
Confronto Scenari Reali
| Numero Unità | Frame Time | FPS Capacity |
|---|---|---|
| 50 | ~0.5ms | 2000 FPS |
| 100 | ~1.3ms | 769 FPS |
| 200 | ~2.5ms | 400 FPS |
| 250 | ~3.3ms | 300 FPS |
| 300 | ~4.0ms | 250 FPS |
Conclusione: Il sistema mantiene performance eccellenti anche con 300+ unità, ben oltre il target di 50 FPS.
Vantaggi per Scenari Specifici
-
Molti ratti in poche celle:
- Vecchio: O(n²) per celle dense
- Nuovo: O(n) con spatial hashing
-
Esplosioni bombe:
- Vecchio: Loop annidati per ogni direzione
- Nuovo: Singola query batch per tutte le posizioni
-
Scalabilità:
- Vecchio: Degrada linearmente con numero unità
- Nuovo: Performance costante grazie a spatial hashing
Compatibilità
- Backward compatible: Mantiene
unit_positionseunit_positions_before - Rimozione futura: Questi dizionari possono essere rimossi dopo test estesi
- Nessuna breaking change: API delle unità invariata
File Modificati
- ✅
requirements.txt- Aggiunto numpy - ✅
engine/collision_system.py- Nuovo sistema (370 righe) - ✅
units/unit.py- Aggiunto collision_layer - ✅
units/rat.py- Ottimizzato collisions() - ✅
units/bomb.py- Esplosioni vettorizzate - ✅
units/gas.py- Query ottimizzate - ✅
units/mine.py- Detection ottimizzata - ✅
units/points.py- Aggiunto collision_layer - ✅
rats.py- Integrato CollisionSystem nel game loop - ✅
test_collision_performance.py- Benchmark suite
Prossimi Passi (Opzionali)
- Rimozione backward compatibility: Eliminare
unit_positions/unit_positions_before - Profiling avanzato: Identificare ulteriori bottleneck
- Spatial grid gerarchico: Per mappe molto grandi (>100x100)
- Caching bbox: Se le unità non si muovono ogni frame
Installazione
cd /home/enne2/Sviluppo/mice
source .venv/bin/activate
pip install numpy
Testing
# Benchmark completo
python test_collision_performance.py
# Gioco normale
./mice.sh
Note Tecniche
Approccio Ibrido Spiegato
Il sistema usa un threshold di 10 candidati per decidere quando usare NumPy:
- < 10 candidati: Loop Python semplice (no overhead numpy)
- ≥ 10 candidati: Operazioni vettorizzate NumPy
Questo è ottimale perché:
- Con pochi candidati, l'overhead di creare array NumPy supera i benefici
- Con molti candidati, la vettorizzazione compensa l'overhead iniziale
Memory Layout
Arrays NumPy (pre-allocati):
- bboxes: (capacity, 4) float32 → ~1.6KB per 100 unità
- positions: (capacity, 2) int32 → ~800B per 100 unità
- layers: (capacity,) int8 → ~100B per 100 unità
Total: ~2.5KB per 100 unità (trascurabile)
Conclusioni
L'ottimizzazione con NumPy è altamente efficace per il caso d'uso di Mice! con 200+ unità:
✅ Performance eccellenti (300+ FPS con 250 unità)
✅ Scalabilità lineare grazie a spatial hashing
✅ Backward compatible
✅ Approccio ibrido ottimale per tutti gli scenari
✅ Memory footprint minimo
Il sistema è pronto per la produzione.