Construirea de jocuri și aplicații în rețea în timp real poate fi o provocare. Acest tutorial vă va arăta cum să conectați clienții Flash folosind Cirrus și vă va prezenta câteva tehnici vitale.
Să aruncăm o privire asupra rezultatului final la care vom lucra. Faceți clic pe butonul de pornire din SWF de mai sus pentru a crea o versiune de "trimitere" a aplicației. Deschideți din nou acest tutorial într-o a doua fereastră de browser, copiați aproapeId din prima fereastră în caseta de text și apoi faceți clic pe Start pentru a crea o versiune de primire a aplicației.
În versiunea "primire", veți vedea două ace rotative: una roșie, una albastră. Acul albastru se rotește de la sine, la o viteză constantă de 90 ° / secundă. Acul roșu se rotește pentru a se potrivi cu unghiul trimis de versiunea "trimitere".
(Dacă acul roșu pare foarte lejer, încercați să mutați ferestrele browserului pentru a putea vedea simultan ambele SWF-uri Flash Player rulează evenimentele EnterFrame la o rată mult mai mică când fereastra browserului se află în fundal, astfel încât fereastra "trimitere" transmite noul unghi mult mai puțin frecvent.)
Mai intai lucrurile: aveti nevoie de o cheie de dezvoltare a lui Cirrus, care poate fi obtinuta pe site-ul Adobe Labs. Acesta este un șir de text atribuit în mod unic pentru înregistrare. Veți folosi acest lucru în toate programele pe care le scrieți pentru a avea acces la serviciu, deci ar fi mai bine să îl definiți ca o constantă într-unul dintre fișierele AS, cum ar fi:
static static public CIRRUS_KEY: String = "„;
Rețineți că fiecare dezvoltator sau echipa de dezvoltare care are nevoie de cheia proprie, nu de fiecare utilizator al aplicațiilor pe care le creați.
Începem prin crearea unei conexiuni de rețea utilizând o instanță a (ați ghicit-o) NetConnection
clasă. Acest lucru este realizat prin apelarea conectați()
cu cheia dvs. menționată anterior și cu adresa URL a serverului de întâlnire al lui Cirrus. Deoarece în momentul în care Cirrus utilizează un protocol închis, există doar un astfel de server; adresa sa este rtmfp: //p2p.rtmfp.net
clasa publică Cirrus static static public CIRRUS_KEY: String = ""netConnection = netConnection = netConnection = new netConnection (); încercați netConnection.connect (" rtmfp: //p2p.rtmfp .net ", cheie); captura (e: eroare)
Deoarece nimic nu se întâmplă instantaneu în comunicarea în rețea, netConnection
obiect vă va spune ce face prin arderea evenimentelor, în special a NetStatusEvent
. Informațiile importante sunt ținute în cod
proprietate a evenimentului info
obiect.
funcția privată OnStatus (e: NetStatusEvent): void switch (e.info.code) caz "NetConnection.Connect.Success": break; // A încercat conexiunea. cazul "NetConnection.Connect.Closed": break; // Conexiunea a fost închisă cu succes. cazul "NetConnection.Connect.Failed": break; // Încercarea de conectare a eșuat.
O încercare de conexiune nereușită se datorează de obicei faptului că anumite porturi sunt blocate de un firewall. În acest caz, nu aveți de ales decât să raportați eșecul utilizatorului, deoarece nu se vor conecta la nimeni până când situația nu se schimbă. Succesul, pe de altă parte, te răsplătește cu propriul tău nearID
. Aceasta este o proprietate de șir a obiectului NetConnection care reprezintă acel NetConnection special, pe respectivul Flash Player, pe calculatorul respectiv. Niciun alt obiect NetConnection din lume nu va avea același identificator nearID.
ID-ul proximal este ca numărul tău personal de telefon - oamenii care vor să vorbească cu tine vor trebui să știe asta. Reversul este, de asemenea, adevărat: nu veți putea să vă conectați la nimeni altcineva fără a cunoaște identificarea lor aproape. Atunci când furnizați altcuiva cu ID-ul dvs. aproape, îl veți folosi ca a Farid
: farID este ID-ul clientului la care încercați să vă conectați. Dacă altcineva vă oferă ID-ul pe care îl aveți, puteți să îl utilizați ca un farID pentru a vă conecta la acestea. Ia-l?
Deci, tot ce trebuie să facem este să ne conectăm la un client și să îi întrebăm pentru identificarea lor necorespunzătoare, și apoi ... așteptați. Cum putem să le aflăm identitatea aproape nedeterminată (pentru a ne folosi ca farID) dacă nu suntem conectați unul la celălalt în primul rând? Răspunsul pe care veți fi surprins să auziți este că este imposibil. Aveți nevoie de un serviciu de terță parte pentru a schimba ID-urile. Exemple ar fi:
NetGroup
s, pe care am putea să le privim într-un viitor tutorialConexiunea la rețea este pur conceptuală și nu ne ajută prea mult după ce conexiunea a fost creată. Pentru a transfera de fapt date de la un capăt al conexiunii la altul pe care îl folosim NetStream
obiecte. Dacă o conexiune la rețea poate fi considerată a fi o construcție a unei căi ferate între două orașe, atunci un NetStream este un tren de corespondență care transmite mesaje reale în jos pe pistă.
NetStreams sunt unidirecționale. Odată create, aceștia acționează fie ca editor (trimitere de informații), fie ca un abonat (care primește informații). Dacă doriți ca un singur client să trimită și să primească informații prin intermediul unei conexiuni, veți avea nevoie de două NetStreams în fiecare client. Odată creat, un NetStream poate face lucruri fanteziste, cum ar fi stream audio și video, dar în acest tutorial vom rămâne cu date simple.
Dacă și numai dacă primim a NetStatusEvent
din NetConnection cu un cod de NetConnection.Connect.Success
, putem crea un obiect NetStream pentru acea conexiune. Pentru un editor, construiți mai întâi fluxul folosind o referință la obiectul netConnection pe care tocmai l-am creat și valoarea predefinită specială. În al doilea rând, sunați publica()
pe curs și dați-i un nume. Numele poate fi orice vă place, este doar acolo pentru un abonat pentru a face diferența între mai multe fluxuri provenite de la același client.
var ns: NetStream = nou NetStream (netConnection, NetStream.DIRECT_CONNECTIONS); ns.publish (nume, null);
Pentru a crea un abonat, treceți din nou obiectul netConnection în constructor, dar de această dată treceți și farID-ul clientului la care doriți să vă conectați. În al doilea rând, sunați Joaca()
cu numele fluxului care corespunde cu numele fluxului de publicare al celuilalt client. Pentru al pune în alt mod, dacă publicați un flux cu numele "Test", abonatul va trebui să utilizeze numele "Test" pentru a se conecta la acesta.
var ns: NetStream = nou NetStream (netConnection, farID); ns.play (nume);
Rețineți cum am avut nevoie de un farID pentru abonat, și nu pentru editor. Putem crea cât mai multe fluxuri de publicații cum ne place și tot ce vor face este să stea acolo și să aștepte o conexiune. Abonații, pe de altă parte, trebuie să știe exact ce computer din lumea în care ar trebui să se aboneze.
Odată ce un flux de publicare este configurat, acesta poate fi folosit pentru a trimite date. Netstream Trimite
metoda are două argumente: un nume de "handler" și un set de parametri cu lungime variabilă. Puteți trece orice obiect doriți ca unul dintre acești parametri, inclusiv tipuri de bază cum ar fi Şir
, int
și Număr
. Obiectele complexe sunt automat "serializate" - adică au toate proprietățile înregistrate pe partea de expediere și apoi re-create pe partea de primire. mulțime
și ByteArray
s copie prea bine.
Numele handlerului corespunde direct numelui unei funcții care va fi apelată în cele din urmă pe partea receptoare. Lista parametrilor variabili corespunde direct cu argumentele la care va fi apelată funcția de recepție. Deci, dacă se face un apel, cum ar fi:
var i: int = 42; netStream.send ("Test", "Este cineva acolo?", i);
Receptorul trebuie să aibă o metodă cu același nume și o semnătură corespunzătoare:
funcția publică Test (mesaj: String, num: int): void trace (message + num);
Pe ce obiect trebuie definită această metodă de primire? Orice obiect doriți. Instanța NetStream are o proprietate numită client
care poate accepta orice obiect pe care îl alocați. Acesta este obiectul pe care Flash Player va căuta o metodă a numelui de trimitere corespunzător. Dacă nu există nici o metodă cu acest nume sau dacă numărul de parametri este incorect sau dacă vreunul din tipurile de argument nu poate fi convertit la tipul de parametru, AsyncErrorEvent
va fi concediat pentru expeditor.
Să consolidăm lucrurile pe care le-am învățat până acum, punând totul într-un fel de cadru. Iată ce vrem să includem:
Pentru a primi date avem nevoie de un mod de trecere a unui obiect în cadrul care are funcții membre care pot fi numite ca răspuns la apelurile corespunzătoare de trimitere. Mai degrabă decât un parametru obiect arbitrar, voi codifica o interfață specifică. De asemenea, voi pune în interfață câteva apeluri pentru diferitele evenimente de eroare pe care le poate trimite Cirrus - în acest fel nu le pot ignora.
pachet import flash.events.ErrorEvent; import flash.events.NetStatusEvent; import flash.net.NetStream; interfața publică ICirrus function onPeerConnect (abonat: NetStream): Boolean; funcția onStatus (e: NetStatusEvent): void; funcția onError (e: ErrorEvent): void;
Vreau ca clasa mea Cirrus să fie cât mai ușor de folosit posibil, așa că vreau să ascund detaliile de bază ale fluxurilor și conexiunilor de la utilizator. În schimb, voi avea o clasă care acționează ca un expeditor sau un receptor și care conectează Flash Player la serviciul Cirrus în mod automat dacă o altă instanță nu a făcut-o deja.
pachet import flash.events.AsyncErrorEvent; import flash.events.ErrorEvent; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.events.NetStatusEvent; import flash.events.SecurityErrorEvent; import flash.net.NetConnection; import flash.net.NetStream; clasa publică Cirrus private static var netConnection: NetConnection; funcția publică obține nc (): NetConnection return netConnection; // Conectați-vă la serviciul cirrus sau dacă obiectul netConnection nu este null // presupunem că suntem deja conectați funcția statică publică Init (key: String): void if (netConnection! = Null) retur; netConnection = NetConnection nou (); încercați netConnection.connect ("rtmfp: //p2p.rtmfp.net", cheie); captură (e: Eroare) // Nu se poate conecta din motive de securitate, fără reapariția punctului. funcția publică Cirrus (cheia: String, iCirrus: ICirrus) Init (cheie); this.iCirrus = iCirrus; netConnection.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); netConnection.addEventListener (IOErrorEvent.IO_ERROR, OnError); netConnection.addEventListener (SecurityErrorEvent.SECURITY_ERROR, OnError) netConnection.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); dacă (netConnection.connected) netConnection.dispatchEvent (noul NetStatusEvent (NetStatusEvent.NET_STATUS, false, false, code: "NetConnection.Connect.Success")); var iCirrus privat: ICirrus; public var ns: NetStream = null;
Vom avea o metodă de a transforma obiectul nostru Cirrus într-un editor și altul pentru al transforma într-un expeditor:
funcția publică Publicare (nume: String, wrapSendStream: NetStream = null): void if (wrapSendStream! = null) ns = wrapSendStream; altceva try ns = nou NetStream (netConnection, NetStream.DIRECT_CONNECTIONS); captură (e: eroare) return; ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; ns.publish (nume, null); funcția publică Play (farId: String, nume: String): void try ns = nou NetStream (netConnection, farId); captură (e: eroare) return; ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; încercați ns.play.apply (ns, [name]); captură (e: eroare)
În cele din urmă, trebuie să trecem de la evenimente la interfața pe care am creat-o:
funcția privată OnError (e: ErrorEvent): void iCirrus.onError (e); funcția privată OnStatus (e: NetStatusEvent): void iCirrus.onStatus (e);
Luați în considerare următorul scenariu care implică două aplicații Flash. Prima aplicație are un ac care se rotește constant în jurul unui cerc (ca o mână pe o față de ceas). Pe fiecare cadru al aplicației, mâna este rotită puțin mai departe și, de asemenea, noul unghi este trimis pe internet la aplicația de primire. Aplicația de primire are un ac, al cărui unghi este setat exclusiv din ultimul mesaj primit de la aplicația care trimite. Iată o întrebare: ambele ace (acul pentru aplicația de trimitere și acul pentru aplicația de primire) indică întotdeauna aceeași poziție? Dacă ați răspuns "da", vă recomand să citiți mai departe.
Să construim și să vedem. Vom desena un ac simplu ca o linie eminând de origine (coordonate (0,0)). În acest fel, ori de câte ori setăm proprietatea de rotație a formei, acul se va roti întotdeauna ca și când un capăt va fi fixat și, de asemenea, putem poziționa cu ușurință forma, unde centrul de rotație ar trebui să fie:
funcția privată CreateNeedle (x: Număr, y: Număr, lungime: Număr, col: uint, alfa: Număr): Formă var shape: Shape = new Shape (); shape.graphics.lineStyle (2, col, alfa); shape.graphics.moveTo (0, 0); shape.graphics.linePentru a (0, -length); // desenați orientarea în sus shape.graphics.lineStyle (); shape.x = x; shape.y = y; forma întoarsă;
Este incomod să aveți două computere una lângă cealaltă, astfel încât pe receptor să folosim de fapt două ace. Primul (acul roșu) va acționa la fel ca în descrierea de mai sus, stabilindu-și unghiul numai de cel mai recent mesaj primit; cel de-al doilea (acul albastru) își va primi poziția inițială de la primul mesaj de rotație primită, dar apoi se va roti automat în timp, fără alte mesaje, la fel ca acul care trimite. În acest fel, putem observa orice discrepanță între locul în care ar trebui să fie acul și unde mesajele de rotație primite spun că ar trebui să fie, toate prin pornirea ambelor aplicații și apoi vizualizând doar aplicația de primire.
private var primul: Boolean = true; // Chemat de către fluxul netstream atunci când este trimis un mesaj public funcția Data (value: Number): void shapeNeedleB.rotation = value; dacă (prima) shapeNeedleA.rotation = valoare; prima = falsă; private var dataLast: Data = null; funcția privată OnEnterFrame (e: Eveniment): void if (dateLast == null) dateLast = new Date (); // Eliminați timpul scurs de la ultimul cadru. var dateNow: Data = data nouă (); var s: Numărul = (dateNow.time - dataLast.time) / 1000; dateLast = dateNow; // Acul A este întotdeauna avansat pe fiecare cadru. // Dar dacă există atașat un flux receptor, // transmite de asemenea și valoarea rotației. formaNeedleA.ro + + 360 * (s / 4); dacă (cirrus.ns.peerStreams.length! = 0) cirrus.ns.send ("Date", shapeNeedleA.rotation);
Vom avea un câmp de text pe aplicație care permite utilizatorului să introducă un farID la care să se conecteze. Dacă aplicația este pornită fără a introduce un farID, se va stabili ca editor. Aceasta se referă la crearea aplicației pe care o vedeți în partea de sus a paginii. Dacă deschideți două ferestre de browser, puteți să copiați id-ul de la o fereastră la alta și să setați o aplicație pentru a vă abona la cealaltă. Acesta va funcționa de fapt pentru oricare două computere conectate la Internet - dar veți avea nevoie de un fel de copiere pe ID-ul apropiat al abonatului.
Dacă rulați atât expeditorul, cât și receptorul de pe același computer, informația de rotație a acului nu se deplasează prea mult. De fapt, pachetele de date trimise de expeditor nu trebuie să atingă deloc rețeaua locală, deoarece sunt destinate aceleiași mașini. În condițiile din lumea reală, datele trebuie să facă multe hamei de la calculator la computer și cu fiecare hops introdus, probabilitatea creșterii problemelor.
Latența este o astfel de problemă. Mai departe, datele trebuie să călătorească fizic, cu atât va dura mai mult să ajungă. Pentru un computer cu sediul în Londra, datele vor lua mai puțin timp pentru a ajunge de la New York (un sfert din drumul din întreaga lume) decât de la Sydney (pe jumătate în jurul globului). Rețeaua de congestie este, de asemenea, o problemă. Atunci când un dispozitiv de pe Internet funcționează la punctul de saturație și este rugat să transfere încă un alt pachet, nu poate face altceva decât să îl arunce. Software-ul care utilizează internetul trebuie să detecteze apoi pachetul pierdut și să solicite expeditorului o altă copie, toate acestea adăugând lag în sistem. În funcție de fiecare capăt al locației conexiunii în lume, ora din zi și lățimea de bandă disponibilă, calitatea conexiunii va varia foarte mult.
Deci, cum sperați să testați pentru toate aceste scenarii diferite? Singurul răspuns practic nu este să ieși și să încerci să găsești toate aceste condiții diferite, ci să re-creezi o condiție dată, după cum este necesar. Acest lucru poate fi realizat folosind ceva numit "emulator WAN".
Un emulator WAN (Network Wide Area) este un software care interferează cu traficul de rețea care călătorește către și de la mașina pe care rulează, astfel încât să încerce să recreeze diferite condiții de rețea. De exemplu, pur și simplu aruncați pachetele de rețea transmise de la o mașină, aceasta poate emula pierderea de pachete care ar putea apărea într-o anumită etapă a transmiterii în realitate a datelor. Prin întârzierea pachetelor cu o anumită sumă înainte ca acestea să fie trimise de cartela de rețea, acestea pot simula diferite nivele de latență.
Există diverse emulatoare WAN, pentru diferite platforme (Windows, Mac, Linux), toate licențiate în diverse moduri. Pentru restul acestui articol voi folosi Softperfect Connection Emulator pentru Windows din două motive: este ușor de folosit și are un proces gratuit.
(Autorul și Tuts + nu sunt în nici un fel afiliate cu produsul menționat. Utilizați-l pe propriul risc.)
După instalarea și funcționarea emulatorului dvs. WAN, îl puteți testa cu ușurință descărcând un fel de flux (cum ar fi radioul pe Internet sau streaming video) și crescând treptat cantitatea de pierderi de pachete. În mod inevitabil, redarea va fi blocată odată ce pierderea pachetului ajunge la o valoare critică care depinde de lățimea de bandă și de dimensiunea fluxului.
Oh, și vă rugăm să rețineți următoarele puncte:
În starea normală veți vedea acele roșii și albastre care indică o poziție aproape identică, probabil cu acul roșu ocazional pâlpâind în timp ce coboară în urmă, apoi brusc prinde din nou. Acum, dacă setați emulatorul dvs. WAN la pierderea de pachete de 2%, veți vedea că efectul devine mult mai pronunțat: aproximativ în fiecare secundă sau așa veți vedea aceeași pâlpâire. Acesta este ceea ce se întâmplă literalmente atunci când pachetul care transporta informațiile de rotație este pierdut: acul roșu doar stă și așteaptă pentru următorul pachet. Imaginați-vă cum ar arăta dacă aplicația nu transfera rotația acului, ci poziția unui alt jucător într-un joc multiplayer - personajul ar stutter de fiecare dată când sa mutat într-o nouă poziție.
În condiții adverse vă puteți aștepta (și prin urmare ar trebui să proiectați pentru) până la 10% pierdere de pachete. Încearcă acest lucru cu ajutorul emulatorului WAN și ar putea să observi un al doilea fenomen. În mod evident, efectul de stuttering este mai pronunțat - dar dacă vă uitați atent, veți observa că atunci când acul se află în spate, nu se întoarce înapoi în poziția corectă, ci trebuie să se "repede" repede.
În exemplul jocului acest lucru este nedorit din două motive. În primul rând, se va părea ciudat să vezi un personaj nu doar stuttering, dar apoi să măriți pozitiv spre poziția sa dorită. În al doilea rând, dacă tot ceea ce vrem să vedem este un personaj de jucător în poziția sa actuală, atunci nu ne pasă de toate acele poziții intermediare: vrem doar poziția cea mai recentă atunci când pachetul este pierdut și apoi retransmis. Toate informațiile, cu excepția celui mai recent, reprezintă o pierdere de timp și de lățime de bandă.
Setați pierderea pachetului până la zero și vom analiza latența. Este puțin probabil că în condiții reale veți deveni vreodată mai bine decât o latență de 30 ms, deci setați-vă emulatorul WAN pentru asta. Când activați emulația, veți observa că acul cedează înapoi într-un fel, deoarece fiecare punct final se reconfigurează cu noua viteză a rețelei. Apoi, acul se va prinde din nou până când se află în mod constant la o anumită distanță în spatele unde ar trebui să fie. De fapt, cele două ace vor arăta stânci solide: doar puțin în afară unul de altul în timp ce se rotesc. Prin setarea diferitelor sume de latență, 30ms, 60ms, 90ms, puteți controla practic cât de departe sunt acele.
Imaginați din nou jocul de calculator cu caracterul jucătorului întotdeauna la o anumită distanță în spatele unde ar trebui să fie. De fiecare dată când v-ați îndreptat spre jucator și luați o lovitură, veți pierde, pentru că de fiecare dată când compuneți lovitura, vă uitați la locul unde a fost playerul, și nu în locul în care se află acum. Cu cât este mai mare latența, cu atât este mai evidentă problema. Jucătorii cu conexiuni slabe la internet ar putea fi, în toate scopurile, invulnerabile!
Nu există multe reparații rapide în viață, deci este o plăcere să relaționăm următoarea. Când ne-am uitat la pierderea de pachete, am văzut cum acul ar veni în mod vădit înainte, deoarece a ajuns la rotația dorită după o pierdere de informații. Motivul pentru aceasta este că, în spatele scenei, fiecare pachet trimis a avut un număr de serie asociat cu acesta care a indicat ordinul său.
Cu alte cuvinte, dacă expeditorul va trimite 4 pachete ...
A, B, C, D
Și dacă unul, să zicem că "B" este pierdut în transmisie, astfel încât receptorul devine ...
A, C, D
... fluxul de destinație poate transmite imediat aplicației "A", dar trebuie să informeze expeditorul cu privire la acest pachet lipsă, să aștepte ca acesta să fie recepționat din nou, apoi să treacă "copia retransmisă a lui B", "C" D“. Avantajul acestui sistem este că mesajele vor fi întotdeauna primite în ordinea în care au fost trimise și că toate informațiile care lipsesc sunt completate automat. Dezavantajul este că pierderea unui singur pachet determină întârzieri relativ mari în transmisie.
În exemplul jocului de calculator discutat (în cazul în care actualizăm poziția personajului jucătorului în timp real), în ciuda faptului că nu dorește să piardă informații, este mai bine să așteptați următorul pachet să vină decât să acordați timp să-i spuneți expeditorului și așteptați pentru re-transmisie. Până când pachetul "B" va sosi, va fi deja înlocuit cu pachetele "C" și "D", iar datele pe care le conține vor fi învechite.
De la Flash Player 10.1, a fost adăugată o proprietate la clasa NetStream pentru a controla doar acest tip de comportament. Se folosește astfel:
funcția publică SetRealtime (ns: NetStream): void ns.dataReliable = false; ns.bufferTime = 0;
Mai exact, este dataReliable
proprietate care a fost adăugată, dar din motive tehnice ar trebui să fie folosită întotdeauna împreună cu setarea bufferTime
proprietate la zero. Dacă modificați codul pentru a seta fluxurile de trimitere și de primire în acest fel și pentru a rula un alt test la pierderea pachetelor, veți observa că efectul de lichidare dispare.
Acesta este un început, dar rămâne un ac foarte nervos. Problema este că poziția acului primitor este în întregime la mila mesajelor primite. La o pierdere de pachete chiar de 10%, majoritatea informațiilor sunt încă recepționate, totuși deoarece grafica aplicației depinde atât de mult de un flux neted al mesajelor, orice ușoară discrepanță apare imediat.
Știm cum ar trebui să arate rotația; de ce nu doar "umple" informațiile lipsă de tapet peste crăpături? Vom începe cu o clasă cum ar fi următoarele, care are două metode, una pentru actualizarea cu rotația cea mai curentă, una pentru citirea rotației curente:
public class Msg funcția publică Scrie (valoare: Număr, data: Data): void funcția publică Citește (): Număr
Acum procesul a fost "decuplat". Fiecare cadru îl putem apela Citit()
și actualizați rotația formei. Când și când apar mesaje noi putem apela Scrie()
pentru a actualiza clasa cu cele mai recente informații. De asemenea, vom ajusta aplicația astfel încât să nu primească doar rotația, ci timpul în care a fost trimisă rotația.
Este numit procesul de completare a valorilor lipsă din cele cunoscute interpolare. Interpolarea este un subiect mare care ia multe forme, așa că vom aborda un subset numit Interpolare liniară, sau "Lerping". În mod programatic, aceasta arată:
funcția publică Lerp (a: număr, b: număr, x: număr): numărul return a + ((b - a) * x);
A și B sunt oricare două valori; X este de obicei o valoare între zero și una. Dacă X este zero, metoda returnează A. Dacă X este una, metoda returnează B. Pentru valorile fracționale între zero și unu, metoda returnează valori în parte între A și B - astfel încât o valoare X de 0,25 returnează o valoare de 25% a drumului de la A la B.
Cu alte cuvinte, dacă la ora 13:00 am condus 5 mile, iar la ora 14:00 am condus 60 de mile, apoi la ora 13:30 am condus Lerp (5, 60, 0,5)
mile. Așa cum se întâmplă, s-ar fi putut accelera, am încetinit și am așteptat în trafic în diferite părți ale călătoriei, însă funcția de interpolare nu poate fi luată în considerare pentru că are doar două valori de lucru. Prin urmare, rezultatul este o aproximare liniară și nu un răspuns exact.
// Țineți 2 valori recente pentru a interpola. valoarea privată varA: Număr = NaN; valoarea private varB: Număr = NaN; // Și cazurile în timp la care se referă valorile. private var secA: Număr = NaN; priv var var secB: număr = NaN; funcția publică Scrie (valoare: Număr, dată: Data): void var secC: Number = date.time / 1000.0; // Dacă noua valoare este distanțată în mod rezonabil față de ultima //, atunci setați a ca b și b ca noua valoare. dacă (esteNaN (secB) || secC -secB> 0,1) valueA = valueB; secA = secB; valoareB = valoare; secB = secC; funcția publică Citește (): Numărul if (isNaN (valueA)) valoarea returnatăB; var secC: Număr = dată nouă () time / 1000.0; var x: Numărul = (secC-secA) / (secB-secA); retur Lerp (valoareA, valoareB, x);
Dacă implementați codul de mai sus, veți observa că funcționează aproape corect, dar pare să aibă un fel de glitch - de fiecare dată când acul face o rotație, apare brusc înapoi în direcția opusă. Am ratat ceva? Documentația pentru proprietatea de rotație a DisplayObject
clasa dezvăluie următoarele:
Indică rotația instanței DisplayObject, în grade, din orientarea inițială. Valorile de la 0 la 180 reprezintă rotația în sensul acelor de ceasornic; valorile de la 0 la -180 reprezintă rotația în sens invers acelor de ceasornic. Valorile din afara acestui interval sunt adăugate sau scăzute de la 360 pentru a obține o valoare în intervalul respectiv.
A fost naivă - am asumat o singură linie de număr, din care am putea alege două puncte și să interpolam. În schimb, nu avem de-a face cu o linie, ci cu o cerc de valori. Dacă mergem peste +180, înfășurăm din nou la -180. De aceea, acul se comporta ciudat. Trebuie să interpolam, dar avem nevoie de o formă de interpolare care să se poată împacheta corect în jurul unui cerc.
Imaginați-vă că vă uitați la două imagini separate ale cuiva care călătoresc pe bicicletă. În prima imagine pedalele sunt poziționate spre partea superioară a bicicletei; în a doua imagine pedalele sunt poziționate spre partea din față a bicicletei. Din aceste două imagini și fără cunoștințe suplimentare nu este posibil să se stabilească dacă călărețul se pedalează înainte sau înapoi. Pedalele ar fi putut să avanseze un sfert de cerc înainte sau trei sferturi dintr-un cerc înapoi. După cum se întâmplă, în aplicația pe care am construit-o, acele sunt mereu "pedalează" în față, dar am dori să codificăm cazul general.
Modul standard pentru a rezolva aceasta este să presupunem că distanța cea mai scurtă din jurul cercului este direcția corectă și, de asemenea, sperăm că actualizările vin suficient de repede, astfel încât să existe o diferență mai mică de jumătate de cerc între fiecare actualizare. S-ar putea să fi avut experiența jucând un joc de conducere multiplayer unde mașina unui alt jucător a rotit momentan într-un mod aparent imposibil - acesta este motivul pentru care.
var min: număr = -180; var max: număr = +180; // Putem "adăuga" sau "scădea" drumul în jurul cercului // dând două măsuri diferite de distanță var difAdd: Number = (b> a)? b-a: (max-a) + (b-min); var difSub: Numărul = (b < a)? a-b : (a-min) + (max-b);
Dacă 'difAdd' este mai mic decât 'difSub', vom începe la 'a' și vom adăuga o interpolare liniară a sumei X. Dacă 'difSub' este distanța mai mică, vom începe la 'a' și vom scădea de la este o interpolare liniară a sumei X. Potențial care ar putea da o valoare care nu se încadrează în gama "min" și "max", așa că vom folosi o aritmetică modulară pentru a obține o valoare din nou în interval. Setul complet de calcule arată astfel:
// Funcție care dă un rezultat similar operatorului% // mod, dar pentru valoarea float. funcția publică Mod (val: Număr, div: Număr): Număr return (val - Math.floor (val / div) * div); // Asigură că valorile din intervalul min / max // se înfășoară corect în intervalul funcției publice Circle (val: Număr, min: Număr, max: Număr): Număr return Mod (val - min, )) + min; // Efectuează o interpolare circulară a lui A și B cu factorul X, // înfășurarea la extrema min / max funcția publică CLerp (a: Număr, b: Număr, x: Număr, min: Număr, max: Număr) var difAdd: Numărul = (b> a)? b-a: (max-a) + (b-min); var difSub: Numărul = (b < a)? a-b : (a-min) + (max-b); return (difAdd < difSub)? Circle(