Comprensione dei comportamenti dello sterzo coda

Immagina una scena di gioco in cui una stanza è affollata di entità controllate dall'intelligenza artificiale. Per qualche ragione, devono lasciare la stanza e passare attraverso una porta. Invece di farli camminare l'uno sull'altro in un flusso caotico, insegnagli come lasciarsi educatamente in fila. Questo tutorial presenta il coda guidare il comportamento con approcci diversi per far muovere una folla mentre si formano file di entità.

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.


introduzione

Queuing, nel contesto di questo tutorial, è il processo di stare in fila, formando una fila di personaggi che stanno pazientemente aspettando di arrivare da qualche parte. Mentre il primo della linea si muove, il resto segue, creando un modello che sembra un treno che tira i carri. Durante l'attesa, un personaggio non dovrebbe mai lasciare la linea.

Per illustrare il comportamento della coda e mostrare le diverse implementazioni, una demo con una "scena di accodamento" è il modo migliore per andare. Un buon esempio è una stanza affollata di entità controllate dall'IA, tutte che cercano di lasciare la stanza e passare attraverso la porta:


Scambia la stanza e passa attraverso la porta senza il comportamento in coda. Clicca per mostrare le forze.

Questa scena è stata realizzata utilizzando due comportamenti precedentemente descritti: ricerca e prevenzione delle collisioni.

La porta è composta da due ostacoli rettangolari posizionati uno accanto all'altro con uno spazio tra loro (la porta). I personaggi cercano un punto situato dietro quello. Quando sono lì, i personaggi vengono riposizionati nella parte inferiore dello schermo.

In questo momento, senza il comportamento della coda, la scena sembra un'orda di selvaggi che si pestano l'un l'altro per raggiungere la destinazione. Quando avremo finito, la folla lascerà il posto senza problemi, formando file.


Vedendo avanti

La prima abilità che un personaggio deve ottenere per mettersi in fila è scoprire se c'è qualcuno davanti a loro. Sulla base di tali informazioni, può decidere se continuare o smettere di muoversi.

Nonostante esistano modi più sofisticati per controllare i vicini, userò un metodo semplificato basato sulla distanza tra un punto e un personaggio. Questo approccio è stato utilizzato nel comportamento di evitare le collisioni per verificare gli ostacoli futuri:


Test per i vicini utilizzando il punto avanti.

Un punto chiamato avanti è proiettato di fronte al personaggio. Se la distanza tra quel punto e un carattere vicino è inferiore o uguale a MAX_QUEUE_RADIUS, significa che c'è qualcuno davanti e il personaggio deve smettere di muoversi.

Il avanti il punto è calcolato come segue (pseudo-codice):

 // Sia qa che ahead sono vettori matematici qa = normalize (velocity) * MAX_QUEUE_AHEAD; avanti = qa + posizione;

La velocità, che dà anche la direzione del personaggio, viene normalizzata e ridimensionata da MAX_QUEUE_AHEAD per produrre un nuovo vettore chiamato qa. quando qa è aggiunto al posizione vettore, il risultato è un punto davanti al personaggio e una distanza di MAX_QUEUE_AHEAD unità lontane da esso.

Tutto questo può essere avvolto nel getNeighborAhead () metodo:

 funzione privata getNeighborAhead (): Boid var i: int; var ret: Boid = null; var qa: Vector3D = velocity.clone (); qa.normalize (); qa.scaleBy (MAX_QUEUE_AHEAD); avanti = position.clone (). add (qa); per (i = 0; i < Game.instance.boids.length; i++)  var neighbor :Boid = Game.instance.boids[i]; var d :Number = distance(ahead, neighbor.position); if (neighbour != this && d <= MAX_QUEUE_RADIUS)  ret = neighbor; break;   return ret; 

Il metodo controlla la distanza tra il avanti punto e tutti gli altri caratteri, restituendo il primo carattere la cui distanza è inferiore o uguale a MAX_QUEUE_AHEAD. Se non viene trovato alcun carattere, il metodo ritorna nullo.


Creazione del metodo di accodamento

Come con tutti gli altri comportamenti, il forza di accodamento è calcolato con un metodo chiamato coda():

 private function queue (): Vector3D var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) // TODO: agire perché il vicino è avanti return new Vector3D (0, 0); 

Il risultato di getNeighborAhead () memorizzato nella variabile vicino di casa. Se vicino! = null significa che c'è qualcuno davanti; altrimenti, il percorso è chiaro.

Il coda(), come tutti gli altri metodi di comportamento, deve restituire una forza che è la forza di governo relativa al metodo stesso. coda() restituirà una forza senza grandezza per ora, quindi non produrrà effetti.

Il aggiornare() il metodo di tutti i personaggi nella scena della porta, fino ad ora, è (pseudo-codice):

 public function update (): void var doorway: Vector3D = getDoorwayPosition (); sterzo = ricerca (porta); // cerca lo sterzo della porta = sterzo + collisione Evidente (); // evitare ostacoli direzione = sterzo + coda (); // coda lungo la direzione sterzata = troncata (sterzo, MAX_FORCE); sterzo = sterzo / massa; velocity = truncate (velocity + steering, MAX_SPEED); posizione = posizione + velocità;

Da coda() restituisce una forza nulla, i personaggi continueranno a muoversi senza formare righe. È tempo di fare in modo che intervengano quando viene rilevato un vicino.


Alcune parole sull'arresto del movimento

I comportamenti dello sterzo sono basati su forze che cambiano costantemente, quindi l'intero sistema diventa molto dinamico. A seconda dell'implementazione, più sono le forze coinvolte, più diventa difficile individuare e cancellare un vettore di forza specifico.

L'implementazione utilizzata in questa serie di comportamento dello sterzo aggiunge tutte le forze. Di conseguenza, per annullare una forza, deve essere ricalcolata, invertita e aggiunta nuovamente al vettore corrente della forza di governo.

Questo è praticamente ciò che accade nel comportamento di arrivo, in cui la velocità viene annullata per far smettere di muoversi il personaggio. Ma cosa succede quando più forze agiscono insieme, come evitare le collisioni, fuggire e altro ancora?

Le seguenti sezioni presentano due idee per far smettere di muoversi un personaggio. Il primo utilizza un approccio "hard stop" che agisce direttamente sul vettore di velocità, ignorando tutte le altre forze di governo. Il secondo usa un vettore di forza, chiamato freno, per cancellare con grazia tutte le altre forze di governo, facendo in modo che il personaggio smetta di muoversi.


Arresto del movimento: "Hard Stop"

Diverse forze di guida sono basate sul vettore di velocità del personaggio. Se quel vettore cambia, tutte le altre forze saranno influenzate quando vengono ricalcolate. L'idea di "hard stop" è piuttosto semplice: se c'è un personaggio davanti a noi, "riduciamo" il vettore di velocità:

 private function queue (): Vector3D var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) velocity.scaleBy (0.3);  return new Vector3D (0, 0); 

Nel codice sopra, il velocità il vettore è ridimensionato a 30% della sua magnitudine attuale (lunghezza) mentre un personaggio è avanti. Di conseguenza, il movimento viene drasticamente ridotto, ma alla fine tornerà alla sua normale ampiezza quando il personaggio che sta bloccando il modo in cui si muove.

È più facile da capire analizzando il modo in cui viene calcolato il movimento ogni aggiornamento:

 velocity = truncate (velocity + steering, MAX_SPEED); posizione = posizione + velocità;

Se la velocità la forza continua a ridursi, così fa il timone forza, perché si basa sul velocità vigore. Crea un circolo vizioso che finirà con un valore estremamente basso per velocità. Questo è quando il personaggio smette di muoversi.

Quando il processo di restringimento termina, ogni aggiornamento di gioco aumenterà il velocità vector un po ', influenzando il timone forza anche. Alla fine, diversi aggiornamenti successivi porteranno entrambi velocità e timone vettore di nuovo alla loro magnitudine normale.

L'approccio "hard stop" produce il seguente risultato:


Comportamento in coda con approccio "hard stop". Clicca per mostrare le forze.

Anche se questo risultato è abbastanza convincente, sembra un risultato "robotico". Una vera folla di solito non ha spazi vuoti tra i loro membri.


Arresto del movimento: forza frenante

Il secondo approccio per fermare il movimento cerca di creare un risultato meno "robotico" annullando tutte le forze di guida attive usando a freno vigore:

 private function queue (): Vector3D var v: Vector3D = velocity.clone (); freno var: Vector3D = new Vector3D (); var vicino: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; brake.y = -steering.y * 0.8; v.scaleBy (-1); freno = freno.add (v);  freno di ritorno; 

Invece di creare il freno forza ricalcolando e invertendo ciascuna delle forze di guida attive, freno è calcolato in base all'attuale timone vettore, che contiene tutte le forze di guida aggiunte al momento:


Rappresentazione della forza frenante.

Il freno la forza riceve entrambi i suoi X e y componenti dal timone forza, ma invertita e con una scala di 0.8. Significa che freno ha l'80% della magnitudine di timone e punta nella direzione opposta.

Mancia: Usando il timone la forza direttamente è pericolosa. Se coda() è il primo comportamento da applicare a un personaggio, il timone la forza sarà "vuota". Come conseguenza, coda() deve essere invocato dopo tutti gli altri metodi di guida, in modo che possa accedere al completo e al finale timone vigore.

Il freno la forza deve anche cancellare la velocità del personaggio. Questo è fatto aggiungendo -velocità al freno vigore. Dopo di ciò, il metodo coda() può restituire la finale freno vigore.

Il risultato dell'uso della forza frenante è il seguente:


Comportamento della coda con l'approccio della forza frenante. Clicca per mostrare le forze.

La sovrapposizione dei caratteri attenuanti

L'approccio di frenatura produce un risultato più naturale rispetto a quello "robotico", perché tutti i personaggi stanno cercando di riempire gli spazi vuoti. Tuttavia, introduce un nuovo problema: i caratteri si sovrappongono.

Per risolvere questo problema, l'approccio del freno può essere migliorato con una versione leggermente modificata dell'approccio "hard stop":

 private function queue (): Vector3D var v: Vector3D = velocity.clone (); freno var: Vector3D = new Vector3D (); var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; brake.y = -steering.y * 0.8; v.scaleBy (-1); freno = freno.add (v); if (distance (position, neighbor.position) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Un nuovo test viene utilizzato per controllare i vicini vicini. Questa volta invece di usare il avanti punto per misurare la distanza, il nuovo test controlla la distanza tra i personaggi posizione vettore:


Controlla i vicini vicini entro il raggio MAX_QUEUE_RADIUS centrato sulla posizione anziché sul punto avanti.

Questo nuovo test controlla se ci sono personaggi vicini all'interno del MAX_QUEUE_RADIUS raggio, ma ora è centrato al posizione vettore. Se qualcuno è nel raggio d'azione, significa che l'area circostante sta diventando troppo affollata e probabilmente i personaggi stanno iniziando a sovrapporsi.

La sovrapposizione è mitigata dal ridimensionamento del velocità vettore fino al 30% della sua magnitudine attuale ogni aggiornamento. Proprio come nell'approccio "hard stop", restringendo il velocità il vettore riduce drasticamente il movimento.

Il risultato sembra meno "robotico", ma non è l'ideale, dal momento che i personaggi sono ancora sovrapposti alla porta:


Comportamento in coda con "hard stop" e forza frenante combinata. Clicca per mostrare le forze.

Aggiungere separazione

Anche se i personaggi stanno cercando di raggiungere la porta in modo convincente, riempiendo tutti gli spazi vuoti quando il sentiero si restringe, si avvicinano troppo l'uno all'altro sulla porta.

Questo può essere risolto aggiungendo una forza di separazione:

 private function queue (): Vector3D var v: Vector3D = velocity.clone (); freno var: Vector3D = new Vector3D (); var neighbor: Boid = getNeighborAhead (); if (neighbor! = null) brake.x = -steering.x * 0.8; brake.y = -steering.y * 0.8; v.scaleBy (-1); freno = freno.add (v); freno = freno.add (separazione ()); if (distance (position, neighbor.position) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Precedentemente usato nel comportamento seguente del leader, la forza di separazione aggiunta al freno la forza farà sì che i personaggi smettano di muoversi nello stesso momento in cui cercano di stare lontano l'uno dall'altro.

Il risultato è una folla convincente che cerca di raggiungere la porta:


Comportamento in coda con "hard stop", forza frenante e separazione combinati. Clicca per mostrare le forze.

Conclusione

Il comportamento della coda consente ai personaggi di stare in fila e attendere pazientemente di arrivare a destinazione. Una volta in linea, un personaggio non tenterà di "imbrogliare" saltando le posizioni; si muoverà solo quando si muove il personaggio proprio di fronte ad esso.

La scena della porta utilizzata in questo tutorial ha mostrato quanto questo comportamento possa essere versatile e modificabile. Alcune modifiche producono risultati diversi, che possono essere regolati con precisione in un'ampia varietà di situazioni. Il comportamento può anche essere combinato con altri, come evitare le collisioni.

Spero che questo nuovo comportamento ti sia piaciuto e inizi a utilizzarlo per aggiungere folle in movimento al tuo gioco!