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.
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:
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.
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:
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
.
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.
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.
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:
Anche se questo risultato è abbastanza convincente, sembra un risultato "robotico". Una vera folla di solito non ha spazi vuoti tra i loro membri.
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:
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:
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:
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:
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:
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!