Vektorregionen Vor einem Sichtfeld verstecken

Zuvor haben wir den Ansatz der Verwendung von Vektorregionen zum Implementieren des Sichtfelds eines Turms untersucht. Die Truppen näherten sich auf offenem Feld dem Turm, und zwischen ihnen lagen keine Hindernisse. Nehmen wir an, es gibt ein Hindernis, sagen wir, eine Mauer, die die Sichtbarkeit der Truppe vom Turm verdeckt. Wie sollen wir das umsetzen? In diesem Lernprogramm wird ein Ansatz zur Lösung dieses Problems vorgeschlagen.


Endergebnisvorschau

Werfen wir einen Blick auf das Endergebnis, auf das wir hinarbeiten werden. Klicken Sie auf den Turm unten auf der Bühne, um die Simulation zu starten.


Schritt 1: Das Grundkonzept

Wir versuchen also in diesem Tutorial etwas zu erreichen. Beobachten Sie das Bild oben. Der Turm kann die Truppeneinheit sehen, wenn sie sich im Sichtfeld des Turmes befindet (oben). Sobald wir eine Mauer zwischen dem Turm und dem Soldaten aufgestellt haben, ist die Sicht des Soldaten vom Turm abgeschirmt.


Schritt 2: Zusammenfassung

Zunächst einmal eine kleine Überarbeitung. Angenommen, der Vektor der Sichtlinie des Turms ist P und der Vektor von Turman zu Soldat ist Q. Der Soldat ist für den Turmturm sichtbar, wenn:

  • Winkel zwischen P und Q ist kleiner als der Blickwinkel (in diesem Fall 30 ° auf beiden Seiten)
  • Die Größe von P ist mehr als Q

Schritt 3: Annäherungsüberblick

Oben ist der Pseudo-Code für den Ansatz, den wir unternehmen werden. In Schritt 2 wird erklärt, ob sich der Soldat im Sichtfeld des Revolvers befindet (FOV). Nun wollen wir feststellen, ob sich der Soldat hinter einer Mauer befindet.

Wir werden dazu Vektoroperationen verwenden. Ich bin sicher, durch die Erwähnung davon kommen mir das Dot-Produkt und das Cross-Produkt schnell in den Sinn. Wir werden einen kleinen Umweg machen, um diese beiden Vektoroperationen zu überarbeiten, um sicherzustellen, dass jeder folgen kann.


Schritt 4: Punkt- und Kreuzprodukt zwischen Vektoren

Lassen Sie uns noch einmal die Vektoroperationen durchgehen: Punktprodukt und Kreuzprodukt. Dies ist keine Math-Klasse, und wir haben diese bereits ausführlicher behandelt, aber es ist trotzdem gut, unser Gedächtnis über die Arbeitsweise aufzufrischen, deshalb habe ich das Bild oben eingefügt. Das Diagramm zeigt die Operation "B-Punkt A" (obere rechte Ecke) und "B-Kreuz A" (rechte untere Ecke)..

Wichtiger sind die Gleichungen dieser Operationen. Schauen Sie sich das Bild unten an. | A | und | B | beziehen sich auf die skalare Größenordnung jedes Vektors - die Länge des Pfeils. Das Punktprodukt bezieht sich auf den Cosinus des Winkels zwischen den Vektoren, und das Kreuzprodukt bezieht sich auf den Sinus des Winkels zwischen den Vektoren.


Schritt 5: Sinus und Cosinus

Die Trigonometrie ist ein weiterer Schritt zum Thema: Sinus und Cosinus. Ich bin mir sicher, dass diese Graphen die Erinnerungen (oder Qualen) wieder aufleben lassen. Klicken Sie unten auf die Schaltflächen der Flash-Präsentation, um die Diagramme mit unterschiedlichen Einheiten (Grad oder Bogenmaß) anzuzeigen..

Beachten Sie, dass diese Wellenformen kontinuierlich und sich wiederholend sind. Zum Beispiel können Sie die Sinuswelle ausschneiden und in den negativen Bereich einfügen, um so etwas wie unten zu erhalten.


Schritt 6: Zusammenfassung der Werte

Grad Sinus grad Cosinus von Grad
-180 0 -1
-90 -1 0
0 0 1
90 1 0
180 0 -1

Die Tabelle oben zeigt die Cosinus- und Sinuswerte, die bestimmten Graden entsprechen. Sie werden feststellen, dass das positive Sinusdiagramm den Bereich von 0 ° bis 180 ° abdeckt und das positive Cosinusdiagramm über -90 ° bis 90 °. Wir werden diese Werte später auf das Punktprodukt und das Kreuzprodukt beziehen.


Schritt 7: Geometrische Interpretation des Punktprodukts

Wie können all diese nützlich sein? Um es auf den Punkt zu bringen: Das Punktprodukt ist ein Maß dafür, wie parallel die Vektoren sind, während Kreuzprodukt ein Maß dafür ist, wie senkrecht die Vektoren sind.

Lasst uns zuerst mit dem Punktprodukt umgehen. Man erinnere sich an die Formel für das Punktprodukt, wie in Schritt 4 erwähnt. Wir können feststellen, ob das Ergebnis positiv oder negativ ist, indem der Kosinus des Winkels zwischen den beiden Vektoren betrachtet wird. Warum? Weil die Größe eines Vektors immer positiv ist. Der einzige Parameter, der das Vorzeichen des Ergebnisses bestimmen kann, ist der Cosinus des Winkels.

Es sei erneut daran erinnert, dass der positive Cosinus-Graph -90 ° - 90 ° wie in Schritt 6 abdeckt. Daher wird das Punktprodukt von A mit einem der Vecotrs L, M, N, O oben einen positiven Wert erzeugen, da sich der Winkel verkeilt zwischen A und einem dieser Vektoren liegt zwischen -90 ° und 90 °! (Um genau zu sein, ist der positive Bereich eher -89 ° - 89 °, da sowohl -90 ° als auch 90 ° Cosinuswerte von 0 erzeugen, was uns zum nächsten Punkt bringt.) Das Punktprodukt zwischen A und P (gegebenes P ist senkrecht zu A) ergibt 0. Der Rest, den ich glaube, kann man schon erraten: Das Punktprodukt von A mit K, R oder Q erzeugt einen negativen Wert.

Mit dem Punktprodukt können wir die Fläche auf unserer Bühne in zwei Regionen aufteilen. Das Punktprodukt des nachstehenden Vektors mit einem Punkt, der innerhalb des mit "x" markierten Bereichs liegt, erzeugt einen positiven Wert, wohingegen das Punktprodukt mit denen im mit "o" markierten Bereich negative Werte erzeugt.


Schritt 8: Geometrische Interpretation des Kreuzprodukts

Lasst uns zum Kreuzprodukt übergehen. Denken Sie daran, dass das Kreuzprodukt sich auf das bezieht Sinus der Winkel zwischen den beiden Vektoren eingeschlossen. Die positive Sinuskurve deckt einen Bereich von 0 ° bis 180 ° ab; Der negative Bereich umfasst 0 ° bis -180 °. Das Bild unten fasst diese Punkte zusammen.

Wenn man sich noch einmal das Diagramm aus Schritt 7 ansieht, erzeugt das Kreuzprodukt zwischen A und K, L oder M positive Werte, während das Kreuzprodukt zwischen A und N, O, P oder Q negative Werte erzeugt. Das Kreuzprodukt zwischen A und R wird 0 erzeugen, da der Sinus von 180 ° 0 ist.

Zur weiteren Verdeutlichung ist das Kreuzprodukt des Vektors zwischen jedem Punkt, der in der "o" -markierten Region darunter liegt, positiv, während diejenigen in der "x" -markierten Region negativ sind.

Dabei ist zu beachten, dass das Kreuzprodukt im Gegensatz zum Dot-Produkt sequenzempfindlich ist. Dies bedeutet Ergebnisse von AxB und BxA wird in der Richtung unterschiedlich sein. Wenn wir also unser Programm schreiben, müssen wir bei der Auswahl des Vektors, mit dem verglichen werden soll, präzise vorgehen.

(Hinweis: Diese erläuterten Konzepte gelten für den kartesischen 2D-Raum.)


Schritt 9: Demo-Anwendung

Um Ihr Verständnis zu verstärken, habe ich hier eine kleine Anwendung eingefügt, mit der Sie spielen können. Klicke auf den blauen Ball oben auf der Bühne und ziehe ihn herum. Beim Verschieben wird der Wert des Textfelds aktualisiert, abhängig davon, welche Operation Sie gewählt haben (Punkt oder Kreuzprodukt zwischen dem statischen Pfeil und dem von Ihnen kontrollierten)..

Mit der umgekehrten Richtung des Kreuzprodukts können Sie eine Seltsamkeit beobachten. Die obere Region ist negativ und die untere ist positiv, im Gegensatz zu unserer Erklärung im vorherigen Schritt. Nun, dies liegt daran, dass die y-Achse im Flash-Koordinatenraum im Vergleich zum kartesischen Koordinatenraum invertiert ist. es zeigt nach unten, während es bei Mathematikern traditionell nach oben gerichtet ist.


Schritt 10: Definieren von Regionen

Nachdem Sie das Konzept der Regionen verstanden haben, üben wir ein wenig Übung. Wir teilen unseren Raum in vier Quadranten auf: A1, A2, B1, B2.

Ich habe die Ergebnisse tabellarisch aufgelistet. "Vektor" bezieht sich hier auf den Pfeil im obigen Bild. "Punkt" bezieht sich auf eine beliebige Koordinate im angegebenen Bereich. Der Vektor unterteilt die Bühne in vier Hauptbereiche, in denen die Teiler (gepunktete Linien) bis unendlich reichen.

Region Vektor auf Diagrammkreuzprodukt mit Punkt Vektor auf Diagrammpunktprodukt mit Punkt
A1 (+) wegen Flash-Koordinatenraum (+)
A2 (+) (-)
B1 (-) wegen Flash-Koordinatenraum (+)
B2 (-) (-)

Schritt 11: Regionen aufgeteilt

Hier ist die Flash-Präsentation mit den Ideen, die in Schritt 10 erläutert wurden. Klicken Sie mit der rechten Maustaste auf die Bühne, um das Kontextmenü zu öffnen und die Region auszuwählen, die hervorgehoben werden soll.


Schritt 12: Implementierung

Hier ist die ActionScript-Implementierung des Konzepts, das in Schritt 10 erläutert wurde. Sie können sich den gesamten Code im Quelldownload ansehen AppLine.as.

 // Hervorhebung der Farbe entsprechend der Benutzerauswahl private function color (): void // Jeder Ball auf der Bühne wird für jeden ausgewählten Fall gegen die Bedingungen geprüft (var item: Ball in sp) var vec1: Vector2D = neuer 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 (Wählen Sie == 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; 

Schritt 13: Abgeschirmte Sicht

Nachdem wir die geometrischen Interpretationen von Punktprodukt und Kreuzprodukt verstanden haben, werden wir sie auf unser Szenario anwenden. Die Flash-Präsentation oben zeigt Variationen desselben Szenarios und fasst die Bedingungen zusammen, die für einen Soldaten gelten, der von einer Mauer abgeschirmt ist und sich noch im FOV des Turms befindet. Sie können mit den Pfeiltasten durch die Rahmen blättern.

Die folgenden Erläuterungen beziehen sich auf den 2D-Flash-Koordinatenraum. In Bild 1 befindet sich eine Wand zwischen dem Turm und dem Soldaten. Sei A und B die Vektoren vom Turm zum Schwanz bzw. zum Kopf des Wandvektors. Sei C der Vektor der Mauer und D der Vektor vom Schwanzende bis zum Soldaten. Schließlich sei Q der Vektor vom Turm zum Soldaten.

Die daraus resultierenden Bedingungen habe ich unten aufgelistet.

Ort Kreuzprodukt
Truppe ist vor der Wand C x D> 0
Truppe ist hinter der Mauer C x D

Dies ist nicht die einzige Bedingung, die gilt, da wir den Soldaten auch auf beide Seiten innerhalb der gepunkteten Linien beschränken müssen. Schauen Sie sich die Bilder 2-4 an, um die nächsten Bedingungen zu sehen.

Ort Kreuzprodukt
Truppe ist innerhalb der Wände. Q x A 0
Truppe ist links von der Wand Q x A> 0, Q x B> 0
Truppe ist rechts von der Wand Q x A

Ich denke, meine Mitleser können jetzt die geeigneten Bedingungen wählen, um zu bestimmen, ob der Soldat nicht sichtbar ist oder nicht. Beachten Sie, dass diese Bedingungen bewertet werden, nachdem wir festgestellt haben, dass sich die Truppe im FOV des Turms befindet (siehe Schritt 3)..


Schritt 14: Actionscript-Implementierung

Hier ist die ActionScript-Implementierung der Konzepte, die in Schritt 13 erläutert wurden. Das Bild oben zeigt den Anfangsvektor der Wand, C. Klicken Sie auf die rote Schaltfläche und ziehen Sie sie nach unten, um den abgeschirmten Bereich anzuzeigen. Sie können den vollständigen Quellcode in sehen HiddenSector.as.

Okay, ich hoffe, Sie haben mit dem roten Ball experimentiert, und wenn Sie aufmerksam genug sind, haben Sie möglicherweise einen Fehler bemerkt. Beachten Sie, dass kein Bereich abgeschirmt ist, da sich der rote Knopf nach links vom anderen Ende der Wand bewegt und der Wandvektor so invertiert, dass er nach links und nicht nach rechts zeigt. Die Lösung ist im nächsten Schritt.

Sehen wir uns jedoch vorher ein wichtiges ActionScript-Snippet hier an HiddenSector.as:

 Privates Funktions-Highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50) var-Sektor: Number = Math2.radianOf (30); für jeden (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 (); 

Schritt 15: Richtung der Wand

Um dieses Problem zu lösen, müssen wir wissen, ob der Wandvektor nach links oder nach rechts zeigt. Nehmen wir an, wir haben einen Referenzvektor R, der immer nach rechts zeigt.

Richtung des Vektors Skalarprodukt
Die Wand zeigt nach rechts (dieselbe Seite wie R) w. R> 0
Wand zeigt nach links (gegenüber von R) w. R

Natürlich gibt es auch andere Möglichkeiten, dieses Problem zu lösen, aber ich denke, es ist eine Möglichkeit, die in diesem Lernprogramm formulierten Konzepte anzuwenden.


Schritt 16: Actionscript Tweaks

Nachfolgend finden Sie eine Flash-Präsentation, die die in Schritt 15 erläuterte Korrektur implementiert. Wenn Sie damit gespielt haben, scrollen Sie nach unten, um die ActionScript-Anpassungen zu überprüfen.

Die Änderungen gegenüber der vorherigen Implementierung werden hervorgehoben. Außerdem werden die Bedingungssätze entsprechend der Wandrichtung neu definiert:

 Privates Funktions-Highlight (): void var lineOfSight: Vector2D = new Vector2D (0, -50); var Sektor: Number = Math2.radianOf (30); var pointToRight: Vector2D = neuer Vector2D (10, 0); // in der zweiten Version für jeden hinzugefügt (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();   

Überprüfen Sie die vollständige Quelle HiddenSector2.as.


Schritt 17: Wand aufstellen

Jetzt werden wir unsere Arbeit ergänzen Szene1.as aus dem vorherigen Tutorial. Zuerst werden wir unsere Mauer aufstellen.

Wir initiieren die Variablen,

 public class Scene1_2 erweitert Sprite privater Fluss: Sprite; private var wall_origin: Vector2D, wall: Vector2D; // im zweiten Tutorial private var troops hinzugefügt: Vector.; private var troopVelo: Vektor.;

… Dann erstmal die Wand zeichnen,

 öffentliche Funktion Scene1_2 () makeTroops (); makeRiver (); makeWall (); // im zweiten Tutorial hinzugefügt makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); function start (): void stage.addEventListener (Event.ENTER_FRAME, verschieben); 
 private Funktion makeWall (): void wall_origin = new Vector2D (200, 260); Wand = neuer 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); 

… Und in jedem Frame neu zeichnen, weil die graphics.clear () Anruf ist irgendwo in BehaviourTurret ():

 // im zweiten Tutorial hinzugefügt private Funktion move (e: Event): void behaviourTroops (); BehaviourTurret (); redrawWall (); 
 // im zweiten Tutorial hinzugefügt private 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); 

Schritt 18: Interaktion mit der Wand

Truppen werden auch mit der Mauer interagieren. Wenn sie mit der Wand kollidieren, gleiten sie an der Wand entlang. Ich werde nicht näher darauf eingehen, da dies ausführlich in der Kollisionsreaktion zwischen einem Kreis und einem Liniensegment dokumentiert wurde. Ich möchte die Leser dazu auffordern, dies zu überprüfen.

Der folgende Ausschnitt lebt in der Funktion BehaviourTroops ().

 // Version 2 // Wenn Sie durch den Fluss laufen, verlangsamen Sie // wenn Sie mit der Wand kollidieren, schieben Sie durch // sonst normale Geschwindigkeit var collideWithRiver: Boolean = river.hitTestObject (Troops [i]) var Math2.radianOf (-90)); var wall12Troop: Vector2D = neuer Vector2D (Truppen [i] .x - wall_origin.x, Truppen [i] .y - wall_origin.y); var collideWithWall: Boolean = Truppen [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; if (collideWithRiver) Truppen [i] .y + = troopVelo [i] .y * 0,3; else if (collideWithWall) // Repositioniere Truppe var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (Truppen [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (wall)); var reposition: Vector2D = projOnNorm.add (projOnWall); Truppen [i] .x = wall_origin.x + reposition.x; Truppen [i] .y = wall_origin.y + reposition.y; // schiebe durch die Wand var-Einstellung: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (Anpassung); slideVelo = slideVelo.add (troopVelo [i]) Truppen [i] .x + = slideVelo.x; Truppen [i] .y + = SlideVelo.y;  else Truppen [i] .y + = troopVelo [i] .y

Schritt 19: Überprüfen der "Verborgenheit" der Soldaten

Schließlich kommen wir zum Fleisch dieses Tutorials: Aufstellen des Zustands und Überprüfen, ob sich Soldaten hinter der Mauer befinden und daher vor der Sichtweite des Turms geschützt sind. Ich habe die wichtigen Patchcodes hervorgehoben:

 // prüfe ob der Feind in Sichtweite ist // 1. Innerhalb des Sichtbereichs // 2. Im Sichtbereich // 3. Näher als der aktuellste Feind 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; 

Checke den kompletten Code ein Szene1_2.as.


Schritt 20: Starten Sie die Anwendung

Zum Schluss können wir uns zurücklehnen und den Patch in Aktion überprüfen. Drücken Sie Strg + Eingabetaste, um die Ergebnisse Ihrer Arbeit anzuzeigen. Ich habe unten eine Kopie der funktionierenden Flash-Präsentation beigefügt. Klicken Sie auf den Turm unten auf der Bühne, um die Simulation zu starten.