Pikselnivå Kollisjonsdeteksjon Basert på Pixelfarger

I denne opplæringen følger jeg tilnærmingen til Richard Davey (Takk, Richard!), Og brukes av ham og andre til å oppdage kollisjoner mellom bitmaps med en subtil modifikasjon. Jeg vil også sammenligne ytelse mellom ulike tilnærminger til bitmap-kollisjonsdeteksjon ved hjelp av Grant Skinner PerformanceTest-sele.

Merk: I tillegg til å være en del av Shoot-'Em-Up-sesjonen, er denne artikkelen også en del av kollisjonsdeteksjon og reaksjon.


Trinn 1: Oversikt

Jeg beskriver denne alternative tilnærmingen kort her.

  1. Kontroller om det er noen overlapping mellom de to bitmaps.
  2. Hvis det er, fortsett til # 3. Ellers slippe ut.
  3. Kontroller om overlappingsområdet har noen ugjennomsiktige piksler overlappende.
  4. I så fall overlapper bitmapene. Ellers slippe ut.

Trinn 2: Bindende bokser

Først kontrollerer vi om de to bitmaps begrensningsboksene overlapper ved hjelp av rektangel. Skriptene er som nedenfor. Først, variablene.

 private var fiende1: Bitmap, myShip: Bitmap; privat var myShipSp: Sprite; privat var rec_e: rektangel, rec_m: rektangel; privat var intersec: rektangel;
 enemy1 = ny E1 som Bitmap; addChild (enemy1); myShip = new My as Bitmap; myShipSp = ny Sprite; addChild (myShipSp); myShipSp.addChild (MinForsendelse); enemy1.x = stage.stageWidth >> 1; myShipSp.x = scene.stageWidth >> 1; enemy1.y = scene.stageHeight * 0.2; myShipSp.y = scene.stageHeight * 0.8; // tegnebokser rundt sprite tegnet (enemy1.getBounds (scene), dette, 0); tegne (myShipSp.getBounds (scene), dette, 0);

Her ser vi etter eventuelle overlappende områder mellom boksene. Sjekk ut DetectVisible.as i kilden nedlasting for hele skriptet

 privat funksjon oppdatering (e: Event): void // bestemmer grenseboksen til skjæringsområdet rec_e = enemy1.getBounds (scene); rec_m = myShipSp.getBounds (scenen); intersec = rec_e.intersection (rec_m); // redraw grenseboksen til begge sprites this.graphics.clear (); tegne (fiende1.getBounds (scene), dette, 0); tegne (myShipSp.getBounds (scene), dette, 0); // bare tegne avgrensningsboks av skjæringsområdet hvis det er en hvis (! intersec.isEmpty ()) lines.graphics.clear (); tegne (kryss, linjer); t.text = "Interseksjonsområde med rødt rektangel."  else t.text = "Ingen kryssområde." 

Her er en demonstrasjon. Dra det mindre romskipet rundt.

(Ikke vær redd for den røde boksen som blir "etterlatt" når skipet blir trukket ut av den andre grenseboksen.)


Trinn 3: Tegning inn i innsnittsområdet

Så hvis det er et kryssende feltområde, fortsetter vi å sjekke om det er overlappende piksler i området. La oss imidlertid først prøve å tegne bitmap inn i dette skjæringsområdet. Det fulle skriptet er inne DetectVisible2.as

 privat funksjon oppdatering (e: Event): void // bestemmer grenseboksen til skjæringsområdet rec_e = enemy1.getBounds (scene); rec_m = myShipSp.getBounds (scenen); intersec = rec_e.intersection (rec_m); // redraw grenseboksen til begge sprites this.graphics.clear (); tegne (fiende1.getBounds (scene), dette, 0); tegne (myShipSp.getBounds (scene), dette, 0); // bare tegne avgrensningsboks av skjæringsområdet hvis det er en hvis (! intersec.isEmpty ()) lines.graphics.clear (); tegne (kryss, linjer); // for å tegne skjæringsområdet og sjekke overlapping av farget område var eM: Matrix = enemy1.transform.matrix; var myM: Matrix = myShipSp.transform.matrix; bdt_intersec = ny 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 (fiende1, eM); bdt_intersec.draw (myShip, myM); bm_intersec.bitmapData = bdt_intersec; bm_intersec.x = 10 bm_intersec.y = scene.stageHeight * 0,8 - bm_intersec.height; t.text = "Intersection område med rødt rektangel. \ n" else t.text = "Ingen kryssområde." 

Vær oppmerksom på at siden vi tegner området ved hjelp av en matrise, tas hensyn til eventuelle skalering, skeving og andre transformasjoner på begge bitmaps. Her er en demo; sjekk ut boksen nederst i venstre hjørne.


Trinn 4: Sjekk etter Farge i Intersection Area

Så hvordan ser vi etter riktig piksel? Vel, først, gir vi fargen på denne kryssingsboksen en nyanse av svart (Rød = 0, Grønn = 0, Blå = 0). Deretter vil skyggen til mindre romskip skilt i denne mørke boksen som grønn, med blandingsmodus for ADD. På samme måte blir skyggen til det større stasjonære romvesenet malt rødt.

Så nå vil det være områder av rødt og grønt for romskipene, og svart hvis det ikke er noen overlappende område. Men hvis det er piksler fra disse to bitmapene som overlapper, trekkes disse i gul (Rød = 255, Grønn = 255, Blå = 0). Vi bruker metoden Bitmapdata.getColorBoundsRect for å sjekke om dette området er til stede.

Her er koden i Main.as

 // for å tegne skjæringsområdet og sjekke overlapping av farget område var eM: Matrix = enemy1.transform.matrix; var myM: Matrix = myShipSp.transform.matrix; bdt_intersec = ny BitmapData (intersec.width, intersec.height, false, 0) eM.tx - = intersec.x; eM.ty - = intersec.y myM.tx - = intersec.x; myM.ty - = intersec.y // tweak farge bdt_intersec.draw (fiende1, eM, ny 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 = scene.stageHeight * 0,8 - bm_intersec.height; t.text = "Interseksjonsområde med rødt rektangel. \ n" // kontroller eksistensen av riktig farge intersec_color = bdt_intersec.getColorBoundsRect (0xffffff, 0xffff00); hvis (! intersec_color.isEmpty ()) t.appendText ("Og det er interesecting pixels i området.");

Legg merke til at vi undertrykker Røde og Blå komponenter i linje 113 for å maksimere Green for det lille romskipet. På linje 112 gjør vi det samme med det fremmede romskipet med Blue og Green-komponentene.


Sammenligning av tilnærminger

Så etter å ha mottatt kommentarer til ytelsesproblemer angående kollisjonsdeteksjon bestemte jeg meg for å gjøre noen raske og skitne tester på disse tilnærmingene. Jeg opprettet 20 fiendtlige romskip og en spiller romskip og sjekket kollisjonssensor mellom den ene spilleren sender mot de andre 20. Disse sprites er pakket inn i samme nærhet for å tvinge kollisjonsdeteksjon for alle tilnærminger for å ha en komplett løp.

Den første tilnærmingen er den enkleste. BitmapData er fanget ved oppstart og for hver ramme kollisjonsdeteksjon er merket med BitmapData.hitTest (). For den andre tilnærmingen, BitmapData oppdateres hver ramme og kollisjonsdeteksjon er utført basert på disse BitmapData fanget. Den tredje refererer til tilnærmingen som foreslås av denne opplæringen.

Så resultatet for en av testene jeg har gjort er som nedenfor.

 - bitmapdata fixed (1000 iterasjoner) Player versjon: WIN 11,1,102,55 (debug) - metode ... ttl ms ... avg ms bitmapdata fixed 168 0.17 - - bitmapdata oppdateringer (1000 iterasjoner) Player versjon: WIN 11,1,102,55 (debug) - metode ... ttl ms ... avg ms bitmapdata oppdateringer 5003 5.00 - - tilpasset metode (1000 iterasjoner) Player versjon: WIN 11,1,102,55 (debug) - metode ... ttl ms ... avg ms tilpasset metode 4408 4.41 -

De Ytelsestest gir forskjellige resultater når jeg kjører test. Så jeg kjørte det et par ganger og avledet en gjennomsnittlig tid. Konklusjon: Den raskeste metoden er den første, etterfulgt av den tredje og den andre tilnærmingen.

Så lagrer vekk BitmapData for bitmaps når de først blir introdusert i scenen og sjekker etter hitTest hver ramme etter er faktisk effektiv, forutsatt at disse sprites ikke utfører andre transformasjoner enn oversettelse (som rotasjon, skeving og skalering) over tid. Ellers vil du bli tvunget til å vedta enten den andre eller tredje tilnærmingen, og den tredje er mer effektiv som angitt av bildet ovenfor.

Du kan sjekke ut Collisions.as og Results.as for hele skriptet.


Søker etter dyre metoder

Jeg begynte deretter å søke etter de spesifikke kodelinjene som tok opp mer beregningstid. Den andre og tredje tilnærmingen tok mer tid, så jeg avledet flere funksjoner fra dem, som hver bryte på forskjellige punkter. Sjekk ut ett av resultatene nedenfor.

 - standard hitTest (1000 iterasjoner) Spillerversjon: WIN 11,1,102,55 (debug) include bounds - method ... ttl ms ... avg ms default hitTest 189 0.19 - - default hitTest (1000 iterasjoner) Spillerversjon: WIN 11,1,102,55 ( debug) include transform - method ... ttl ms ... avg ms default hitTest 357 0.36 - - default hitTest (1000 iterasjoner) Player version: WIN 11,1,102,55 (debug) inkluderer hittest - metode ... ttl ms ... avg ms default hitTest 4427 4,43 - - tilpasset metode (1000 iterasjoner) Spillerversjon: WIN 11,1,102,55 (debug) inlcude bounds and transform - metode ... ttl ms ... avg ms tilpasset metode 411 0.41 - - tilpasset metode (1000 iterasjoner) Spillerversjon: WIN 11, 1,102,55 (debug) inkluderer tegning og grenser - metode ... ttl ms ... avg ms tilpasset metode 3320 3,32 -

Den første, andre og tredje gang refererer til den andre tilnærmingen ved forskjellige bruddpunkter, og den fjerde og femte gangen refererer til den tredje tilnærmingen. Ser på den tredje og den femte tiden, BitmapData.draw ser ut til å ta mye beregningstid. Og tiden som trengs for å tegne med den andre tilnærmingen, synes å være dyrere i beregningstid, noe som får meg til å tro at størrelsene for BitmapData.draw å drive på, betyr noe. Du kan sjekke ut Collisions2.as og Results2.as for de fulle skriptene.

Én ting jeg finner litt forstyrrende er inkonsekvensen av disse testene - jeg får alltid ikke samme resultat, selv om de nesten følger samme rangering til enhver tid. Så det er godt nok til å gjøre noen enkle sammenligninger mellom funksjoner.

Konklusjon

Vel, takk for din tid med å lese dette lille tipset. Håper det har vært nyttig. Gi igjen kommentarer hvis du ikke er enig med noe i denne opplæringen. Jeg vil gjerne svare på tilbakemelding!