Vector Regions Ascunderea dintr-un câmp vizual

Anterior, am explorat abordarea utilizării regiunilor vectoriale pentru a pune în aplicare câmpul vizual al unui turnulet. Trupele se apropie de turela pe câmp deschis și nu stăteau obstacole între ele. Acum, să presupunem că există o piedică, spune un zid, care ascunde vizibilitatea trupelor de la turelă; cum ar trebui să implementăm asta? Acest tutorial sugerează o abordare a abordării acestei probleme.


Rezultatul final al rezultatelor

Să aruncăm o privire asupra rezultatului final la care vom lucra. Faceți clic pe turela din partea inferioară a scenei pentru a începe simularea.


Pasul 1: Conceptul de bază

Deci, iată ce încercăm să realizăm în acest tutorial. Observați imaginea de mai sus. Trusa poate vedea unitatea de trooper dacă este în câmpul de vedere al turelei (sus). Odată ce plasăm un zid între turelă și trooper, vizibilitatea trooperului este protejată de turelă.


Pasul 2: Recapitulați

Mai întâi de toate, hai să facem o mică revizie. Spuneți că vectorul liniei de vedere a turelei este P și vectorul de la turela până la trooper este Q. Trupa este vizibilă pentru turelă dacă:

  • Unghiul dintre P și Q este mai mic decât unghiul de vizualizare (în acest caz 30 ° pe ambele părți)
  • Amplitudinea lui P este mai mare decât Q

Pasul 3: Prezentare generală a abordării

Mai sus este pseudocodul abordării pe care o vom întreprinde. Determinarea faptului dacă trooperul se află în câmpul de vedere al turelei (FOV) este explicat în pasul 2. Acum, să ajungem la determinarea dacă armata este în spatele unui perete.

Vom folosi operațiile vectoriale pentru a realiza acest lucru. Sunt sigur că, prin menționarea acestui lucru, produsul dot și produsele cruce ajung rapid în minte. Vom face un mic ocol pentru a revizui aceste două operațiuni vectoriale doar pentru a ne asigura că toată lumea poate urma.


Pasul 4: Produsul Dot și Cross între vectori

Să revedem operațiile vectoriale: produsul punctat și produsul încrucișat. Aceasta nu este o clasă de matematică, iar noi le-am acoperit mai detaliat înainte, dar, totuși, este bine să ne reîmprospătăm memoria pe lucrări, așa că am inclus imaginea de mai sus. Diagrama prezintă operația "B dot A" (colțul din dreapta sus) și operația "B cross A" (colțul din dreapta jos).

Mai importante sunt ecuațiile acestor operațiuni. Uitați-vă la imaginea de mai jos. | A | și | B | se referă la magnitudinea scalară din fiecare vector - lungimea săgeții. Rețineți că produsul punct se referă la cosinusul unghiului dintre vectori și produsul cruce se referă la sinele unghiului dintre vectori.


Pasul 5: Sinus și cosinus

Forarea în continuare în subiect, trigonometria vine să joace: sinus și cosinus. Sunt sigur că aceste grafice reaprinde amintirile plăcute (sau agoniile). Faceți clic pe butoanele din prezentarea Flash pentru a vizualiza graficele respective cu unități diferite (grade sau radiani).

Rețineți că aceste forme de undă sunt continue și repetitive. De exemplu, puteți tăia și lipi undă sinusoidală în intervalul negativ pentru a obține ceva de genul de mai jos.


Pasul 6: Sumarul valorilor

grad Sine de grad Cosin de grad
-180 0 -1
-90 -1 0
0 0 1
90 1 0
180 0 -1

Tabelul de mai sus prezintă valorile cosinusului și sinusurilor corespunzătoare anumitor grade. Veți observa că graficul sinusoidal pozitiv acoperă intervalul de la 0 ° la 180 °, iar graful cosinus pozitiv acoperă -90 ° la 90 °. Vom relaționa aceste valori la produsul dot și vom cruce mai târziu.


Pasul 7: Interpretarea geometrică a produsului Dot

Deci, cum pot fi toate acestea utile? Pentru a tăia la chase, produsul punct este o măsură a modului în care paralel vectorii sunt în timp ce produsul crud este o măsură a modului în care ortogonale vectorii sunt.

Să ne ocupăm mai întâi de produsul dot. Reamintește formula pentru produsul punctat, așa cum este menționat în Etapa 4. Putem determina dacă rezultatul este pozitiv sau negativ doar prin privirea la cosinusul unghiului întins între cele două vectori. De ce? Deoarece magnitudinea unui vector este întotdeauna pozitivă. Singurul parametru lăsat să dicteze semnul rezultatului este cosinusul unghiului.

Din nou, amintiți-vă că graficul cosinus pozitiv acoperă -90 ° -90 °, ca în pasul 6. Prin urmare, produsul punct al lui A cu oricare dintre vecotorii L, M, N, O de mai sus va produce o valoare pozitivă, între A și oricare dintre acele vectori se află între -90 ° și 90 °! (Pentru a fi precis, intervalul pozitiv este mai mult ca -89 ° -89 °, deoarece atat -90 ° si 90 ° produce valori cosinus de 0, ceea ce ne duce la punctul urmator.) Produsul punct intre A si P (dat P este perpendiculară la A) va produce 0. Restul cred că puteți ghici deja: produsul punct al lui A cu K, R sau Q va produce o valoare negativă.

Prin utilizarea produsului dot, putem împărți zona pe scena noastră în două regiuni. Produsul punct al vectorului de mai jos cu orice punct care se află în interiorul regiunii marcate cu "x" va produce o valoare pozitivă, în timp ce produsul punct cu cele din regiunea "o" va produce valori negative.


Pasul 8: Interpretarea geometrică a produsului încrucișat

Să trecem la produsul încrucișat. Amintiți-vă că produsul cruce se referă la sinus de unghi sandwich între cele două vectori. Graficul sinetic pozitiv acoperă un interval de 0 ° la 180 °; intervalul negativ acoperă 0 ° la -180 °. Imaginea de mai jos rezumă aceste puncte.

Deci, aruncând din nou la diagrama de la pasul 7, produsul încrucișat între A și K, L sau M va produce valori pozitive, în timp ce produsul încrucișat între A și N, O, P sau Q va produce valori negative. Produsul încrucișat între A și R va produce 0, deoarece sinusul de 180 ° este 0.

Pentru a clarifica mai departe, produsul încrucișat al vectorului între orice punct care se află în regiunea marcată "o" de mai jos va fi pozitivă, în timp ce cele din regiunea "x" vor fi negative.

Un punct de luat în considerare este că, spre deosebire de produsul dot, produsul cruce este sensibil la secvență. Aceasta înseamnă rezultate din AxB și BxA vor fi diferite în ceea ce privește direcția. Deci, pe măsură ce scriem programul nostru, trebuie să fim preciși atunci când alegem ce vector să fie comparat.

(Notă: Aceste concepte s-au explicat pentru spațiul cartezian 2D.)


Pasul 9: Aplicația Demo

Pentru a vă consolida înțelegerea, am introdus aici o mică cerere pentru a vă permite să jucați. Faceți clic pe bara albastră din partea de sus a scenei și trageți-o în jur. Pe măsură ce vă deplasați, valoarea casetei de text se va actualiza în funcție de operațiunea pe care ați ales-o (punctul sau produsul încrucișat între săgeata statică cu cea pe care o controlezi).

Puteți observa o ciudățenie cu direcția inversată a produsului încrucișat. Regiunea din partea de sus este negativă, iar partea de jos este pozitivă, spre deosebire de explicația noastră din etapa anterioară. Ei bine, acest lucru se datorează faptului că axa y este inversată în spațiul de coordonate Flash în comparație cu spațiul de coordonate cartezian; se arată în jos, în timp ce în mod tradițional matematicienii o consideră o direcție ascendentă.


Pasul 10: Definirea regiunilor

Acum că ați înțeles conceptul de regiuni, să facem puțină practică. Vom împărți spațiul nostru în patru cadrane: A1, A2, B1, B2.

Am înregistrat rezultatele pentru a verifica mai jos. "Vector" se referă la săgeata din imaginea de mai sus. "Punctul" se referă la orice coordonate din regiunea specificată. Vectorul împarte stadiul în patru zone principale, unde divizoarele (liniile punctate) se extind până la infinit.

Regiune Vector pe diagrama încrucișați produsul cu punct Vector pe diagrama punct produs cu punct
A1 (+), datorită spațiului de coordonate Flash (+)
A2 (+) (-)
B1 (-), din cauza spațiului de coordonate Flash (+)
B2 (-) (-)

Pasul 11: Regiuni împărțite

Iată prezentarea Flash prezentând ideile așa cum este explicat în pasul 10. Faceți clic dreapta pe scenă pentru a afișa meniul contextual și selectați regiunea pe care doriți să o vedeți evidențiată.


Pasul 12: Implementarea

Iată implementarea ActionScript a conceptului explicat în Pasul 10. Simțiți-vă liber să vizualizați întreaga bucată de cod în descărcarea sursei, ca AppLine.as.

 // a se vedea culoarea în funcție de alegerea utilizatorului funcția privată color (): void // fiecare minge pe scenă este verificată în funcție de condițiile pentru cazul selectat pentru fiecare (element var: Ball în sp) var vec1: Vector2D = vector2D noi (element. x-stage.stageWidth * 0.5; item.y - stage.stageHeight * 0.5); dacă (select == 0) if (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; altceva item.col = 0x334455;  altceva dacă (select == 1) if (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; altceva item.col = 0x334455;  altceva dacă (select == 2) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; altceva item.col = 0x334455;  altceva dacă (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; altceva item.col = 0x334455;  altceva dacă (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; 

Pasul 13: Vizibilitatea ecranată

După ce am înțeles interpretările geometrice ale produselor punctuale și produselor încrucișate, vom aplica scenariul nostru. Prezentarea Flash de mai sus prezintă variații ale aceluiași scenariu și rezumă condițiile aplicate unui trooper ecranat de un perete, încă în interiorul FOV al turelei. Puteți parcurge cadrele utilizând butoanele săgeată.

Următoarele explicații se bazează pe spațiul de coordonate Flash 2D. În Cadrul 1, un perete este plasat între turelă și trooper. Fie A și B vectorii de la turela la coadă și respectiv în capul vectorului peretelui. Fie C vectorul peretelui, iar D este vectorul din coada peretelui către trooper. În cele din urmă, permiteți Q să fie vectorul de la turelă până la trooper.

Am comportat condițiile de mai jos.

Locație Produs crucificat
Trupa este în fața zidului C x D> 0
Trupa este în spatele zidului C x D

Aceasta nu este singura condiție aplicabilă, pentru că trebuie să restricționăm trooperul în interiorul liniilor punctate de ambele părți. Verificați cadrele 2-4 pentru a vedea următorul set de condiții.

Locație Produs crucificat
Trupa se află în părțile laterale ale zidului. Q x A 0
Trupa este în partea stângă a zidului Q x A> 0, Q x B> 0
Trupa este în partea dreaptă a zidului Q x A

Cred ca colegii mei cititori pot alege acum conditiile potrivite pentru a determina daca trooperul este ascuns sau nu. Rețineți că acest set de condiții este evaluat după ce am descoperit că trupele se află în interiorul FOV al turelei (consultați pasul 3).


Pasul 14: Implementarea ActionScript

Iată implementarea ActionScript a conceptelor explicate la pasul 13. Imaginea de mai sus arată vectorul inițial al peretelui, C. Faceți clic și trageți butonul roșu de mai jos și mutați-l pentru a vedea zona ecranată. Puteți vedea codul sursă complet în HiddenSector.as.

Bine, sper că ați experimentat cu mingea roșie și, dacă sunteți suficient de atenți, ați fi observat o eroare. Rețineți că nu există zonă protejată deoarece butonul roșu se mișcă spre stânga celuilalt capăt al peretelui, inversând astfel vectorul de perete spre dreapta spre stânga. Soluția este în etapa următoare.

Cu toate acestea, înainte de aceasta, să examinăm aici un fragment de acțiune ActionScript important HiddenSector.as:

 funcția privată highlight (): void var lineOfSight: Vector2D = nou Vector2D (0, -50) var sector: Number = Math2.radianOf (30); pentru fiecare (element var: Ball în sp) var turret_sp: Vector2D = nou Vector2D (item.x - turret.x, item.y - turret.y); // Q dacă (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 altceva item.col = 0;  item.draw (); 

Pasul 15: Direcția zidului

Pentru a rezolva această problemă, trebuie să știm dacă vectorul de perete se îndreaptă spre stânga sau spre dreapta. Să presupunem că avem un vector de referință, R, care întotdeauna arată spre dreapta.

Direcția vectorului Produsul Dot
Zidul este îndreptat spre dreapta (aceeași parte ca R) w. R> 0
Zidul este îndreptat spre stânga (partea opusă R) w. R

Desigur, există alte moduri în jurul acestei probleme, dar cred că este o oportunitate de a utiliza conceptele exprimate în acest tutorial, deci nu te duci.


Pasul 16: Schimbări de acțiuni ale Actului

Mai jos este o prezentare Flash care implementează corecția explicată la pasul 15. După ce ați jucat cu ea, derulați în jos pentru a verifica trucurile ActionScript.

Modificările de la implementarea anterioară sunt evidențiate. De asemenea, seturile de condiții sunt redefinite în funcție de direcția peretelui:

 funcția privată evidențiere (): void var lineOfSight: Vector2D = new Vector2D (0, -50); var sector: număr = Math2.radianOf (30); var pointToRight: Vector2D = Vector2D nou (10, 0); // adăugat în a doua versiune pentru fiecare (var: Ball în sp) var turret_sp: Vector2D = nou Vector2D (item.x - turret.x, item.y - turret.y); // Q dacă (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 altceva lateral = wall.vectorProdus (wall_sp)> 0 // C x D && turret_sp.vectorProdus (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();   

Verificați sursa completă în HiddenSector2.as.


Pasul 17: Configurați peretele

Acum ne vom plictisi munca Scene1.as din tutorialul anterior. Mai întâi, ne vom ridica zidul.

Inițiem variabilele,

 clasa publică Scene1_2 extinde Sprite private var river: Sprite; privat var wall_origin: Vector2D, perete: Vector2D; // adăugat în al doilea tutorial private var trupe: Vector.; privat var troopVelo: Vector.;

... apoi trageți peretele pentru prima dată,

 funcția publică Scene1_2 () makeTroops (); makeRiver (); makeWall (); // adăugat la al doilea tutorial makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, start); funcția start (): void stage.addEventListener (Event.ENTER_FRAME, mutare); 
 funcția privată makeWall (): void wall_origin = Vector2D nou (200, 260); perete = nou 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); 

... și redenumiți pe fiecare cadru, deoarece graphics.clear () apelul este undeva în behaviourTurret ():

 // a adăugat în al doilea tutorial mișcarea funcției private (e: Event): void behaviourTroops (); behaviourTurret (); redrawWall (); 
 // adăugat în al doilea tutorial funcția privată 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); 

Pasul 18: Interacțiunea cu peretele

Trupele vor interacționa și cu peretele. Pe măsură ce se ciocnesc cu peretele, se vor aluneca de-a lungul peretelui. Nu voi încerca să intru în detaliu acest lucru, așa cum a fost documentat extensiv în Reacția de coliziune între un cerc și un segment de linie. Încurajez cititorii să verifice acest lucru pentru explicații suplimentare.

Următorul fragment de viață trăiește în funcție behaviourTroops ().

 // Versiunea 2 // dacă bate prin râu, încetinește // dacă se ciocnește cu peretele, alunecă prin // altceva viteza normală var collideWithRiver: Boolean = river.hitTestObject (trupe [i]) var wall_norm: Vector2D = wall.rotate ( Math2.radianOf (-90)); var wall12Trope: Vector2D = nou Vector2D (trupe [i] .x - wall_origin.x, trupe [i] .y - wall_origin.y); var collideWithWall: Boolean = trupe [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; dacă (collideWithRiver) trupe [i] .y + = truopVelo [i] .y * 0.3; altfel dacă (collideWithWall) // repoziționa trupe var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (trupe [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (perete)); var repozitionare: Vector2D = projOnNorm.add (projOnWall); trupe [i] .x = wall_origin.x + reposition.x; trupelor [i] .y = wall_origin.y + reposition.y; // alunecare prin perete var reglare: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); Vârf varVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (ajustare); slideVelo = slideVelo.add (troopVelo [i]) trupe [i] .x + = slideVelo.x; trupe [i] .y + = slideVelo.y;  altfel trupe [i] .y + = truopVelo [i] .y

Pasul 19: Verificarea "ascunsă" a trupelor

În cele din urmă, venim la carnea acestui tutorial: stabilirea condiției și verificarea faptului dacă trupele sunt în spatele zidului și, prin urmare, sunt protejate de vizibilitatea foișorilor. Am subliniat codurile importante de patch-uri:

 // verificați dacă inamicul se află în vedere // 1. În cadrul sectorului de vedere // 2. În intervalul de vizualizare // 3. Mai aproape de inamicul cel mai apropiat curent 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.vectorProdus (wall12troop) < 0; var shielded:Boolean = withinLeft && withinRight && behindWall //if all conditions fulfilled, update closestEnemy if (c1 && c2&& c3 && !shielded) closestDistance = turret2Item.getMagnitude(); closestEnemy = item; 

Verificați codul complet în Scene1_2.as.


Pasul 20: Aplicați aplicația

În cele din urmă, putem să ne întoarcem și să verificăm patch-ul în acțiune. Apăsați Ctrl + Enter pentru a vedea rezultatele lucrării. Am inclus o copie a prezentării flash Flash de mai jos. Faceți clic pe turela din partea inferioară a scenei pentru a începe simularea.

Cod