La decente navigazione NPC richiede spesso la possibilità di evitare ostacoli. Questo tutorial copre il evitare le collisioni comportamento di sterzata, che consente ai personaggi di evitare con garbo qualsiasi numero di ostacoli nell'ambiente.
Nota: Sebbene questo tutorial sia scritto usando AS3 e Flash, dovresti essere in grado di utilizzare le stesse tecniche e concetti in quasi tutti gli ambienti di sviluppo di giochi. Devi avere una conoscenza di base dei vettori di matematica.
L'idea alla base di evitare le collisioni è di generare una forza di governo per evitare gli ostacoli ogni volta che si è abbastanza vicini da bloccare il passaggio. Anche se l'ambiente ha diversi ostacoli, questo comportamento userà uno di essi alla volta per calcolare la forza di evitamento.
Vengono analizzati solo gli ostacoli che precedono il personaggio; il più vicino, ritenuto il più minaccioso, viene selezionato per la valutazione. Di conseguenza il personaggio è in grado di schivare tutti gli ostacoli nell'area, passando da uno all'altro con grazia e senza soluzione di continuità.
Il comportamento di evitare le collisioni non è un algoritmo di individuazione dei percorsi. Farà muovere i personaggi attraverso l'ambiente, evitando gli ostacoli, alla fine trovando una via per passare attraverso i blocchi - ma non funziona molto bene con gli ostacoli "L" o "T", ad esempio.
Mancia: Questo comportamento di evitare le collisioni può sembrare simile al comportamento di fuga, ma c'è una differenza importante tra di loro. Un personaggio che si muove vicino a un muro lo eviterà solo se bloccherà la strada, ma il comportamento di fuggire allontanerà sempre il personaggio dal muro.Il primo passo per evitare gli ostacoli nell'ambiente è percepirli. Gli unici ostacoli che il personaggio deve preoccupare sono quelli che gli stanno di fronte e bloccano direttamente la rotta corrente.
Come precedentemente spiegato, il vettore di velocità descrive la direzione del personaggio. Sarà usato per produrre un nuovo vettore chiamato avanti
, che è una copia del vettore di velocità, ma con una lunghezza diversa:
avanti
il vettore è la linea di vista del personaggio. Questo vettore è calcolato come segue:
avanti = posizione + normalizza (velocità) * MAX_SEE_AHEAD
Il avanti
lunghezza del vettore (regolata con MAX_SEE_AHEAD
) definisce quanto lontano il personaggio "vedrà".
Il più grande MAX_SEE_AHEAD
è, prima il personaggio inizierà a recitare per schivare un ostacolo, perché lo percepirà come una minaccia anche se è lontano:
Per controllare la collisione, ogni ostacolo (o il suo riquadro di delimitazione) deve essere descritto come una forma geometrica. L'uso di una sfera (cerchio in due dimensine) dà i migliori risultati, quindi ogni ostacolo nell'ambiente sarà descritto come tale.
Una possibile soluzione per verificare la collisione è l'intersezione della linea-sfera: la linea è la avanti
vettore e la sfera è l'ostacolo. Questo approccio funziona, ma userò una semplificazione di ciò che è più facile da capire e ha risultati simili (anche migliori a volte).
Il avanti
il vettore verrà utilizzato per produrre un altro vettore con metà della sua lunghezza:
Il ahead2
il vettore è calcolato esattamente come avanti
, ma la sua lunghezza è dimezzata:
avanti = posizione + normalizza (velocità) * MAX_SEE_AHEAD ahead2 = position + normalize (velocity) * MAX_SEE_AHEAD * 0.5
Vogliamo eseguire un controllo di collisione per verificare se uno di questi due vettori si trova all'interno della sfera di ostacolo. Questo si ottiene facilmente confrontando la distanza tra la fine del vettore e il centro della sfera.
Se la distanza è inferiore o uguale al raggio della sfera, il vettore si trova all'interno della sfera e viene rilevata una collisione:
Se o dei due vettori avanti sono all'interno della sfera di ostacolo, quindi quell'ostacolo sta bloccando la strada. La distanza euclidea tra due punti può essere usata:
distanza funzione privata (a: Object, b: Object): Number return Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); private function lineIntersectsCircle (avanti: Vector3D, ahead2: Vector3D, obstacle: Circle): Boolean // la proprietà "center" dell'ostacolo è una Vector3D. distanza di ritorno (obstacle.center, avanti) <= obstacle.radius || distance(obstacle.center, ahead2) <= obstacle.radius;
Se più di un ostacolo blocca la strada, viene selezionato per il calcolo quello più vicino (il "più minaccioso"):
La forza di evitamento deve allontanare il personaggio dall'ostacolo, permettendogli di schivare la sfera. Può essere fatto usando un vettore formato usando il centro della sfera (che è un vettore di posizione) e il avanti
vettore. Calcoliamo questa forza di evitamento come segue:
avoidance_force = ahead - obstacle_center avoidance_force = normalize (avoidance_force) * MAX_AVOID_FORCE
Dopo avoidance_force
è calcolato è normalizzato e ridimensionato da MAX_AVOID_FORCE
, che è un numero usato per definire il avoidance_force
lunghezza. Il più grande MAX_AVOID_FORCE
è, più forte è la forza di evitamento che allontana il personaggio dall'ostacolo.
L'implementazione finale per il collisionAvoidance ()
metodo, che restituisce la forza di evitamento, è:
private function collisionAvoidance (): Vector3D ahead = ...; // calcola il vettore avanti ahead2 = ...; // calcola il vettore ahead2 var mostThreatening: Obstacle = findMostThreateningObstacle (); var avoidance: Vector3D = new Vector3D (0, 0, 0); if (mostThreatening! = null) avoidance.x = ahead.x - mostThreatening.center.x; avoidance.y = ahead.y - mostThreatening.center.y; avoidance.normalize (); avoidance.scaleBy (MAX_AVOID_FORCE); else avoidance.scaleBy (0); // annulla la forza di evitamento evitamento di ritorno; funzione privata findMostThreateningObstacle (): Obstacle var mostThreatening: Obstacle = null; per (var i: int = 0; i < Game.instance.obstacles.length; i++) var obstacle :Obstacle = Game.instance.obstacles[i]; var collision :Boolean = lineIntersecsCircle(ahead, ahead2, obstacle); // "position" is the character's current position if (collision && (mostThreatening == null || distance(position, obstacle) < distance(position, mostThreatening))) mostThreatening = obstacle; return mostThreatening;
La forza di evitamento deve essere aggiunta al vettore di velocità del personaggio. Come precedentemente spiegato, tutte le forze di guida possono essere combinate in una, producendo una forza che rappresenta tutto il comportamento attivo che agisce sul personaggio.
A seconda dell'angolo e della direzione della forza di prevenzione, non interromperà altre forze di guida, come la ricerca o la deviazione. La forza di evitamento viene aggiunta alla velocità del giocatore come al solito:
steering = nothing (); // il vettore nullo, che significa sterzata "forza zero" = sterzo + ricerca (); // supponendo che il personaggio stia cercando qualcosa di sterzo = sterzo + collisione Evidenza (); sterzo = troncato (sterzo, max_force) sterzata = sterzo / velocità di massa = troncata (velocità + sterzata, max_velocità) posizione = posizione + velocità
Poiché tutti i comportamenti di guida vengono ricalcolati ad ogni aggiornamento del gioco, la forza di evitamento rimarrà attiva fintanto che l'ostacolo bloccherà il percorso.
Non appena l'ostacolo non intercetta il avanti
linea vettoriale, la forza di evitamento diventerà nulla (nessun effetto) o sarà ricalcolata per evitare il nuovo ostacolo minaccioso. Il risultato è un personaggio in grado di evitare gli ostacoli:
L'implementazione attuale ha due problemi, entrambi relativi al rilevamento delle collisioni. Il primo succede quando il avanti
i vettori sono fuori dalla sfera degli ostacoli, ma il personaggio è troppo vicino (o all'interno) all'ostacolo.
Se ciò accade, il personaggio toccherà (o inserirà) l'ostacolo, saltando il processo di evitamento perché non è stata rilevata alcuna collisione:
avanti
i vettori sono fuori dall'ostacolo, ma il personaggio è dentro. Questo problema può essere risolto aggiungendo un terzo vettore al controllo di collisione: il vettore di posizione del personaggio. L'uso di tre vettori migliora notevolmente il rilevamento delle collisioni.
Il secondo problema si verifica quando il personaggio è vicino all'ostacolo, allontanandolo da esso. A volte le manovre causano una collisione, anche se il personaggio sta semplicemente ruotando per affrontare un'altra direzione:
Questo problema può essere risolto ridimensionando il file avanti
vettori in base alla velocità attuale del personaggio. Il codice per calcolare il avanti
il vettore, ad esempio, è cambiato in:
dynamic_length = length (velocity) / MAX_VELOCITY ahead = position + normalize (velocity) * dynamic_length
La variabile dynamic_length
andrà da 0 a 1. Quando il personaggio si muove a tutta velocità, dynamic_length
è 1; quando il personaggio sta rallentando o accelerando, dynamic_length
è 0 o superiore (ad esempio 0,5).
Di conseguenza, se il personaggio sta semplicemente manovrando senza muoversi, dynamic_length
tende a zero, producendo un nulla avanti
vettore, che non ha collisioni.
Di seguito è riportato il risultato con questi miglioramenti:
Per dimostrare il comportamento di evitare collisioni in azione, penso che un'orda di zombi sia la soluzione perfetta. Di seguito una demo che mostra diversi zombi (con diverse velocità) alla ricerca del cursore del mouse. Art di SpicyPixel e Clint Bellanger, di OpenGameArt.
Il comportamento di evitare le collisioni consente a qualsiasi personaggio di evitare gli ostacoli nell'ambiente. Dal momento che tutte le forze di guida vengono ricalcolate ad ogni aggiornamento di gioco, i personaggi interagiscono perfettamente con diversi ostacoli, analizzando sempre il più minaccioso (il più vicino).
Anche se questo comportamento non è un algoritmo di individuazione dei percorsi, i risultati ottenuti sono abbastanza convincenti per le mappe affollate.