Eerder verkenden we de aanpak van het gebruik van vectorgebieden om het gezichtsveld van een torentje te implementeren. Troepen naderden het torentje op open veld en er was geen belemmering tussen hen. Stel nu dat er een belemmering is, zeg een muur, die de zichtbaarheid van de troep van het torentje verhult; hoe moeten we dat toepassen? Deze tutorial suggereert een aanpak om dit probleem aan te pakken.
Laten we eens kijken naar het uiteindelijke resultaat waar we naartoe zullen werken. Klik op het torentje onderaan het podium om de simulatie te starten.
Dus hier is wat we proberen te bereiken in deze tutorial. Observeer de bovenstaande afbeelding. Het torentje kan de trooper-eenheid zien als deze zich binnen het gezichtsveld van de toren bevindt (boven). Zodra we een muur tussen het torentje en de cavalerist plaatsen, wordt de zichtbaarheid van de cavalerist beschermd tegen torentjes.
Laten we eerst een kleine herziening doen. Stel dat de vector van de vizierlijn van het torentje P is en de vector van torentje naar trooper is Q. De trooper is zichtbaar voor het torentje als:
Hierboven staat de pseudo-code voor de aanpak die we zullen uitvoeren. Bepalen of de trooper zich in het gezichtsveld van het torentje bevindt (FOV) wordt uitgelegd in stap 2. Laten we nu bepalen of de trooper zich achter een muur bevindt.
We zullen vectorbewerkingen gebruiken om dit te bereiken. Ik ben er zeker van dat bij het noemen van dit snel het puntproduct en het kruisproduct in me opkomen. We zullen een kleine omweg maken om deze twee vectorbewerkingen te herzien om er zeker van te zijn dat iedereen in staat is om te volgen.
Laten we de vectorbewerkingen opnieuw bekijken: het puntproduct en het crossproduct. Dit is geen wiskundeles, en we hebben deze eerder in meer detail behandeld, maar toch is het goed om ons geheugen te verfrissen over de werking, dus ik heb de bovenstaande afbeelding toegevoegd. Het diagram toont de bewerkingen "B punt A" (rechter bovenhoek) en "B kruis A" (rechtsonder).
Belangrijker zijn de vergelijkingen van deze operaties. Bekijk de afbeelding hieronder. | A |
en | B |
verwijs naar de scalaire grootte van elke vector - de lengte van de pijl. Merk op dat het puntproduct betrekking heeft op de cosinus van de hoek tussen de vectoren en dat het kruisproduct betrekking heeft op de sinus van de hoek tussen de vectoren.
Verderop in het onderwerp komt Trigonometrie om te spelen: de sinus en de cosinus. Ik ben er zeker van dat deze grafieken mooie herinneringen (of kwellingen) nieuw leven inblazen. Klik op de onderstaande knoppen in Flash-presentatie om die grafieken met verschillende eenheden (graden of radialen) te bekijken.
Merk op dat deze golfvormen continu en repetitief zijn. U kunt bijvoorbeeld de sinusgolf in het negatieve bereik knippen en plakken om zoiets als hieronder te krijgen.
Mate | Sine of degree | Cosinus van graad |
-180 | 0 | -1 |
-90 | -1 | 0 |
0 | 0 | 1 |
90 | 1 | 0 |
180 | 0 | -1 |
De bovenstaande tabel toont de cosinus- en sinuswaarden die overeenkomen met specifieke graden. U ziet dat de positieve sinusgrafiek een bereik van 0 ° tot 180 ° beslaat en een positieve cosinusgrafiek tussen -90 ° en 90 °. We zullen deze waarden later relateren aan het puntproduct en het kruisproduct.
Dus hoe kunnen al deze nuttig zijn? Om de achtervolging in te schakelen, is het puntproduct een maat voor hoe parallel de vectoren zijn terwijl het kruisproduct een maat is voor hoe orthogonale de vectoren zijn.
Laten we eerst het puntproduct behandelen. Herinner de formule voor het puntproduct, zoals vermeld in stap 4. We kunnen bepalen of het resultaat positief of negatief is, alleen door te kijken naar de cosinus van de hoek ingeklemd tussen de twee vectoren. Waarom? Omdat de grootte van een vector altijd positief is. De enige parameter die over is om het teken van het resultaat te dicteren, is de cosinus van de hoek.
Nogmaals, herinner u eraan dat de positieve cosinusgrafiek -90 ° - 90 ° dekt, zoals in stap 6. Daarom zal het puntproduct van A met een van de bovengenoemde L, M, N, O hierboven een positieve waarde produceren, omdat de hoek ingeklemd is tussen A en een van deze vectoren ligt binnen -90 ° en 90 °! (Om precies te zijn, het positieve bereik is meer als -89 ° - 89 ° omdat zowel -90 ° als 90 ° cosinuswaarden van 0 produceren, wat ons naar het volgende punt brengt.) Het puntproduct tussen A en P (gegeven P staat loodrecht op A) levert 0 op. De rest denk ik dat je al kunt raden: het puntproduct van A met K, R of Q zal een negatieve waarde produceren.
Door het puntproduct te gebruiken, kunnen we het gebied op ons podium opdelen in twee regio's. Het puntproduct van de onderstaande vector met een punt dat binnen het "x" -gemarkeerde gebied ligt, zal een positieve waarde produceren, terwijl het puntproduct met die in het "o" -gemarkeerde gebied negatieve waarden zal produceren.
Laten we verder gaan met het cross-product. Vergeet niet dat het kruisproduct betrekking heeft op de sinus van hoek ingeklemd tussen de twee vectoren. De positieve sinusgrafiek beslaat een bereik van 0 ° tot 180 °; het negatieve bereik beslaat 0 ° tot -180 °. De onderstaande afbeelding vat deze punten samen.
Dus, opnieuw kijkend naar het diagram uit stap 7, zal het crossproduct tussen A en K, L of M positieve waarden produceren, terwijl het crossproduct tussen A en N, O, P of Q negatieve waarden zal produceren. Het kruisproduct tussen A en R zal 0 produceren, omdat sinus van 180 ° 0 is.
Om verder te verduidelijken, zal het kruisproduct van vector tussen elk punt dat in het "o" -gemarkeerde gebied hieronder ligt positief zijn, terwijl die in "x" -gemarkeerd gebied negatief zullen zijn.
Een punt om op te letten is dat het crossproduct, in tegenstelling tot het puntproduct, sequentiegevoelig is. Dit betekent resultaten van AxB
en BXA
zal anders zijn in termen van richting. Dus terwijl we ons programma schrijven, moeten we precies zijn wanneer we kiezen met welke vector we vergeleken moeten worden.
(Opmerking: deze begrippen zijn van toepassing op de 2D-cartesiaanse ruimte.)
Om je begrip te versterken, heb ik hier een kleine applicatie geplaatst waarmee je kunt spelen. Klik op de blauwe bal bovenaan het podium en sleep deze rond. Terwijl u verplaatst, wordt de tekstvakwaarde bijgewerkt afhankelijk van de bewerking die u hebt gekozen (punt of kruisproduct tussen de statische pijl met degene die u bestuurt).
Je kunt een eigenaardigheid observeren met de omgekeerde richting van het product. Het gebied bovenaan is negatief en de bodem is positief, in tegenstelling tot onze uitleg in de vorige stap. Welnu, dit komt doordat de y-as wordt omgekeerd in de Flash-coördinaatruimte in vergelijking met de Cartesiaanse coördinaatruimte; het wijst naar beneden, terwijl traditioneel wiskundigen het als naar boven wijzend beschouwen.
Nu je het concept van regio's hebt begrepen, laten we een beetje oefenen. We zullen onze ruimte verdelen in vier kwadranten: A1, A2, B1, B2.
Ik heb de resultaten getabelleerd om hieronder te controleren. "Vector" verwijst hier naar de pijl in de bovenstaande afbeelding. "Punt" verwijst naar elke coördinaat in de opgegeven regio. De vector verdeelt het toneel in vier hoofdgebieden, waar de verdelers (stippellijnen) zich uitstrekken tot in het oneindige.
Regio | Vector op diagram dwarsproduct met punt | Vector op het product van de diagrampunt met punt |
A1 | (+) vanwege de Flash-coördinaatruimte | (+) |
A2 | (+) | (-) |
B1 | (-), vanwege de Flash-coördinaatruimte | (+) |
B2 | (-) | (-) |
Dit is de Flash-presentatie waarin de ideeën worden getoond zoals uitgelegd in stap 10. Klik met de rechtermuisknop op het podium om het contextmenu te openen en selecteer de regio die u wilt zien gemarkeerd.
Hier is de ActionScript-implementatie van het concept uitgelegd in stap 10. Voel je vrij om het hele stuk code in de brondownload te bekijken, als AppLine.as
.
// kleur markeren op basis van gebruikersselectie persoonlijke functie kleur (): ongeldig // elke bal op scène wordt gecontroleerd aan de hand van de voorwaarden voor de geselecteerde case voor elk (var-item: Ball in sp) var vec1: Vector2D = new Vector2D (item. x - stage.stageWidth * 0.5, item.y - stage.stageHeight * 0.5); if (select == 0) if (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (select == 1) if (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (select == 2) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (select == 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 (select == 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;
Nadat we de geometrische interpretaties van het puntproduct en het kruisproduct hebben begrepen, zullen we dit toepassen op ons scenario. De bovenstaande Flash-presentatie toont variaties van hetzelfde scenario en vat de voorwaarden samen die worden toegepast op een trooper die wordt afgeschermd door een muur die zich nog in de FOV van het torentje bevindt. U kunt met de pijltjestoetsen door de kaders bladeren.
De volgende verklaringen zijn gebaseerd op de 2D Flash-coördinaatruimte. In frame 1 wordt een muur tussen het torentje en de cavalerist geplaatst. Laat A en B de vectoren zijn van het torentje naar de staart en respectievelijk de kop van de muurvector. Laat C de vector van de muur zijn en D de vector van de staart van de muur naar de trooper. Laat tenslotte de vector zijn van het torentje naar de trooper.
Ik heb de resulterende voorwaarden hieronder weergegeven.
Plaats | Kruisproduct |
De troep staat voor de muur | C x D> 0 |
De troep is achter muur | C x D |
Dit is niet de enige voorwaarde die van toepassing is, omdat we ook de trooper moeten beperken tot de gestippelde lijnen aan beide zijden. Bekijk frames 2-4 om de volgende reeks voorwaarden te bekijken.
Plaats | Kruisproduct |
Troop bevindt zich aan de zijkanten van de muur. | Q x A 0 |
De troep staat links van de muur | Q x A> 0, Q x B> 0 |
Troop staat rechts van de muur | Q x A |
Ik denk dat mijn collega-lezers nu de juiste voorwaarden kunnen kiezen om te bepalen of de trooper aan het zicht onttrokken is of niet. Houd er rekening mee dat deze set voorwaarden wordt geëvalueerd nadat we hebben vastgesteld dat de troep zich binnen de FOV van de toren bevindt (zie stap 3).
Dit is de ActionScript-implementatie van de concepten die in stap 13 zijn uitgelegd. De afbeelding hierboven toont de beginvector van de muur, C. Klik en sleep de rode knop hieronder en verplaats deze om het afgeschermde gebied te bekijken. U kunt de volledige broncode bekijken in HiddenSector.as
.
Oké, ik hoop dat je hebt geëxperimenteerd met de rode bal, en als je oplettend genoeg bent, heb je misschien een fout opgemerkt. Let op: er is geen gebied afgeschermd als de rode knop naar links van het andere uiteinde van de muur beweegt, waardoor de muurvector wordt omgekeerd om naar links te wijzen in plaats van naar rechts. De oplossing is in de volgende stap.
Laten we hier echter eerst een belangrijk ActionScript-fragment bekijken HiddenSector.as
:
persoonlijke functie highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50) var sector: Number = Math2.radianOf (30); voor elk (var item: 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 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 ();
Om dit probleem op te lossen, moeten we weten of de muurvector naar links of rechts wijst. Laten we zeggen dat we een referentievector R hebben die altijd naar rechts wijst.
Richting van vector | Punt product |
De muur wijst naar rechts (dezelfde kant als R) | w. R> 0 |
De muur wijst naar links (tegenovergestelde kant van R) | w. R |
Natuurlijk zijn er andere manieren om dit probleem aan te pakken, maar ik denk dat het een gelegenheid is om concepten te gebruiken die in deze tutorial worden uitgedrukt, dus daar ga je.
Hieronder staat een Flash-presentatie die de correctie implementeert die in stap 15 is uitgelegd. Nadat je ermee hebt gespeeld, scrol je omlaag om de ActionScript-aanpassingen te controleren.
De wijzigingen ten opzichte van de vorige implementatie zijn gemarkeerd. Ook worden de conditiesets opnieuw gedefinieerd op basis van de muurrichting:
persoonlijke functie highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50); var sector: Number = Math2.radianOf (30); var pointToRight: Vector2D = new Vector2D (10, 0); // toegevoegd in de tweede versie voor elk (var-item: 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();
Bekijk de volledige bron in HiddenSector2.as
.
Nu zullen we ons werk patchen Scene1.as
van de vorige tutorial. Eerst zullen we onze muur opzetten.
We initiëren de variabelen,
public class Scene1_2 breidt Sprite uit private var river: Sprite; private var wall_origin: Vector2D, wall: Vector2D; // toegevoegd in tweede tutorial private var troops: Vector.; private var troopVelo: Vector. ;
... trek dan de muur voor de eerste keer,
public function Scene1_2 () makeTroops (); makeRiver (); makeWall (); // toegevoegd in 2e tutorial makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); function start (): void stage.addEventListener (Event.ENTER_FRAME, move);
private function 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);
... en opnieuw tekenen op elk frame, omdat de graphics.clear ()
gesprek is ergens binnen behaviourTurret ()
:
// toegevoegd in 2e tutorial privé functie move (e: Event): void behaviourTroops (); behaviourTurret (); redrawWall ();
// toegevoegd in tweede tutorial private function 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);
Troepen zullen ook met de muur interageren. Als ze tegen de muur botsen, glijden ze langs de muur. Ik zal hier niet op in detail treden, omdat dit uitgebreid is beschreven in de botsingsreactie tussen een cirkel en een lijnsegment. Ik moedig lezers aan om dat te controleren voor verdere uitleg.
Het volgende fragment leeft in de functie behaviourTroops ()
.
// Versie 2 // als je door de rivier waadt, langzamer // als je botst met de muur, schuif erdoor // anders normale snelheid var collideWithRiver: Boolean = river.hitTestObject (troops [i]) var wall_norm: Vector2D = wall.rotate ( Math2.radianOf (-90)); var wall12Troop: Vector2D = new Vector2D (troops [i] .x - wall_origin.x, troops [i] .y - wall_origin.y); var collideWithWall: Boolean = troops [i] .rad> Math.abs (wall12Troop.projectionOn (wall_orm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; als (collideWithRiver) troepen [i] .y + = troopVelo [i] .y * 0.3; else if (collideWithWall) // reposition troop var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (troepen [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (wand)); var reposition: Vector2D = projOnNorm.add (projOnWall); troepen [i] .x = wall_origin.x + reposition.x; troepen [i] .y = wall_origin.y + reposition.y; // schuif door de muur var-aanpassing: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (aanpassing); slideVelo = slideVelo.add (troopVelo [i]) troepen [i] .x + = slideVelo.x; troepen [i] .y + = slideVelo.y; else troops [i] .y + = troopVelo [i] .y
Ten slotte komen we aan het einde van deze tutorial: het instellen van de conditie en het controleren of troopers achter de muur zitten en daarom worden afgeschermd van de zichtbaarheid van torens. Ik heb de belangrijke patchcodes gemarkeerd:
// controleer of de vijand in zicht is // 1. Binnen de sector van het zicht // 2. Binnen bereik van weergave // 3. Dichterbij dan de huidige dichtstbijzijnde vijand 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;
Bekijk de volledige code in Scene1_2.as
.
Eindelijk kunnen we achterover leunen en de patch in actie bekijken. Druk op Ctrl + Enter om de resultaten van uw werk te bekijken. Ik heb hieronder een kopie van de werkende Flash-presentatie bijgevoegd. Klik op het torentje onderaan het podium om de simulatie te starten.