Rilevamento di collisione a livello di pixel basato su pixel

In questo tutorial, seguirò l'approccio suggerito da Richard Davey (Grazie, Richard!), E usato da lui e altri, nel rilevare le collisioni tra le bitmap con una modifica sottile. Inoltre, confronterò le prestazioni tra vari approcci alla rilevazione delle collisioni bitmap utilizzando l'imbracatura PerformanceTest di Grant Skinner.

Nota: oltre a far parte della sessione Shoot-E-Up, questo articolo fa anche parte di Collision Detection e Reaction.


Passaggio 1: panoramica

Descrivo questo approccio alternativo in breve qui.

  1. Controlla se c'è qualche sovrapposizione tra i due bitmap.
  2. Se c'è, procedere al # 3. Altrimenti, abbandona.
  3. Verificare se l'area di sovrapposizione ha sovrapposti pixel opachi.
  4. In tal caso, i bitmap si sovrappongono. Altrimenti, abbandona.

Passaggio 2: caselle vincolanti

Per prima cosa, controlliamo se le caselle di delimitazione dei due bitmap si sovrappongono usando Rectangle. Gli script sono come sotto. Innanzitutto, le variabili.

 private var enemy1: Bitmap, myShip: Bitmap; private var myShipSp: Sprite; private var rec_e: Rectangle, rec_m: Rectangle; private var intersec: Rectangle;
 enemy1 = new E1 as Bitmap; addChild (enemy1); myShip = new My as Bitmap; myShipSp = new Sprite; addChild (myShipSp); myShipSp.addChild (MiaSped); enemy1.x = stage.stageWidth >> 1; myShipSp.x = stage.stageWidth >> 1; enemy1.y = stage.stageHeight * 0.2; myShipSp.y = stage.stageHeight * 0.8; // disegno di caselle attorno al disegno sprite (enemy1.getBounds (stage), this, 0); draw (myShipSp.getBounds (stage), this, 0);

Qui controlliamo l'eventuale sovrapposizione tra le caselle. Check-out DetectVisible.as nel download di origine per lo script completo

 refresh di funzione privata (e: Event): void // determinazione del riquadro di delimitazione dell'area di intersezione rec_e = enemy1.getBounds (stage); rec_m = myShipSp.getBounds (stage); intersec = rec_e.intersection (rec_m); // ridisegna il riquadro di delimitazione di entrambi gli sprite this.graphics.clear (); disegnare (enemy1.getBounds (stage), this, 0); draw (myShipSp.getBounds (stage), this, 0); // disegna solo una casella di delimitazione dell'area di intersezione se ce n'è una se (! intersec.isEmpty ()) lines.graphics.clear (); disegnare (intersec, linee); t.text = "Area di intersezione con rettangolo rosso."  else t.text = "Nessuna area di intersezione." 

Ecco una demo. Trascina l'astronave più piccola intorno.

(Non preoccuparti della scatola rossa che viene "lasciata indietro" quando la nave viene trascinata fuori dal riquadro di delimitazione dell'altro).


Passaggio 3: disegno nell'area di intersezione

Quindi, se c'è un'area di intersezione, procediamo a verificare se ci sono pixel sovrapposti nell'area. Tuttavia, proviamo prima a disegnare bitmap in questa area di intersezione. La sceneggiatura completa è in DetectVisible2.as

 refresh di funzione privata (e: Event): void // determinazione del riquadro di delimitazione dell'area di intersezione rec_e = enemy1.getBounds (stage); rec_m = myShipSp.getBounds (stage); intersec = rec_e.intersection (rec_m); // ridisegna il riquadro di delimitazione di entrambi gli sprite this.graphics.clear (); disegnare (enemy1.getBounds (stage), this, 0); draw (myShipSp.getBounds (stage), this, 0); // disegna solo una casella di delimitazione dell'area di intersezione se ce n'è una se (! intersec.isEmpty ()) lines.graphics.clear (); disegnare (intersec, linee); // per tracciare l'area di intersezione e verificare la sovrapposizione di aree colorate var eM: Matrix = enemy1.transform.matrix; var myM: Matrix = myShipSp.transform.matrix; bdt_intersec = new BitmapData (intersec.width, intersec.height, false, 0) eM.tx - = intersec.x; eM.ty - = intersec.y myM.tx - = intersec.x; myM.ty - = intersec.y bdt_intersec.draw (enemy1, eM); bdt_intersec.draw (myShip, myM); bm_intersec.bitmapData = bdt_intersec; bm_intersec.x = 10 bm_intersec.y = stage.stageHeight * 0.8 - bm_intersec.height; t.text = "Area di intersezione con rettangolo rosso. \ n" else t.text = "Nessuna area di intersezione." 

Si noti che, dal momento che stiamo disegnando l'area con l'uso di una matrice, qualsiasi ridimensionamento, inclinazione e altre trasformazioni su entrambe le bitmap vengono prese in considerazione. Ecco una demo; controlla la casella nell'angolo in basso a sinistra.


Passaggio 4: verificare il colore nell'area di intersezione

Quindi, come controlliamo il pixel giusto? Prima di tutto, diamo una tonalità di nero al colore di questa scatola di intersezione (rosso = 0, verde = 0, blu = 0). Quindi, l'ombra dell'astronave più piccola verrà dipinta in questa casella scura come verde, con la modalità di fusione ADD. Allo stesso modo, l'ombra della più grande astronave aliena stazionaria sarà dipinta di rosso.

Così ora, ci saranno aree di rosso e verde per le astronavi, e nere se non ci sono aree sovrapposte. Tuttavia, se ci sono pixel da questi due bitmap che si sovrappongono, questi saranno disegnati in giallo (rosso = 255, verde = 255, blu = 0). Usiamo il metodo Bitmapdata.getColorBoundsRect per verificare l'esistenza di questa zona.

Ecco lo snippet in Main.as

 // per tracciare l'area di intersezione e verificare la sovrapposizione di aree colorate var eM: Matrix = enemy1.transform.matrix; var myM: Matrix = myShipSp.transform.matrix; bdt_intersec = new BitmapData (intersec.width, intersec.height, false, 0) eM.tx - = intersec.x; eM.ty - = intersec.y myM.tx - = intersec.x; myM.ty - = intersec.y // modifica colore bdt_intersec.draw (nemico1, eM, nuovo ColorTransform (1,1,1,1,255, -255, -255), BlendMode.ADD); bdt_intersec.draw (myShip, myM, new ColorTransform (1,1,1,1, -255,255, -255), BlendMode.ADD); bm_intersec.bitmapData = bdt_intersec; bm_intersec.x = 10 bm_intersec.y = stage.stageHeight * 0.8 - bm_intersec.height; t.text = "Area di intersezione con rettangolo rosso. \ n" // controlla l'esistenza del colore giusto intersec_color = bdt_intersec.getColorBoundsRect (0xffffff, 0xffff00); if (! intersec_color.isEmpty ()) t.appendText ("E ci sono pixel interesecanti nell'area.");

Nota che sopprimiamo i componenti Rosso e Blu nella linea 113 al massimo in Verde per la piccola astronave. Sulla linea 112 facciamo lo stesso con l'astronave aliena con i componenti Blue e Green.


Confronto degli approcci

Quindi, dopo aver ricevuto commenti su problemi di prestazioni relativi al rilevamento delle collisioni, ho deciso di eseguire alcuni test rapidi e sporchi su questi approcci. Ho creato 20 astronavi nemiche e un'astronave di un giocatore e controllato il rilevamento delle collisioni tra quella nave di un giocatore contro l'altra 20. Questi sprite sono imballati nelle stesse vicinanze per forzare il rilevamento delle collisioni per tutti gli approcci per avere una corsa completa.

Il primo approccio è il più semplice. BitmapData viene catturato all'avvio e per ogni frame, il rilevamento delle collisioni viene verificato utilizzando BitmapData.hitTest (). Per il secondo approccio, BitmapData viene aggiornato ogni frame e il rilevamento delle collisioni viene eseguito in base a quelli BitmapData catturato. Il terzo si riferisce all'approccio suggerito da questo tutorial.

Quindi il risultato di uno dei test che ho fatto è il seguente.

 - bitmapdata fixed (1000 iterations) Versione del player: WIN 11,1,102,55 (debug) - metodo ... ttl ms ... avg ms bitmapdata fixed 168 0.17 - - aggiornamenti bitmapdata (1000 iterazioni) Versione del player: WIN 11,1,102,55 (debug) - metodo ... ttl ms ... avg ms bitmapdata updates 5003 5,00 - - metodo personalizzato (1000 iterazioni) Versione del player: WIN 11,1,102,55 (debug) - metodo ... ttl ms ... avg ms metodo personalizzato 4408 4.41 -

Il Test della prestazione dà risultati diversi ogni volta che eseguo il test. Così l'ho eseguito alcune volte e derivato un tempo medio. Conclusione: il metodo più veloce è il primo, seguito dal terzo e poi dal secondo approccio.

Quindi riporre via BitmapData per bitmap quando vengono introdotti per la prima volta nello stage e per il controllo hitTest ogni fotogramma dopo è effettivamente efficiente, a condizione che questi sprite non eseguano trasformazioni diverse dalla traduzione (come rotazione, inclinazione e ridimensionamento) nel tempo. Altrimenti, sarai costretto ad adottare il secondo o il terzo approccio e il terzo sarà più efficiente come indicato dall'immagine sopra.

Puoi controllare Collisions.as e Results.as per la sceneggiatura completa.


Alla ricerca di metodi costosi

Mi sono imbarcato in seguito per cercare le linee specifiche di codice che hanno richiesto più tempo di calcolo. Il secondo e il terzo approccio richiedevano più tempo, quindi ne ricavai diverse funzioni, ognuna delle quali si rompeva in punti diversi. Dai un'occhiata a uno dei risultati qui sotto.

 - default hitTest (1000 iterazioni) Versione del player: WIN 11,1,102,55 (debug) include bounds - metodo ... ttl ms ... avg ms default hitTest 189 0.19 - - hitTest predefinito (1000 iterazioni) Versione del player: WIN 11,1,102,55 ( debug) include transform - metodo ... ttl ms ... avg ms default hitTest 357 0.36 - - hitTest predefinito (1000 iterazioni) Versione del player: WIN 11,1,102,55 (debug) include hittest - metodo ... ttl ms ... avg ms default hitTest 4427 4.43 - - metodo personalizzato (1000 iterazioni) Versione del player: WIN 11,1,102,55 (debug) inlcude bounds e transform - metodo ... ttl ms ... avg ms metodo personalizzato 411 0.41 - - metodo personalizzato (1000 iterazioni) Versione del player: WIN 11, 1,102,55 (debug) include draw e bound - metodo ... ttl ms ... avg ms metodo personalizzato 3320 3.32 -

La prima, la seconda e la terza volta si riferiscono al secondo approccio a diversi breakpoint e la quarta e la quinta volta si riferiscono al terzo approccio. Guardando il terzo e il quinto tempo risultati, BitmapData.draw sembra richiedere molto tempo di calcolo. E il tempo impiegato per disegnare con il secondo approccio sembra essere più costoso in termini di tempo di calcolo, il che mi porta a pensare che le dimensioni per BitmapData.draw operare su importa. Puoi controllare Collisions2.as e Results2.as per gli script completi.

Una cosa che trovo un po 'inquietante è l'incongruenza di questi test: non ottengo sempre risultati identici, sebbene seguano quasi sempre la stessa classifica. Quindi, è abbastanza buono per fare un semplice confronto tra le funzioni.

Conclusione

Bene, grazie per il tuo tempo guardando questo piccolo consiglio. Spero che sia stato utile. Lascia commenti se non sei d'accordo con nulla in questo tutorial. Mi piacerebbe rispondere al feedback!