Tidigare undersökte vi sättet att använda vektorregioner för att implementera synfältet på ett torn. Trupper närmade sig tornet på öppna fält och inga hinder låg mellan dem. Antag nu att det finns en hinder, säg en vägg, som döljer synligheten av trupperna från tornet. hur ska vi implmentera det? Denna handledning föreslår ett tillvägagångssätt för att hantera denna fråga.
Låt oss ta en titt på det slutliga resultatet vi kommer att arbeta för. Klicka på tornet längst ner på scenen för att starta simuleringen.
Så här är det vi försöker uppnå i denna handledning. Observera bilden ovan. Tornet kan se trooperen om det befinner sig inom tornets synfält (topp). När vi placerar en mur mellan tornet och trooperen, är tropparens synlighet skyddad från tornet.
Först av allt, låt oss göra en liten revision. Säg att tornets vektor sikt är P och vektorn från tornet till trooper är Q. Trooper är synlig för tornet om:
Ovanstående är pseudokoden för det tillvägagångssätt vi ska åta. Att fastställa huruvida trooperen befinner sig inom tornets synfält (FOV) förklaras i steg 2. Nu får vi bestämma om trooperna ligger bakom en vägg.
Vi ska använda vektoroperationer för att uppnå detta. Jag är säker på att genom att nämna detta kommer punktprodukten och korsprodukten snabbt att komma ihåg. Vi ska göra en liten omväg för att revidera dessa två vektoroperationer bara för att alla ska kunna följa.
Låt oss se över vektoroperationen: punktprodukt och korsprodukt. Det här är inte en matteklass, och vi har behandlat dem mer i detalj innan, men ändå är det bra att uppdatera vårt minne om arbetet så jag har tagit med bilden ovan. Diagrammet visar funktionen "B dot A" (höger högra hörnet) och "B cross A" (nederst till höger).
Viktigare är ekvationerna för dessa operationer. Ta en titt på bilden nedan. | A |
och | B |
referera till skalär storlek av varje vektor - pilens längd. Observera att punktprodukten hänför sig till cosinus av vinkeln mellan vektorerna och korsprodukten hänför sig till sinus av vinkeln mellan vektorerna.
Borrning vidare in i ämnet kommer trigonometri att spela: sinus och cosinus. Jag är säker på att dessa grafer återskapar förtjusta minnen (eller agonier). Klicka på knapparna på Flash presentation nedan för att se de graferna med olika enheter (grader eller radianer).
Observera att dessa vågformer är kontinuerliga och repetitiva. Du kan till exempel klippa och klistra sinusvågen i det negativa området för att få något som nedan.
Grad | Sine of degree | Cosine av grad |
-180 | 0 | -1 |
-90 | -1 | 0 |
0 | 0 | 1 |
90 | 1 | 0 |
180 | 0 | -1 |
Tabellen ovan visar cosinus- och sinusvärdena som motsvarar specifika grader. Du kommer att märka att den positiva sinusgrafen täcker 0 ° till 180 ° och det positiva cosinusgraven täcker -90 ° till 90 °. Vi ska relatera dessa värden till punktprodukten och korsprodukten senare.
Så hur kan alla dessa vara användbara? För att skära till jakten är prickprodukten ett mått på hur parallell vektorerna är medan korsprodukt är ett mått på hur ortogonal vektorerna är.
Låt oss hantera prickprodukten först. Återkalla formeln för punktprodukten, som nämns i steg 4. Vi kan bestämma huruvida resultatet är positivt eller negativt bara genom att titta på cosinus av vinkeln som är sandwichad mellan de två vektorerna. Varför? Eftersom storleken på en vektor alltid är positiv. Den enda parametern som lämnas för att diktera tecknet på resultatet är vinkelns cosinus.
Återigen, återkalla den positiva cosinusgraven täcker -90 ° - 90 °, som i steg 6. Därför kommer punktprodukten av A med vilken som helst av vecoterna L, M, N, O ovan att ge ett positivt värde, eftersom vinkeln klev mellan A och någon av dessa vektorer är inom -90 ° och 90 °! (För att vara exakt är det positiva intervallet mer som -89 ° - 89 ° eftersom både -90 ° och 90 ° ger cosinusvärden på 0, vilket leder oss till nästa punkt.) Punktprodukten mellan A och P (given P är vinkelrätt mot A) kommer att producera 0. Resten tror jag att du redan kan gissa: prickproduktet av A med K, R eller Q kommer att ge ett negativt värde.
Genom att använda prickprodukten kan vi dela området på vårt stadium i två regioner. Dotprodukten av vektorn nedan med vilken punkt som ligger inuti den "x" -markerade regionen kommer att ge ett positivt värde, medan punktprodukten med de i "o" -markerade regionen kommer att producera negativa värden.
Låt oss gå vidare till korsprodukten. Kom ihåg att korsprodukten är relaterad till sinus av vinkel sandwichad mellan de två vektorerna. Den positiva sinusgrafen täcker ett intervall från 0 ° till 180 °; det negativa området täcker 0 ° till -180 °. Bilden nedan sammanfattar dessa punkter.
Så ser vi igen på diagrammet från steg 7, kommer korsprodukten mellan A och K, L eller M att producera positiva värden, medan korsprodukten mellan A och N, O, P eller Q kommer att ge negativa värden. Korsprodukten mellan A och R kommer att producera 0, eftersom sinus av 180 ° är 0.
För att ytterligare klargöra är kryssprodukten av vektor mellan vilken punkt som ligger i den "o" -markerade regionen nedan positiv, medan de i "x" -markerade regionen kommer att vara negativa.
En punkt att notera är att, till skillnad från punktprodukten, är korsprodukten sekvenskänslig. Detta betyder resultat av AxB
och BXA
kommer att vara olika i riktning. Så när vi skriver vårt program måste vi vara exakta när vi väljer vilken vektor som ska jämföras mot.
(Obs! Dessa begrepp förklaras gälla för 2D kartesiskt utrymme.)
För att förstärka din förståelse har jag lagt ett litet program för att låta dig leka med. Klicka på den blå bollen på toppen av scenen och dra den runt. När du flyttar uppdateras textrutans värde beroende på vilken operation du har valt (punkt eller korsprodukt mellan den statiska pilen med den du kontrollerar).
Du kan observera en odditet med korsproduktens inverterade riktning. Regionen på toppen är negativ och botten är positiv, i motsats till vår förklaring i föregående steg. Jo, det beror på att y-axeln är inverterad i Flash-koordinatutrymmet jämfört med kartesiskt koordinatutrymme; det pekar ner, medan traditionellt matematiker tar det som att peka uppåt.
Nu när du har förstått begreppet regioner, låt oss göra lite övning. Vi ska dela ut vårt utrymme i fyra kvadranter: A1, A2, B2, B2.
Jag har tabulerat resultaten för att söka efter nedan. "Vector" hänvisar här till pilen i bilden ovan. "Punkt" avser någon koordinat i den angivna regionen. Vektorn delar upp scenen i fyra huvudområden, där delarna (streckade linjer) sträcker sig till oändligheten.
Område | Vektor på diagramskorsprodukt med punkt | Vektor på diagram punktprodukt med punkt |
A1 | (+), på grund av Flash-koordinatutrymme | (+) |
A2 | (+) | (-) |
B1 | (-), på grund av Flash-koordinatutrymme | (+) |
B2 | (-) | (-) |
Här presenterar Flash-presentationen idéerna som förklaras i steg 10. Högerklicka på scenen för att öppna snabbmenyn och välj den region som du vill se markerad.
Här är ActionScript-implementeringen av konceptet som beskrivs i steg 10. Känn dig fri att visa hela koden i källnedladdning, som AppLine.as
.
// markera färg enligt användarvalet privat funktion färg (): void // varje boll på scenen är markerad mot villkor för valt fall för varje (varoboll: Boll i sp) var vec1: Vector2D = ny Vector2D (item. x - stage.stageWidth * 0,5, item.y - stage.stageHeight * 0,5); om (välj == 0) om (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; annat item.col = 0x334455; annars om (välj == 1) om (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; annat item.col = 0x334455; annars om (välj == 2) om (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; annat item.col = 0x334455; annars om (välj == 3) om (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; annat item.col = 0x334455; annars om (välj == 5) om (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;
Efter att ha förstått de geometriska tolkningarna av punktprodukt och korsprodukt ska vi tillämpa det på vårt scenario. Flash-presentationen ovan visar variationer av samma scenario och sammanfattar villkoren som tillämpas på en trooper som är skyddad av en vägg men inuti turret FOV. Du kan bläddra genom ramarna med pilknapparna.
Följande förklaringar baseras på 2D Flash-koordinatutrymmet. I ram 1 placeras en vägg mellan tornet och truppen. Låt A och B vara vektorerna från tornet till svansen respektive huvudet på väggens vektor. Låt C vara vektorn på väggen och D vara vektorn från väggens svans till trooperen. Slutligen, låt Q vara vektorn från tornet till trooperen.
Jag har tabulerat de resulterande villkoren nedan.
Plats | Cross Product |
Troop är framför väggen | C x D> 0 |
Troop är bakom väggen | C x D |
Detta är inte det enda villkoret som är tillämpligt, eftersom vi också måste begränsa trooperen inom de streckade linjerna på båda sidor. Kolla in bildrutorna 2-4 för att se nästa uppsättning villkor.
Plats | Cross Product |
Troop ligger inom väggens sidor. | Q x A 0 |
Troop är till vänster om väggen | Q x A> O, Q x B> 0 |
Troop är till höger om väggen | Q x A |
Jag tror att mina medläsare nu kan välja lämpliga förhållanden för att avgöra huruvida trooper är gömd eller inte. Tänk på att denna uppsättning villkor utvärderas efter att vi hittat trupper inom turret FOV (se steg 3).
Här är ActionScript-implementeringen av de begrepp som förklaras i steg 13. Bilden ovan visar väggens ursprungliga vektor, C. Klicka och dra den röda knappen nedan och flytta den runt för att se området skärmt. Du kan se hela källkoden i HiddenSector.as
.
Okej, jag hoppas att du har experimenterat med den röda bollen, och om du är noggrann så kanske du har märkt ett fel. Observera att det inte finns något område som är skyddat eftersom den röda knappen flyttas till vänster om den andra änden av väggen och därmed inverterar väggvektorn för att peka åt vänster istället för höger. Lösningen ligger i nästa steg.
Men innan vi får se en viktig ActionScript-kod här i HiddenSector.as
:
privat funktion höjdpunkt (): void var lineOfSight: Vector2D = ny Vector2D (0, -50) var sektor: Number = Math2.radianOf (30); för varje (varobjekt: Boll i sp) var turret_sp: Vector2D = ny Vector2D (item.x - turret.x, item.y - turret.y); // Q om (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 ();
För att lösa problemet måste vi veta om väggvektorn pekar åt vänster eller höger. Låt oss säga att vi har en referensvektor, R, som alltid pekar åt höger.
Riktning av vektor | Punkt produkt |
Vägg pekar mot höger (samma sida som R) | w. R> 0 |
Vägg pekar åt vänster (motsatt sida av R) | w. R |
Naturligtvis finns det andra sätt kring detta problem men jag tycker att det är en möjlighet att utnyttja koncept som uttrycks i denna handledning, så där går du.
Nedan följer en Flash-presentation som implementerar den korrigering som förklaras i steg 15. När du har spelat med den, bläddrar du ner för att kontrollera ActionScript-tweaks.
Ändringarna från föregående genomförande är markerade. Också omständigheterna är omdefinierade enligt väggriktningen:
privat funktion höjdpunkt (): void var lineOfSight: Vector2D = ny Vector2D (0, -50); var sektor: Nummer = Math2.radianOf (30); var pointToRight: Vector2D = ny Vector2D (10, 0); // läggs i andra versionen för varje (varobjekt: Boll i sp) var turret_sp: Vector2D = ny Vector2D (item.x - turret.x, item.y - turret.y); // Q om (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.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();
Kolla in hela källan i HiddenSector2.as
.
Nu ska vi lappa vårt arbete på Scene1.as
från föregående handledning. Först ska vi sätta upp vår vägg.
Vi initierar variablerna,
public class Scene1_2 utökar Sprite private var river: Sprite; privat var wall_origin: Vector2D, wall: Vector2D; // läggs till i andra handledning privata varrupper: Vector.; privat var troopVelo: Vector. ;
... dra sedan väggen för första gången,
allmän funktion Scene1_2 () makeTroops (); makeRiver (); makeWall (); // läggs till i 2: a handledning makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); funktionstart (): void stage.addEventListener (Event.ENTER_FRAME, flytta);
privat funktion makeWall (): void wall_origin = ny Vector2D (200, 260); vägg = ny vektor2D (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);
... och omdragen på varje ram, eftersom graphics.clear ()
samtal är någonstans i behaviourTurret ()
:
// läggs till i 2: a handledning privat funktion flyttning (e: Event): void behaviourTroops (); behaviourTurret (); redrawWall ();
// läggs till i andra handledning privat funktion 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);
Trupperna kommer också att interagera med väggen. När de kolliderar med väggen kommer de att glida längs väggen. Jag kommer inte att försöka gå in i detaljer om detta eftersom det har dokumenterats mycket i kollisionsreaktion mellan en cirkel och ett linjesegment. Jag uppmanar läsarna att kolla upp det för ytterligare förklaring.
Följande snipp lever i funktionen behaviourTroops ()
.
// Version 2 // om vada genom floden, sakta ner // om kolliderar med väggen, glida genom // annars normal hastighet var kollidera medRiver: Boolean = river.hitTestObject (trupper [i]) var wall_norm: Vector2D = wall.rotate Math2.radianOf (-90)); var wall12Troop: Vector2D = ny Vector2D (trupper [i] .x - wall_origin.x, trupper [i] .y - wall_origin.y); var collideWithWall: Boolean = trupper [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; om (collideWithRiver) trupper [i] .y + = troopVelo [i] .y * 0.3; annars om (collideWithWall) // reposition troop var projOnNorm: Vector2D = wall_norm.normalise (); ProjOnNorm.scale (trupper [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (vägg)); var reposition: Vector2D = projOnNorm.add (projOnWall); trupper [i] .x = wall_origin.x + reposition.x; trupper [i] .y = wall_origin.y + reposition.y; // glida genom väggen var justering: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (justering); slideVelo = slideVelo.add (troopVelo [i]) trupper [i] .x + = slideVelo.x; trupper [i] .y + = slideVelo.y; andra trupper [jag] .y + = troopVelo [i] .y
Slutligen kommer vi till köttet i denna handledning: Ställa in villkoret och kontrollera om soldaterna ligger bakom väggen och därför skyddas från tornets synlighet. Jag har markerat de viktiga patchkoderna:
// kontrollera om fienden är i sikte // 1. Inom sektorns syn // 2. Inom synvinkel // 3. Närmare än nuvarande närmaste 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 bakomWall: 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;
Kolla in hela koden i Scene1_2.as
.
Slutligen kan vi luta oss tillbaka och kolla in korrigeringsfilen. Tryck Ctrl + Enter för att se resultaten av ditt arbete. Jag har tagit med en kopia av den fungerande Flash-presentationen nedan. Klicka på tornet längst ner på scenen för att starta simuleringen.