Regioni vettoriali nascondendo da un campo visivo

In precedenza, abbiamo esplorato l'approccio all'uso delle regioni vettoriali per implementare il campo visivo di una torretta. Le truppe si avvicinarono alla torretta in campo aperto e non ci furono ostacoli tra loro. Supponiamo ora che esista un ostacolo, diciamo un muro, che offusca la visibilità della truppa dalla torretta; come dovremmo impiantarlo? Questo tutorial suggerisce un approccio per affrontare questo problema.


Anteprima del risultato finale

Diamo un'occhiata al risultato finale su cui lavoreremo. Fare clic sulla torretta nella parte inferiore dello stage per avviare la simulazione.


Passaggio 1: il concetto di base

Ecco cosa cerchiamo di ottenere in questo tutorial. Osserva l'immagine qui sopra. La torretta può vedere l'unità trooper se è all'interno del campo visivo della torretta (in alto). Una volta posizionato un muro tra la torretta e il soldato, la visibilità del soldato è protetta dalla torretta.


Passaggio 2: riassociare

Prima di tutto, facciamo una piccola revisione. Diciamo che il vettore della linea di vista della torretta è P e che il vettore dalla torretta al soldato è Q. Il soldato è visibile alla torretta se:

  • L'angolo tra P e Q è inferiore all'angolo di campo (in questo caso 30 ° su entrambi i lati)
  • La magnitudo di P è più di Q

Passaggio 3: Panoramica sull'approccio

Sopra è lo pseudo-codice per l'approccio che intraprenderemo. Determinare se il trooper si trova nel campo visivo della torretta (FOV) è spiegato nel passaggio 2. Ora proviamo a determinare se il trooper è dietro un muro.

Dovremo utilizzare le operazioni vettoriali per raggiungere questo obiettivo. Sono sicuro che, menzionando questo, mi vengono in mente rapidamente i prodotti punto e croce. Faremo una piccola deviazione per rivedere queste due operazioni vettoriali solo per essere sicuri che tutti possano seguire.


Passaggio 4: punto e croce prodotto tra vettori

Rivediamo le operazioni vettoriali: dot prodotto e cross product. Questa non è una lezione di matematica, e abbiamo già trattato questi dettagli in maggior dettaglio, ma comunque è bene rinfrescare la memoria sul funzionamento, quindi ho incluso l'immagine qui sopra. Lo schema mostra l'operazione "B punto A" (angolo in alto a destra) e "B croce A" (angolo in basso a destra).

Più importanti sono le equazioni di queste operazioni. Dai un'occhiata all'immagine qui sotto. | A | e | B | fare riferimento al grandezza scalare di ogni vettore - la lunghezza della freccia. Si noti che il prodotto punto si riferisce al coseno dell'angolo tra i vettori e il prodotto incrociato si riferisce al seno dell'angolo tra i vettori.


Passaggio 5: Seno e Coseno

Avanzando ulteriormente nell'argomento, la trigonometria entra in gioco: il seno e il coseno. Sono sicuro che questi grafici riaccendono ricordi affettuosi (o angoscia). Fare clic sui pulsanti sulla presentazione Flash di seguito per visualizzare i grafici con unità diverse (gradi o radianti).

Nota che queste forme d'onda sono continue e ripetitive. Ad esempio, puoi tagliare e incollare l'onda sinusoidale nell'intervallo negativo per ottenere qualcosa di simile in basso.


Passaggio 6: riepilogo dei valori

Grado Seno di laurea Coseno di laurea
-180 0 -1
-90 -1 0
0 0 1
90 1 0
180 0 -1

La tabella sopra mostra i valori del coseno e del seno corrispondenti a determinati gradi. Noterai che il grafico sinusoidale positivo copre un intervallo compreso tra 0 ° e 180 ° e copre il grafico positivo del coseno da -90 ° a 90 °. Metteremo questi valori in relazione al prodotto punto e incroceremo il prodotto in un secondo momento.


Passaggio 7: Interpretazione geometrica del prodotto a punti

Quindi, come possono essere utili? Per tagliare al punto, il prodotto punto è una misura di come parallelo i vettori sono mentre il prodotto incrociato è una misura di come ortogonale i vettori sono.

Affrontiamo prima il prodotto con punti. Richiama la formula per il prodotto punto, come menzionato al punto 4. Possiamo determinare se il risultato è positivo o negativo osservando il coseno dell'angolo inserito tra i due vettori. Perché? Perché la grandezza di un vettore è sempre positiva. L'unico parametro rimasto a dettare il segno del risultato è il coseno dell'angolo.

Ancora, ricorda che il grafico positivo del coseno copre -90 ° - 90 °, come nel punto 6. Pertanto, il prodotto punto di A con uno qualsiasi dei vecotrs L, M, N, O sopra produrrà un valore positivo, perché l'angolo è incuneato tra A e qualsiasi di questi vettori è compreso tra -90 ° e 90 °! (Per essere precisi, la gamma positiva è più simile a -89 ° - 89 ° perché entrambi -90 ° e 90 ° producono valori di coseno pari a 0, che ci porta al punto successivo.) Il prodotto punto tra A e P (dato P è perpendicolare a A) produrrà 0. Il resto penso che si possa già intuire: il prodotto punto di A con K, R o Q produrrà un valore negativo.

Utilizzando il prodotto punto, possiamo dividere l'area sul nostro palco in due regioni. Il prodotto punto del vettore sottostante con qualsiasi punto che si trova all'interno della regione marcata "x" produrrà un valore positivo, mentre il prodotto punto con quelli nella regione contrassegnata con "o" produrrà valori negativi.


Passaggio 8: interpretazione geometrica del prodotto incrociato

Passiamo al prodotto incrociato. Ricorda che il prodotto a croce si riferisce al seno di angolo inserito tra i due vettori. Il grafico sinusoidale positivo copre un intervallo compreso tra 0 ° e 180 °; l'intervallo negativo copre da 0 ° a -180 °. L'immagine sotto riassume questi punti.

Quindi, guardando ancora il diagramma del punto 7, il prodotto incrociato tra A e K, L o M produrrà valori positivi, mentre il prodotto incrociato tra A e N, O, P o Q produrrà valori negativi. Il prodotto incrociato tra A e R produrrà 0, poiché il seno di 180 ° è 0.

Per chiarire ulteriormente, il prodotto incrociato del vettore tra qualsiasi punto che si trova nella regione marcata "o" sotto sarà positivo, mentre quelli nella regione marcata con "x" saranno negativi.

Un punto da prendere in considerazione è che, a differenza del prodotto dot, il prodotto cross è sensibile alla sequenza. Questo significa risultati di AxB e BXA sarà diverso in termini di direzione. Quindi, mentre scriviamo il nostro programma, dobbiamo essere precisi nella scelta del vettore da confrontare.

(Nota: questi concetti spiegati si applicano allo spazio cartesiano 2D).


Passaggio 9: Applicazione demo

Per rafforzare la tua comprensione, ho inserito qui una piccola applicazione per farti giocare. Clicca sulla palla blu nella parte superiore del palco e trascinala. Mentre ti sposti, il valore della casella di testo si aggiornerà a seconda dell'operazione che hai scelto (punto o croce tra la freccia statica e quella che controlli).

È possibile osservare una stranezza con la direzione invertita del prodotto incrociato. La regione in alto è negativa e il fondo è positivo, in contrasto con la nostra spiegazione nel passaggio precedente. Bene, questo è dovuto all'asse y invertito nello spazio delle coordinate Flash rispetto allo spazio delle coordinate cartesiane; punta verso il basso, mentre tradizionalmente i matematici la considerano come rivolta verso l'alto.


Passaggio 10: definizione delle regioni

Ora che hai capito il concetto di regioni, facciamo un po 'di pratica. Divideremo il nostro spazio in quattro quadranti: A1, A2, B1, B2.

Ho elencato i risultati per verificare di seguito. "Vector" qui si riferisce alla freccia nell'immagine sopra. "Punto" si riferisce a qualsiasi coordinata nella regione specificata. Il vettore divide lo stage in quattro aree principali, dove i divisori (linee tratteggiate) si estendono all'infinito.

Regione Vettore sul diagramma croce prodotto con punto Vettore sul prodotto punto diagramma con punto
A1 (+), a causa dello spazio delle coordinate Flash (+)
A2 (+) (-)
B1 (-), a causa dello spazio delle coordinate Flash (+)
B2 (-) (-)

Passaggio 11: regioni divise

Ecco la presentazione Flash che mostra le idee come spiegato nel passaggio 10. Fai clic con il pulsante destro del mouse sul palco per aprire il menu di scelta rapida e selezionare la regione che desideri vedere evidenziata.


Passaggio 12: implementazione

Ecco l'implementazione di ActionScript del concetto spiegato nel Passaggio 10. Sentiti libero di visualizzare l'intero pezzo di codice nel download sorgente, come AppLine.as.

 // evidenziando il colore in base alla selezione utente funzione privata color (): void // ogni pallina sul palco viene verificata in base alle condizioni del caso selezionato per ciascuna voce (var item: Ball in sp) var vec1: Vector2D = new Vector2D (item. x - stage.stageWidth * 0.5, item.y - stage.stageHeight * 0.5); if (seleziona == 0) if (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455;  else if (seleziona == 1) if (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455;  else if (seleziona == 2) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455;  else if (seleziona == 3) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1) <0) item.col = 0xFF9933; else item.col = 0x334455;  else if (select == 4) if (vec.vectorProduct(vec1) < 0 &&vec.dotProduct(vec1) > 0) item.col = 0xFF9933; else item.col = 0x334455;  else if (seleziona == 5) if (vec.vectorProduct (vec1) < 0 &&vec.dotProduct(vec1) < 0) item.col = 0xFF9933; else item.col = 0x334455;  item.draw();   //swapping case according to user selction private function swap(e:ContextMenuEvent):void  if (e.target.caption == "VectorProduct") select = 0; else if (e.target.caption == "DotProduct") select = 1; else if (e.target.caption == "RegionA1") select = 2; else if (e.target.caption == "RegionA2") select = 3; else if (e.target.caption == "RegionB1") select = 4; else if (e.target.caption == "RegionB2") select = 5; 

Step 13: Visibilità schermata

Avendo compreso le interpretazioni geometriche del prodotto punto e del prodotto incrociato, lo applicheremo al nostro scenario. La presentazione Flash sopra mostra variazioni dello stesso scenario e riassume le condizioni applicate a un soldato protetto da un muro ancora all'interno del FOV della torretta. È possibile scorrere tra i frame usando i pulsanti freccia.

Le seguenti spiegazioni si basano sullo spazio delle coordinate Flash 2D. Nel fotogramma 1, un muro è posto tra la torretta e il soldato. Sia A e B i vettori dalla torretta alla coda e la testa del vettore del muro, rispettivamente. Sia C il vettore del muro, e D sia il vettore dalla coda del muro al soldato. Infine, sia Q il vettore dalla torretta al soldato.

Ho tabulato le condizioni risultanti di seguito.

Posizione Prodotto incrociato
La truppa è di fronte al muro C x D> 0
La truppa è dietro il muro C x D

Questa non è l'unica condizione applicabile, perché abbiamo anche bisogno di limitare il trooper all'interno delle linee tratteggiate su entrambi i lati. Dai un'occhiata ai frame 2-4 per vedere il prossimo set di condizioni.

Posizione Prodotto incrociato
La truppa è all'interno dei lati del muro. Q x A 0
Truppa è a sinistra del muro Q x A> 0, Q x B> 0
Truppa è a destra del muro Q x A

Penso che i miei colleghi lettori possano ora scegliere le condizioni appropriate per determinare se il soldato è nascosto o meno dalla vista. Tieni presente che questo insieme di condizioni viene valutato dopo aver trovato la truppa all'interno del FOV della torretta (fare riferimento al punto 3).


Passaggio 14: Implementazione Actionscript

Ecco l'implementazione di ActionScript dei concetti illustrati nel passaggio 13. L'immagine sopra mostra il vettore iniziale del muro, C. Fai clic e trascina il pulsante rosso in basso e spostalo per vedere l'area schermata. È possibile visualizzare il codice sorgente completo in HiddenSector.as.

Ok, spero che tu abbia sperimentato con la palla rossa, e se sei abbastanza attento potresti aver notato un errore. Nota: non c'è un'area schermata poiché il pulsante rosso si sposta a sinistra dell'altra estremità del muro, invertendo quindi il vettore del muro in modo che punti a sinistra anziché a destra. La soluzione è nel passaggio successivo.

Tuttavia, prima diamo un'occhiata a un importante snippet di ActionScript qui in HiddenSector.as:

 private function highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50) var settore: Number = Math2.radianOf (30); per ogni (oggetto var: Ball in sp) var turret_sp: Vector2D = new Vector2D (item.x - torretta.x, item.y - torretta.y); // Q if (Math.abs (lineOfSight.angleBetween (turret_sp)) < sector)  var wall:Vector2D = new Vector2D(wall2.x - wall1.x, wall2.y - wall1.y); //C var turret_wall1:Vector2D = new Vector2D(wall1.x - turret.x, wall1.y - turret.y); //A var turret_wall2:Vector2D = new Vector2D(wall2.x - turret.x, wall2.y - turret.y); //B var wall_sp:Vector2D = new Vector2D (item.x - wall1.x, item.y - wall1.y); //D if ( wall.vectorProduct (wall_sp) < 0 // C x D && turret_sp.vectorProduct(turret_wall1) < 0 // Q x A && turret_sp.vectorProduct(turret_wall2) > 0 // Q x B) item.col = 0xcccccc else item.col = 0;  item.draw (); 

Step 15: Direction of Wall

Per risolvere questo problema, dobbiamo sapere se il vettore del muro sta puntando a sinistra oa destra. Diciamo che abbiamo un vettore di riferimento, R, che punta sempre a destra.

Direzione del vettore Prodotto a punti
Il muro punta a destra (stesso lato di R) w. R> 0
Il muro punta a sinistra (lato opposto di R) w. R

Naturalmente, ci sono altri modi per aggirare questo problema, ma immagino che sia un'opportunità per utilizzare i concetti espressi in questo tutorial, quindi eccovi.


Step 16: Twist di Actionscript

Di seguito è una presentazione Flash che implementa la correzione spiegata nel passaggio 15. Dopo aver giocato con esso, scorrere verso il basso per controllare le modifiche di ActionScript.

Le modifiche rispetto alla precedente implementazione sono evidenziate. Inoltre, i set di condizioni vengono ridefiniti in base alla direzione della parete:

 funzione privata highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50); settore var: Number = Math2.radianOf (30); var pointToRight: Vector2D = new Vector2D (10, 0); // aggiunto in una seconda versione per ogni oggetto (var: Ball in sp) var turret_sp: Vector2D = new Vector2D (item.x - turret.x, item.y - turret.y); // Q if (Math.abs (lineOfSight.angleBetween (turret_sp)) < sector)  var wall:Vector2D = new Vector2D(wall2.x - wall1.x, wall2.y - wall1.y); //C var turret_wall1:Vector2D = new Vector2D(wall1.x - turret.x, wall1.y - turret.y); //A var turret_wall2:Vector2D = new Vector2D(wall2.x - turret.x, wall2.y - turret.y); //B var wall_sp:Vector2D = new Vector2D (item.x - wall1.x, item.y - wall1.y); //D var sides: Boolean; //switches according to wall direction if (pointToRight.dotProduct(wall) > 0) sides = wall.vectorProduct (wall_sp) < 0 // C x D && turret_sp.vectorProduct(turret_wall1) < 0 // Q x A && turret_sp.vectorProduct(turret_wall2) > 0 // Q x B else sides = wall.vectorProduct (wall_sp)> 0 // C x D && turret_sp.vectorProduct (turret_wall1)> 0 // Q x A && turret_sp.vectorProduct (turret_wall2) < 0 // Q x B  if (sides)  item.col = 0xcccccc  else  item.col = 0;  item.draw();   

Guarda l'origine completa in HiddenSector2.as.


Passaggio 17: impostare il muro

Ora rimediamo al nostro lavoro Scene1.as dal tutorial precedente. Per prima cosa, installeremo il nostro muro.

Iniziamo le variabili,

 public class Scene1_2 estende Sprite private var river: Sprite; private var wall_origin: Vector2D, wall: Vector2D; // aggiunto nel secondo tutorial truppe var private: Vector.; private var troopVelo: Vector.;

... quindi disegna il muro per la prima volta,

 funzione pubblica Scene1_2 () makeTroops (); makeRiver (); makeWall (); // aggiunto in 2nd tutorial makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); function start (): void stage.addEventListener (Event.ENTER_FRAME, move); 
 funzione privata makeWall (): void wall_origin = new Vector2D (200, 260); wall = new Vector2D (80, -40); graphics.lineStyle (2, 0); graphics.moveTo (wall_origin.x, wall_origin.y); graphics.lineTo (wall_origin.x + wall.x, wall_origin.y + wall.y); 

... e ridisegna su ogni frame, perché il graphics.clear () la chiamata è da qualche parte dentro behaviourTurret ():

 // aggiunto in 2nd tutorial private function move (e: Event): void behaviourTroops (); behaviourTurret (); redrawWall (); 
 // aggiunto nella seconda funzione privata tutorial redrawWall (): void graphics.lineStyle (2, 0); graphics.moveTo (wall_origin.x, wall_origin.y); graphics.lineTo (wall_origin.x + wall.x, wall_origin.y + wall.y); 

Step 18: Interazione con Wall

Le truppe interagiranno anche con il muro. Mentre si scontrano con il muro, scorreranno lungo il muro. Non cercherò di entrare nei dettagli di questo dato che è stato ampiamente documentato in Collision Reaction Between a Circle and a Line Segment. Invito i lettori a verificarlo per ulteriori spiegazioni.

Il seguente frammento vive nella funzione behaviourTroops ().

 // Versione 2 // se si guada attraverso il fiume, rallenta // se collide con il muro, scorri // alla velocità normale normale var collideWithRiver: Boolean = river.hitTestObject (truppe [i]) var wall_norm: Vector2D = wall.rotate ( Math2.radianOf (-90)); var wall12Troop: Vector2D = new Vector2D (truppe [i] .x - wall_origin.x, truppe [i] .y - wall_origin.y); var collideWithWall: Boolean = troops [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; if (collideWithRiver) truppe [i] .y + = troopVelo [i] .y * 0.3; altrimenti if (collideWithWall) // riposiziona troop var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (truppe [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (muro)); riposizionamento delle variabili: Vector2D = projOnNorm.add (projOnWall); truppe [i] .x = wall_origin.x + reposition.x; truppe [i] .y = wall_origin.y + reposition.y; // scorri attraverso il muro var adjustment: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (regolazione); slideVelo = slideVelo.add (troopVelo [i]) truppe [i] .x + = slideVelo.x; truppe [i] .y + = slideVelo.y;  else troops [i] .y + = troopVelo [i] .y

Passo 19: Controllo della "Nascondità" delle truppe

Infine, veniamo alla carne di questo tutorial: impostare la condizione e verificare se i soldati sono dietro il muro e quindi protetti dalla visibilità della torretta. Ho evidenziato i codici patch importanti:

 // controlla se il nemico è in vista // 1. All'interno del settore di vista // 2. Entro il campo di vista // 3. Più vicino al nemico più vicino attuale var c1: Boolean = Math.abs (lineOfSight.angleBetween (turret2Item)) < Math2.radianOf(sectorOfSight) ; var c2:Boolean = turret2Item.getMagnitude() < lineOfSight.getMagnitude(); var c3:Boolean = turret2Item.getMagnitude() < closestDistance; //Checking whether troop is shielded by wall var withinLeft:Boolean = turret2Item.vectorProduct(turret2wall1) < 0 var withinRight:Boolean = turret2Item.vectorProduct(turret2wall2) > 0 var behindWall: Boolean = wall.vectorProduct (wall12troop) < 0; var shielded:Boolean = withinLeft && withinRight && behindWall //if all conditions fulfilled, update closestEnemy if (c1 && c2&& c3 && !shielded) closestDistance = turret2Item.getMagnitude(); closestEnemy = item; 

Controlla il codice completo in Scene1_2.as.


Passaggio 20: Avviare l'applicazione

Infine, possiamo sederci e controllare la patch in azione. Premi Ctrl + Invio per vedere i risultati del tuo lavoro. Ho incluso una copia della presentazione Flash funzionante di seguito. Fare clic sulla torretta nella parte inferiore dello stage per avviare la simulazione.