Vector Regioner Skjuler fra et synsfelt

Tidligere har vi utforsket tilnærmingen til å bruke vektorgrupper for å implementere synsfeltet på et tårn. Tropper nærmet seg tårnet på åpent felt og ingen hindringer lå mellom dem. Anta nå at det er en hindring, si en vegg som skjuler synligheten av troppen fra tårnet. hvordan skal vi implmentere det? Denne opplæringen foreslår en tilnærming til å takle dette problemet.


Endelig resultatforhåndsvisning

La oss se på det endelige resultatet vi skal jobbe for. Klikk på tårnet nederst på scenen for å starte simuleringen.


Trinn 1: Grunnleggende konsept

Så her er det vi prøver å oppnå i denne opplæringen. Vær oppmerksom på bildet ovenfor. Turret kan se trooperenheten hvis den er innenfor turretens synsfelt (øverst). Når vi plasserer en vegg mellom tårn og tropper, er tropperens synlighet beskyttet mot tårn.


Trinn 2: Sammendrag

Først av alt, la oss gjøre en liten revisjon. Si at vektoren av tårnets synlinje er P og vektoren fra tårn til tropper er Q. Trooperen er synlig for tårnet hvis:

  • Vinkel mellom P og Q er mindre enn synsvinkelen (i dette tilfellet 30 ° på begge sider)
  • P-størrelsen er mer enn Q

Trinn 3: Tilnærming Oversikt

Over er pseudokoden for tilnærmingen vi skal gjennomføre. Bestemme om trooper er innenfor turretens synsfelt (FOV) er forklart i trinn 2. La oss nå avgjøre om trooperen ligger bak en vegg.

Vi skal bruke vektoroperasjoner for å oppnå dette. Det er sikkert ved å nevne dette at punktproduktet og kryssproduktet raskt kommer til syne. Vi skal gjøre en liten omvei for å revidere disse to vektoroperasjonene for å sikre at alle er i stand til å følge.


Trinn 4: Dot og Cross Produkt mellom vektorer

La oss se på vektoroperasjonen: prikkprodukt og kryssprodukt. Dette er ikke en matematikklasse, og vi har dekket dem nærmere i detalj før, men likevel er det godt å oppdatere vårt minne om arbeidene, så jeg har tatt med bildet ovenfor. Diagrammet viser "B dot A" -operasjon (øverste høyre hjørne) og "B cross A" -operasjon (nederst til høyre).

Viktigere er likningene av disse operasjonene. Ta en titt på bildet nedenfor. | A | og | B | referere til skalar størrelse av hver vektor - pilens lengde. Merk at punktproduktet relaterer til cosinus av vinkelen mellom vektorene, og kryssproduktet vedrører vinkelen mellom vinklene mellom vektorene.


Trinn 5: Sine og Cosine

Drilling videre inn i emnet, Trigonometry kommer til å spille: sinus og cosinus. Jeg er sikker på at disse grafer gjenoppretter kjente minner (eller agonier). Klikk på knappene på Flash presentasjon under for å se de grafene med forskjellige enheter (grader eller radianer).

Merk at disse bølgeformene er kontinuerlige og repeterende. For eksempel kan du kutte og lime sinusbølgen i det negative området for å få noe som under.


Trinn 6: Sammendrag av verdier

Grad Sine of degree Koselig av grad
-180 0 -1
-90 -1 0
0 0 1
90 1 0
180 0 -1

Tabellen over viser cosinus- og sinusverdiene som svarer til bestemte grader. Du vil legge merke til at den positive sinusgrafikken dekker 0 ° til 180 ° og den positive cosinusgrafen dekker -90 ° til 90 °. Vi skal forholde disse verdiene til punktproduktet og krysse produktet senere.


Trinn 7: Geometrisk tolkning av punktprodukt

Så hvordan kan alle disse være nyttige? For å kutte til jakten, er prikkproduktet et mål på hvordan parallell vektorene er mens kryssprodukt er et mål på hvordan ortogonale vektoren er.

La oss håndtere prikkproduktet først. Husk formelen for prikkproduktet, som nevnt i trinn 4. Vi kan bestemme om resultatet er positivt eller negativt bare ved å se på cosinus av vinkelen som er sandwichet mellom de to vektorene. Hvorfor? Fordi størrelsen på en vektor alltid er positiv. Den eneste parameteren som er igjen for å diktere tegn på resultatet, er vinkelenes cosinus.

Igjen, husk at positiv cosinusgraf dekker -90 ° - 90 °, som i trinn 6. Derfor vil punktproduktet av A med noen av vektene L, M, N, O ovenfor gi en positiv verdi fordi vinkelen kile mellom A og noen av disse vektorer er innenfor -90 ° og 90 °! (For å være nøyaktig, er det positive området mer enn -89 ° - 89 ° fordi både -90 ° og 90 ° gir cosinusverdier på 0, som bringer oss til neste punkt.) Dotproduktet mellom A og P (gitt P er vinkelrett på A) vil produsere 0. Resten jeg tror du allerede kan gjette: punktproduktet av A med K, R eller Q vil gi en negativ verdi.

Ved å bruke prikkproduktet kan vi dele området på scenen i to regioner. Dotproduktet av vektoren under med et hvilket som helst punkt som ligger inne i "x" -merket region vil gi en positiv verdi, mens punktproduktet med de i "o" -merket regionen vil gi negative verdier.


Trinn 8: Geometrisk tolkning av kryssprodukt

La oss gå videre til kryssproduktet. Husk at kryssproduktet er relatert til sinus av vinkel sandwichet mellom de to vektorene. Den positive sinusgrafen dekker et område på 0 ° til 180 °; det negative området dekker 0 ° til -180 °. Bildet nedenfor oppsummerer disse punktene.

Så ser du igjen på diagrammet fra Trinn 7, vil kryssproduktet mellom A og K, L eller M produsere positive verdier, mens kryssproduktet mellom A og N, O, P eller Q vil gi negative verdier. Kryssproduktet mellom A og R vil produsere 0, da sinus på 180 ° er 0.

For å klargjøre videre vil kryssproduktet mellom vektor mellom et hvilket som helst punkt som ligger i den "o" -markerte regionen nedenfor være positiv, mens de i "x" -merket region vil være negative.

Et poeng å merke seg er at, i motsetning til punktproduktet, er kryssproduktet sekvensfølsomt. Dette betyr resultater av AxB og BXA vil være annerledes når det gjelder retning. Så som vi skriver vårt program, må vi være presise når vi velger hvilken vektor som skal sammenlignes med.

(Merk: Disse konseptene forklares gjelde for 2D kartesisk plass.)


Trinn 9: Demo-applikasjon

For å styrke din forståelse har jeg lagt et lite program for å la deg leke med. Klikk på den blå ballen øverst i scenen og dra den rundt. Når du flytter, oppdateres tekstboksen verdi avhengig av hvilken operasjon du har valgt (punkt eller kryssprodukt mellom den statiske pilen med den du kontrollerer).

Du kan observere en merkelighet med kryssproduktets inverterte retning. Regionen på toppen er negativ og bunnen er positiv, i motsetning til vår forklaring i forrige trinn. Vel, dette skyldes at y-aksen er invertert i Flash-koordinatplass i forhold til kartesisk koordinatplass det peker ned, mens tradisjonelle matematikere tar det som peker oppover.


Trinn 10: Definere regioner

Nå som du har forstått konseptet av regioner, la oss gjøre litt øvelse. Vi skal dele rommet vårt inn i fire kvadranter: A1, A2, B1, B2.

Jeg har tabulert resultatene for å sjekke for nedenfor. "Vector" her refererer til pilen i bildet ovenfor. "Punkt" refererer til hvilken som helst koordinat i det angitte området. Vektoren deler scenen i fire hovedområder, hvor delene (stiplede linjer) strekker seg til uendelig.

Region Vektor på diagramoverskridende produkt med punkt Vektor på diagrampunktsprodukt med punkt
A1 (+), på grunn av Flash koordinat plass (+)
A2 (+) (-)
B1 (-), på grunn av Flash koordinatrom (+)
B2 (-) (-)

Trinn 11: Regioner delt

Her presenterer Flash-presentasjonen ideene som forklart i trinn 10. Høyreklikk på scenen for å pope sammen menyen og velg regionen du vil se uthevet.


Trinn 12: Implementering

Her er ActionScript-implementeringen av konseptet som er forklart i trinn 10. Du kan se hele koden i kilde nedlasting, som AppLine.as.

 // markere farge i henhold til brukervalg privat funksjon farge (): void // hver ball på scenen er kontrollert mot forholdene for valgt tilfelle for hver (var-element: Ball i sp) var vec1: Vector2D = new Vector2D (item. x - scene.stageWidth * 0,5, item.y - stage.stageHeight * 0,5); hvis (velg == 0) hvis (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; ellers item.col = 0x334455;  annet hvis (velg == 1) hvis (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; ellers item.col = 0x334455;  annet hvis (velg == 2) hvis (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; ellers item.col = 0x334455;  annet hvis (velg == 3) hvis (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; ellers item.col = 0x334455;  annet hvis (velg == 5) hvis (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; 

Trinn 13: Skjermet synlighet

Etter å ha forstått de geometriske tolkninger av punktprodukt og kryssprodukt, skal vi bruke det til vårt scenario. Flash-presentasjonen ovenfor viser variasjoner av det samme scenariet og oppsummerer betingelsene som brukes på en trooper som er skjermet av en vegg, men inne i tårnens FOV. Du kan bla gjennom rammene ved hjelp av piltastene.

Følgende forklaringer er basert på 2D Flash-koordinatplassen. I ramme 1 er en vegg plassert mellom tårnet og trooperen. La A og B være vektorer fra tårnet til halen og hodet på veggens vektor, henholdsvis. La C være vektoren til veggen, og D være vektoren fra halen av veggen til trooperen. Til slutt, la Q være vektoren fra tårnet til trooperen.

Jeg har tabulert de resulterende forholdene nedenfor.

plassering Cross produkt
Troop er foran veggen C x D> 0
Troop er bak veggen C x D

Dette er ikke den eneste betingelsen som gjelder, fordi vi også trenger å begrense trooperen til de stiplede linjene på begge sider. Sjekk ut rammer 2-4 for å se det neste settet av forhold.

plassering Cross produkt
Troop er innenfor sidene av veggen. Q x A 0
Troop er til venstre for veggen Q x A> 0, Q x B> 0
Troop er til høyre for veggen Q x A

Jeg tror at mine medlesere kan nå velge de riktige betingelsene for å avgjøre om trooper er skjult for visning eller ikke. Husk at dette settet av forhold er vurdert etter at vi fant troppen å være innenfor turretens FOV (se trinn 3).


Trinn 14: Actionscript Implementering

Her er ActionScript-implementeringen av konseptene som er forklart i trinn 13. Bildet over viser den første vektoren av veggen, C. Klikk og dra den røde knappen under og flytt den rundt for å se området skjermet. Du kan se full kildekoden i HiddenSector.as.

Ok, jeg håper du har eksperimentert med den røde ballen, og hvis du er observant nok, har du kanskje lagt merke til en feil. Merk at det ikke er noe område skjermet som den røde knappen beveger seg til venstre for den andre enden av veggen, og reverserer derfor veggen vektoren for å peke til venstre i stedet for høyre. Løsningen er i neste trinn.

Men før, la oss se på en viktig ActionScript-kode her inne HiddenSector.as:

 privat funksjon høydepunkt (): void var lineOfSight: Vector2D = ny Vector2D (0, -50) var sektor: Nummer = Math2.radianOf (30); for hver (var element: Ball i sp) var turret_sp: Vector2D = ny Vector2D (item.x - turret.x, item.y - turret.y); // Q hvis (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 (); 

Trinn 15: Retning av veggen

For å løse dette problemet må vi vite om veggvektoren peker til venstre eller høyre. La oss si at vi har en referanseverktøy, R, som alltid peker til høyre.

Retning av vektor Prikkprodukt
Vegg peker til høyre (samme side som R) w. R> 0
Vegg peker til venstre (motsatt side av R) w. R

Selvfølgelig er det andre måter rundt dette problemet, men jeg skjønner at det er en mulighet til å utnytte konsepter uttrykt i denne opplæringen, så der går du.


Trinn 16: Actionscript Tweaks

Nedenfor er en Flash-presentasjon som implementerer korrigeringen som er forklart i trinn 15. Etter at du har spilt med den, rull ned for å sjekke ActionScript-tweaks.

Endringene fra forrige implementering er uthevet. Også omstillingssettene er omdefinert i henhold til veggretningen:

 privat funksjon høyde (): void var lineOfSight: Vector2D = ny Vector2D (0, -50); var sektor: Nummer = Math2.radianOf (30); var pointToRight: Vector2D = ny Vector2D (10, 0); // lagt til i andre versjon for hver (var element: Ball i sp) var turret_sp: Vector2D = ny Vector2D (item.x - turret.x, item.y - turret.y); // Q hvis (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 ellers sides = wall.vectorProdukt (wall_sp)> 0 // C x D && turret_sp.vectorProdukt (turret_wall1)> 0 // Q x A && turret_sp.vectorProdukt (turret_wall2) < 0 // Q x B  if (sides)  item.col = 0xcccccc  else  item.col = 0;  item.draw();   

Sjekk ut hele kilden i HiddenSector2.as.


Trinn 17: Sett opp veggen

Nå skal vi lappe opp arbeidet vårt Scene1.as fra den forrige opplæringen. Først skal vi sette opp veggen vår.

Vi initierer variablene,

 offentlig klasse Scene1_2 utvider Sprite private var river: Sprite; private var wall_origin: Vector2D, wall: Vector2D; // lagt til i andre opplæringen private var-tropper: Vector.; privat var troopVelo: Vector.;

... deretter tegne veggen for første gang,

 offentlig funksjon Scene1_2 () makeTroops (); makeRiver (); makeWall (); // lagt til i 2. veiledning makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); funksjonstart (): void stage.addEventListener (Event.ENTER_FRAME, flytt); 
 privat funksjon makeWall (): void wall_origin = ny Vector2D (200, 260); vegg = ny 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); 

... og skriv på hver ramme, fordi graphics.clear () ring er et sted i behaviourTurret ():

 // lagt til i 2. opplæring privat funksjon flytte (e: Event): void behaviourTroops (); behaviourTurret (); redrawWall (); 
 // lagt til i andre opplæring privat funksjon 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); 

Trinn 18: Samspill med veggen

Tropper vil samhandle med veggen også. Når de kolliderer med veggen, vil de glide langs veggen. Jeg vil ikke prøve å gå inn på detaljer om dette, siden det har blitt dokumentert omfattende i Kollisionsreaksjon mellom en sirkel og et linjesegment. Jeg oppfordrer leserne til å sjekke det ut for nærmere forklaring.

Følgende utdrag lever i funksjonen behaviourTroops ().

 // Versjon 2 // hvis du går gjennom elven, sakker ned // hvis du kolliderer med veggen, skyv gjennom // ellers normal hastighet var kollidere medRiver: Boolean = river.hitTestObject (tropper [i]) var wall_norm: Vector2D = wall.rotate Math2.radianOf (-90)); var wall12Troop: Vector2D = ny Vector2D (tropper [i] .x - wall_origin.x, tropper [i] .y - wall_origin.y); var collideWithWall: Boolean = tropper [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; hvis (collideWithRiver) tropper [i] .y + = troopVelo [i] .y * 0.3; ellers hvis (collideWithWall) // reposisjon troop var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (tropper [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (vegg)); var reposisjon: Vector2D = projOnNorm.add (projOnWall); tropper [i] .x = wall_origin.x + reposition.x; tropper [i] .y = wall_origin.y + reposition.y; // skyve gjennom veggen var justering: Nummer = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (justering); slideVelo = slideVelo.add (troopVelo [i]) tropper [i] .x + = slideVelo.x; tropper [i] .y + = slideVelo.y;  andre tropper [i] .y + = troopVelo [i] .y

Trinn 19: Kontrollerer Troopers "skjult-ness"

Til slutt kommer vi til kjøttet i denne opplæringen: setter opp tilstanden og kontrollerer om soldater er bak veggen og derfor skjermet fra tårnens synlighet. Jeg har fremhevet de viktige oppdateringskoder:

 // Sjekk om fienden er i sikte // 1. Innenfor sektor // 2. Innenfor rekkevidde // 3. Tettere enn nåværende nærmeste fiende 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 bakWall: 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; 

Sjekk ut hele koden i Scene1_2.as.


Trinn 20: Legg applikasjonen

Til slutt kan vi lene seg tilbake og sjekke ut oppdateringen. Trykk Ctrl + Enter for å se resultatene av arbeidet ditt. Jeg har tatt med en kopi av den fungerende Flash-presentasjonen nedenfor. Klikk på tårnet nederst på scenen for å starte simuleringen.