Construire des jeux et des applications en réseau en temps réel peut être un défi. Ce tutoriel vous montrera comment connecter des clients Flash à l'aide de Cirrus et vous présentera quelques techniques essentielles..
Jetons un coup d'œil au résultat final sur lequel nous allons travailler. Cliquez sur le bouton de démarrage dans le fichier SWF ci-dessus pour créer une version "d'envoi" de l'application. Ouvrez à nouveau ce didacticiel dans une seconde fenêtre du navigateur, copiez le nearId de la première fenêtre dans la zone de texte, puis cliquez sur Démarrer pour créer une version "réceptrice" de l'application..
Dans la version "réception", vous verrez deux aiguilles en rotation: une rouge et une bleue. L'aiguille bleue tourne d'elle-même à une vitesse constante de 90 ° / seconde. L'aiguille rouge tourne pour correspondre à l'angle envoyé par la version «envoi».
(Si l'aiguille rouge semble particulièrement lente, essayez de déplacer les fenêtres du navigateur afin de pouvoir visualiser les deux fichiers SWF en même temps. Flash Player exécute les événements EnterFrame à un taux beaucoup plus faible lorsque la fenêtre du navigateur est en arrière-plan. le nouvel angle beaucoup moins fréquemment.)
Tout d'abord, vous avez besoin d'une "clé de développeur" Cirrus, disponible sur le site Adobe Labs. Il s'agit d'une chaîne de texte qui vous est attribuée lors de l'enregistrement. Vous allez l'utiliser dans tous les programmes que vous écrivez pour accéder au service. Il est donc préférable de le définir comme une constante dans l'un de vos fichiers AS, comme ceci:
Const statique publique publique CIRRUS_KEY: String = "";
Notez que chaque développeur ou équipe de développement ayant besoin de sa propre clé, et non chaque utilisateur des applications que vous créez..
Nous commençons par créer une connexion réseau en utilisant une instance de (vous l’avez deviné) NetConnection
classe. Ceci est réalisé en appelant le relier()
méthode avec votre clé mentionnée précédemment, et l'URL d'un serveur de rendez-vous Cirrus. Comme au moment de la rédaction de ce document, Cirrus utilise un protocole fermé, il n’existe qu’un tel serveur; son adresse est rtmfp: //p2p.rtmfp.net
classe publique Cirrus public static const CIRRUS_KEY: String = ""private static var netConnection: NetConnection; fonction statique publique Init (clé: String): void if (netConnection! = null) return; netConnection = new NetConnection (); try netConnection.connect (" rtmfp: //p2p.rtmfp .net ", clé); catch (e: Erreur)
Puisque rien ne se passe instantanément dans la communication réseau, le netConnection
objet vous permettra de savoir ce qu'il fait en tirant des événements, en particulier le NetStatusEvent
. Les informations importantes sont conservées dans le code
propriété de l'événement Info
objet.
fonction privée OnStatus (e: NetStatusEvent): void switch (code e.info) case "NetConnection.Connect.Success": break; // La tentative de connexion a réussi. case "NetConnection.Connect.Closed": pause; // La connexion a été fermée avec succès. case "NetConnection.Connect.Failed": break; // La tentative de connexion a échoué.
Une tentative de connexion infructueuse est généralement due au blocage de certains ports par un pare-feu. Si tel est le cas, vous n'avez pas d'autre choix que de signaler l'échec à l'utilisateur, qui ne se connectera à personne jusqu'à ce que la situation change. Le succès, en revanche, vous récompense avec votre propre prèsID
. Il s'agit d'une propriété de chaîne de l'objet NetConnection qui représente ce NetConnection particulier, sur cette Flash Player particulière, sur cet ordinateur particulier. Aucun autre objet NetConnection dans le monde n'aura le même nearID.
Le nearID est comme votre numéro de téléphone personnel - les personnes qui veulent vous parler devront le connaître. L'inverse est également vrai: vous ne pourrez pas vous connecter à quelqu'un d'autre sans connaître son ID de proximité. Lorsque vous fournissez votre identifiant nearID à quelqu'un d'autre, il l'utilisera comme farID
: farID est l'ID du client auquel vous essayez de vous connecter. Si quelqu'un d'autre vous donne son identifiant nearID, vous pouvez l'utiliser comme identifiant farID pour vous y connecter. Trouver?
Il suffit donc de se connecter à un client et de lui demander son identifiant de proximité, puis… oh, attendez. Comment pouvons-nous découvrir leur nearID (à utiliser comme notre farID) si nous ne sommes pas connectés les uns aux autres en premier lieu? La réponse que vous serez surpris d'apprendre, c'est que c'est impossible. Vous avez besoin d'un type de service tiers pour échanger les identifiants. Les exemples seraient:
NetGroup
s, que nous pourrions examiner dans un futur tutorielLa connexion réseau est purement conceptuelle et ne nous aide pas beaucoup une fois la connexion établie. Pour transférer des données d’un bout à l’autre de la connexion, nous utilisons NetStream
objets. Si une connexion réseau peut être considérée comme une voie ferrée reliant deux villes, un NetStream est un train de courrier qui achemine de véritables messages le long de la voie..
Les NetStreams sont unidirectionnels. Une fois créés, ils agissent soit en tant qu’éditeur (informations d’envoi), soit d’abonné (en recevant des informations). Si vous souhaitez qu'un seul client envoie et reçoive des informations via une connexion, vous aurez donc besoin de deux NetStreams dans chaque client. Une fois créé, NetStream peut faire des choses sophistiquées comme le streaming audio et vidéo, mais dans ce tutoriel, nous allons nous en tenir à des données simples..
Si, et seulement si, nous recevons un NetStatusEvent
de la NetConnection avec un code de NetConnection.Connect.Success
, nous pouvons créer un objet NetStream pour cette connexion. Pour un éditeur, commencez par construire le flux en utilisant une référence à l'objet netConnection que nous venons de créer et la valeur prédéfinie spéciale. Deuxièmement, appelez publier()
sur le flux et lui donner un nom. Le nom peut être ce que vous voulez, il est simplement là pour qu'un abonné puisse différencier plusieurs flux provenant du même client..
var ns: NetStream = new NetStream (netConnection, NetStream.DIRECT_CONNECTIONS); ns.publish (name, null);
Pour créer un abonné, vous transmettez à nouveau l'objet netConnection au constructeur, mais cette fois, vous transmettez également le farID du client auquel vous souhaitez vous connecter. Deuxièmement, appelez jouer()
avec le nom du flux qui correspond au nom du flux de publication de l'autre client. En d'autres termes, si vous publiez un flux portant le nom "Test", l'abonné devra utiliser le nom "Test" pour s'y connecter..
var ns: NetStream = new NetStream (netConnection, farID); ns.play (nom);
Notez que nous avions besoin d’un farID pour l’abonné et non pour l’éditeur. Nous pouvons créer autant de flux de publication que nous le souhaitons et tout ce qu'ils vont faire est de rester assis et d'attendre une connexion. Les abonnés, par contre, ont besoin de savoir exactement à quel ordinateur du monde ils sont censés s'abonner..
Une fois qu'un flux de publication est configuré, il peut être utilisé pour envoyer des données. Le netstream Envoyer
La méthode prend deux arguments: un nom de "gestionnaire" et un ensemble de paramètres de longueur variable. Vous pouvez passer n'importe quel objet de votre choix comme l'un de ces paramètres, y compris les types de base tels que Chaîne
, int
et Nombre
. Les objets complexes sont automatiquement «sérialisés», c'est-à-dire qu'ils ont toutes leurs propriétés enregistrées du côté de l'envoi, puis recréées du côté du destinataire.. Tableau
le sable ByteArray
s copie très bien aussi.
Le nom du gestionnaire correspond directement au nom d'une fonction qui sera éventuellement appelée du côté du destinataire. La liste de paramètres variables correspond directement aux arguments avec lesquels la fonction de réception sera appelée. Donc, si un appel est fait tel que:
var i: int = 42; netStream.send ("Test", "Y a-t-il quelqu'un?", i);
Le destinataire doit avoir une méthode avec le même nom et une signature correspondante:
fonction publique Test (message: String, num: int): void trace (message + num);
Sur quel objet cette méthode de réception doit-elle être définie? Tout objet que vous aimez. L’instance NetStream a une propriété appelée client
qui peut accepter n'importe quel objet que vous lui attribuez. C'est l'objet sur lequel Flash Player recherchera une méthode du nom d'envoi correspondant. S'il n'y a pas de méthode portant ce nom, ou si le nombre de paramètres est incorrect, ou si l'un des types d'argument ne peut pas être converti en type de paramètre, un AsyncErrorEvent
sera renvoyé pour l'expéditeur.
Consolidons ce que nous avons appris jusqu’à présent en plaçant le tout dans un cadre. Voici ce que nous voulons inclure:
Afin de recevoir des données, nous avons besoin d'un moyen de passer un objet dans la structure qui a des fonctions membres qui peuvent être appelées en réponse aux appels d'envoi correspondants. Plutôt qu'un paramètre d'objet arbitraire, je vais coder une interface spécifique. Je vais également mettre dans l'interface des rappels pour les divers événements d'erreur que Cirrus peut envoyer - de cette façon, je ne peux pas les ignorer..
package import flash.events.ErrorEvent; import flash.events.NetStatusEvent; importer flash.net.NetStream; interface publique ICirrus function onPeerConnect (abonné: NetStream): Boolean; fonction onStatus (e: NetStatusEvent): void; fonction onError (e: ErrorEvent): void;
Je souhaite que ma classe Cirrus soit aussi facile à utiliser que possible. Je souhaite donc masquer les détails de base des flux et des connexions à l'utilisateur. J'aurai plutôt une classe qui fera office d'expéditeur ou de destinataire et qui connectera automatiquement Flash Player au service Cirrus si une autre instance ne l'a pas déjà fait..
package 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; importer flash.net.NetStream; Classe publique Cirrus private static var netConnection: NetConnection; fonction publique get nc (): NetConnection return netConnection; // Connectez-vous au service cirrus ou si l'objet netConnection n'est pas null // supposons que nous sommes déjà connectés, fonction statique publique Init (key: String): void if (netConnection! = Null) return; netConnection = new NetConnection (); try netConnection.connect ("rtmfp: //p2p.rtmfp.net", clé); catch (e: Error) // Impossible de se connecter pour des raisons de sécurité, pas de nouvelle tentative. fonction publique Cirrus (clé: String, iCirrus: ICirrus) Init (clé); 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); if (netConnection.connected) netConnection.dispatchEvent (new NetStatusEvent (NetStatusEvent.NET_STATUS, false, false, code: "NetConnection.Connect.Success")); private var iCirrus: ICirrus; public var ns: NetStream = null;
Nous aurons une méthode pour transformer notre objet Cirrus en éditeur, et une autre pour le transformer en expéditeur:
fonction publique Publish (name: String, wrapSendStream: NetStream = null): void if (wrapSendStream! = null) ns = wrapSendStream; else try ns = new NetStream (netConnection, NetStream.DIRECT_CONNECTIONS); catch (e: Error) return; ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; ns.publish (name, null); fonction publique Play (farId: String, name: String): void try ns = new NetStream (netConnection, farId); catch (e: Error) return; ns.addEventListener (NetStatusEvent.NET_STATUS, OnStatus); ns.addEventListener (AsyncErrorEvent.ASYNC_ERROR, OnError); ns.addEventListener (IOErrorEvent.IO_ERROR, OnError); ns.client = iCirrus; try ns.play.apply (ns, [name]); catch (e: Error)
Enfin, nous devons transmettre les événements à l'interface que nous avons créée:
fonction privée OnError (e: ErrorEvent): void iCirrus.onError (e); fonction privée OnStatus (e: NetStatusEvent): void iCirrus.onStatus (e);
Envisagez le scénario suivant impliquant deux applications Flash. La première application a une aiguille qui tourne régulièrement dans un cercle (comme une main sur un cadran). Sur chaque image de l'application, la main pivote un peu plus loin et le nouvel angle est également envoyé via Internet à l'application réceptrice. L'application réceptrice possède une aiguille dont l'angle est défini uniquement à partir du dernier message reçu de l'application émettrice. Voici une question: les deux aiguilles (l'aiguille de l'application d'envoi et celle de l'application de réception) indiquent-elles toujours la même position? Si vous avez répondu «oui», je vous recommande fortement de lire.
Construisons-le et voyons. Nous allons dessiner une simple aiguille comme une ligne partant de l'origine (coordonnées (0,0)). Ainsi, chaque fois que nous définissons la propriété de rotation de la forme, l'aiguille tourne toujours comme si l'une de ses extrémités était fixée. Nous pouvons également facilement positionner la forme à l'endroit où le centre de rotation devrait être:
fonction privée CreateNeedle (x: Number, y: Number, longueur: Number, col: uint, alpha: Number): Shape var shape: Shape = new Shape (); shape.graphics.lineStyle (2, col, alpha); shape.graphics.moveTo (0, 0); shape.graphics.lineTo (0, -length); // dessine pointant vers le haut shape.graphics.lineStyle (); forme.x = x; shape.y = y; retourner la forme;
Il n'est pas pratique de devoir installer deux ordinateurs l'un à côté de l'autre, de sorte que nous utiliserons deux aiguilles sur le récepteur. La première (aiguille rouge) agira exactement comme dans la description ci-dessus, en définissant son angle uniquement à partir du dernier message reçu; la seconde (aiguille bleue) obtiendra sa position initiale à partir du premier message de rotation reçu, mais fera ensuite une rotation automatique dans le temps, sans aucun message supplémentaire, comme le fait l'aiguille qui envoie. De cette façon, nous pouvons voir toute divergence entre l'emplacement de l'aiguille et celui indiqué par les messages de rotation reçus, le tout en démarrant les deux applications et en ne visualisant que l'application réceptrice..
private var first: Boolean = true; // Appelé par le Netstream destinataire lorsqu'un message est envoyé. Fonction publique Données (valeur: Nombre): void shapeNeedleB.rotation = valeur; if (premier) shapeNeedleA.rotation = valeur; premier = faux; private var dateLast: Date = null; fonction privée OnEnterFrame (e: Event): void if (dateLast == null) dateLast = new Date (); // Détermine le temps écoulé depuis la dernière image. var dateNow: Date = new Date (); var s: Number = (dateNow.time - dateLast.time) / 1000; dateLast = dateNow; // L'aiguille A est toujours avancée sur chaque image. // Mais s'il y a un flux de réception attaché, // transmet également la valeur de la rotation. shapeNeedleA.rotation + = 360 * (s / 4); if (cirrus.ns.peerStreams.length! = 0) cirrus.ns.send ("Données", shapeNeedleA.rotation);
Nous aurons un champ de texte sur l'application qui permet à l'utilisateur d'entrer un farID auquel se connecter. Si l'application est lancée sans entrer farID, elle se définira en tant qu'éditeur. Cela couvre à peu près la création de l'application que vous voyez en haut de la page. Si vous ouvrez deux fenêtres de navigateur, vous pouvez copier l'identifiant d'une fenêtre à l'autre et configurer une application pour qu'elle s'abonne à l'autre. Cela fonctionnera réellement pour deux ordinateurs connectés à Internet - mais vous aurez besoin d'un moyen de copier via l'identifiant nearID de l'abonné..
Si vous exécutez à la fois l'expéditeur et le destinataire sur le même ordinateur, les informations de rotation de l'aiguille n'ont pas beaucoup de chemin à parcourir. En fait, les paquets de données envoyés par l'expéditeur n'ont même pas besoin de toucher le réseau local car ils sont destinés à la même machine. Dans des conditions réelles, les données doivent effectuer de nombreux sauts d'un ordinateur à un autre et avec chaque saut introduit, la probabilité de problèmes augmente..
La latence est l'un de ces problèmes. Plus les données doivent se déplacer physiquement, plus il faudra de temps pour arriver. Pour un ordinateur basé à Londres, les données mettront moins de temps à arriver de New York (un quart du globe) que de Sydney (à mi-chemin du globe). La congestion du réseau est également un problème. Lorsqu'un périphérique sur Internet fonctionne au point de saturation et qu'il est invité à transférer un autre paquet, il ne peut que le rejeter. Les logiciels utilisant Internet doivent alors détecter le paquet perdu et demander à l'expéditeur une autre copie, ce qui ajoute un décalage dans le système. En fonction de chaque extrémité de l'emplacement de la connexion dans le monde, de l'heure et de la bande passante disponible, la qualité de la connexion variera considérablement.
Alors, comment espérez-vous tester tous ces scénarios? La seule solution pratique est de ne pas essayer de trouver toutes ces conditions, mais de recréer une condition donnée si nécessaire. Ceci peut être réalisé en utilisant quelque chose appelé "émulateur WAN".
Un émulateur de réseau étendu (WAN) est un logiciel qui interfère avec le trafic réseau en provenance et à destination de la machine sur laquelle il est exécuté, de manière à tenter de recréer différentes conditions de réseau. Par exemple, en rejetant simplement les paquets réseau transmis par une machine, il peut émuler la perte de paquets qui pourrait survenir à un stade donné de la transmission des données dans le monde réel. En retardant quelque peu les paquets avant qu'ils ne soient envoyés par la carte réseau, le système peut simuler différents niveaux de latence..
Il existe différents émulateurs WAN, pour différentes plates-formes (Windows, Mac, Linux), tous concédés sous licence de différentes manières. Pour le reste de cet article, je vais utiliser l'émulateur de connexion Softperfect pour Windows pour deux raisons: il est facile à utiliser et il dispose d'un essai gratuit..
(L'auteur et Tuts + ne sont aucunement affiliés au produit mentionné. Utilisez-le à vos propres risques.)
Une fois votre émulateur WAN installé et opérationnel, vous pouvez facilement le tester en téléchargeant une sorte de flux (tel que la radio Internet ou le streaming vidéo) et en augmentant progressivement le nombre de pertes de paquets. Inévitablement, la lecture s'arrête une fois que la perte de paquet atteint une valeur critique qui dépend de votre bande passante et de la taille du flux..
Oh, et s'il vous plaît noter les points suivants:
Dans l’état normal, vous verrez les aiguilles rouge et bleue pointer à peu près dans la même position, peut-être avec l’aiguille rouge qui scintille de temps à autre en tombant derrière, puis qui se redresse soudainement. Désormais, si vous définissez votre émulateur WAN sur 2% de perte de paquet, vous verrez l’effet devenir beaucoup plus prononcé: à peu près toutes les secondes, vous verrez le même scintillement. C'est littéralement ce qui se produit lorsque le paquet contenant les informations de rotation est perdu: l'aiguille rouge reste en attente et attend le prochain paquet. Imaginez à quoi cela ressemblerait si l'application ne transférait pas la rotation de l'aiguille, mais la position d'un autre joueur dans une partie multijoueur - le personnage bégaitait chaque fois qu'il se déplaçait à une nouvelle position.
Dans des conditions défavorables, vous pouvez vous attendre (et donc concevoir) à une perte de paquets pouvant atteindre 10%. Essayez ceci avec votre émulateur WAN et vous pourriez entrevoir un second phénomène. Il est clair que l'effet de bégaiement est plus prononcé - mais si vous regardez de plus près, vous remarquerez que lorsque l'aiguille tombe très loin derrière, elle ne revient pas à la position correcte, mais doit rapidement «remonter» vers l'avant..
Dans l'exemple de jeu, cela n'est pas souhaitable pour deux raisons. Tout d’abord, il va sembler étrange de voir un personnage ne pas seulement bégayer, mais aussi se rapprocher de la position voulue. Deuxièmement, si tout ce que nous voulons voir, c’est un personnage joueur à sa position actuelle, alors nous ne nous intéressons pas à toutes ces positions interméditées: nous ne voulons que la position la plus récente lorsque le paquet est perdu, puis retransmis. Toutes les informations, sauf la plus récente, sont une perte de temps et de bande passante.
Réglez votre perte de paquets à zéro et nous examinerons la latence. Il est peu probable que dans les conditions réelles, la latence soit meilleure que de 30 ms. Réglez donc votre émulateur de réseau étendu. Lorsque vous activez l’émulation, vous remarquerez que l’aiguille s’abaisse un peu, chaque extrémité se reconfigurant à la nouvelle vitesse du réseau. Ensuite, l’aiguille rattrapera son retard jusqu’à ce qu’elle se trouve toujours à une certaine distance de l’endroit où elle devrait être. En fait, les deux aiguilles ont l’air solide comme un roc: elles sont légèrement séparées les unes des autres lorsqu’elles tournent. En réglant différentes latences, 30 ms, 60 ms, 90 ms, vous pouvez pratiquement contrôler l’écart entre les aiguilles..
Image à nouveau le jeu d'ordinateur avec le personnage de joueur toujours une certaine distance derrière où ils devraient être. Chaque fois que vous visez le joueur et que vous tentez un tir, vous manquerez, car chaque fois que vous alignez le tir, vous regardez où le joueur était, et non où il se trouve maintenant. Plus la latence est mauvaise, plus le problème est apparent. Les joueurs avec de mauvaises connexions Internet pourraient être invulnérables à toutes fins!
Il n’ya pas beaucoup de solutions miracles dans la vie, c’est donc un plaisir de raconter la suivante. Lorsque nous avons examiné la perte de paquets, nous avons constaté que l’aiguille s’enroulait de façon notable à mesure qu’elle rattrapait sa rotation après une perte d’informations. La raison en est que dans les coulisses chaque paquet envoyé avait un numéro de série associé qui indiquait sa commande.
En d'autres termes, si l'expéditeur devait envoyer 4 paquets…
A B C D
Et si l’un, disons que «B» est perdu dans la transmission pour que le récepteur reçoive…
A, C, D
… Le flux de réception peut passer immédiatement «A» à l'application, mais doit ensuite informer l'expéditeur de ce paquet manquant, attendre qu'il soit reçu à nouveau, puis passer «copie retransmise de B», «C», RÉ'. L'avantage de ce système est que les messages seront toujours reçus dans l'ordre dans lequel ils ont été envoyés et que toute information manquante est automatiquement renseignée. L’inconvénient est que la perte d’un seul paquet provoque des retards relativement importants dans la transmission..
Dans l'exemple de jeu informatique présenté (où nous mettons à jour la position du personnage joueur en temps réel), même si vous ne voulez pas perdre d'informations, il est préférable d'attendre le prochain paquet plutôt que de prendre le temps d'avertir l'expéditeur. attendre la retransmission. Au moment où le paquet "B" arrive, il aura déjà été remplacé par les paquets "C" et "D", et les données qu'il contient seront périmées..
Depuis Flash Player 10.1, une propriété a été ajoutée à la classe NetStream pour contrôler uniquement ce type de comportement. Il est utilisé comme ceci:
fonction publique SetRealtime (ns: NetStream): void ns.dataReliable = false; ns.bufferTime = 0;
Plus précisément c'est la dataReliable
la propriété qui a été ajoutée, mais pour des raisons techniques, elle doit toujours être utilisée en conjonction avec temps tampon
propriété à zéro. Si vous modifiez le code pour définir les flux d'envoi et de réception de cette manière et effectuez un autre test en cas de perte de paquet, vous remarquerez que l'effet d'enroulement disparaît..
C'est un début, mais cela laisse quand même une aiguille très nerveuse. Le problème est que la position de l'aiguille réceptrice est entièrement à la merci des messages reçus. Même avec 10% de perte de paquets, la grande majorité des informations sont toujours reçues. Cependant, comme l'application dépend énormément d'un flux de messages fluide et régulier, toute légère anomalie apparaît immédiatement..
Nous savons à quoi devrait ressembler la rotation; pourquoi ne pas simplement «remplir» les informations manquantes pour créer un papier peint par-dessus les fissures? Nous allons commencer avec une classe comme celle-ci qui a deux méthodes, une pour mettre à jour avec la rotation la plus récente, une pour lire la rotation en cours:
classe publique Msg fonction publique Write (valeur: Number, date: Date): void fonction publique Read (): Number
Maintenant, le processus a été "découplé". Chaque image que nous pouvons appeler le Lis()
méthode et mettre à jour la rotation de la forme. Au fur et à mesure que de nouveaux messages arrivent, nous pouvons appeler le Écrire()
méthode pour mettre à jour la classe avec les dernières informations. Nous allons également ajuster l'application pour qu'elle reçoive non seulement la rotation mais aussi l'heure d'envoi de la rotation.
Le processus de remplissage des valeurs manquantes des valeurs connues est appelé interpolation. L’interpolation est un sujet vaste qui prend de nombreuses formes. Nous allons donc traiter d’un sous-ensemble appelé Interpolation linéaire, ou 'Lerping'. Par programmation, cela ressemble à ceci:
fonction publique Lerp (a: nombre, b: nombre, x: nombre): nombre retourne a + ((b - a) * x);
A et B sont deux valeurs quelconques; X est généralement une valeur comprise entre zéro et un. Si X est égal à zéro, la méthode retourne A. Si X est égal à un, la méthode retourne B. Pour les valeurs fractionnaires comprises entre zéro et un, la méthode renvoie les valeurs se situant entre A et B, de sorte qu'une valeur X de 0,25 renvoie une valeur de 25%. du chemin de A à B.
En d’autres termes, si à 13 h 00, vous avez parcouru 5 milles et à 14 h, j’ai parcouru 60 milles, puis à 13 h 30, j’ai conduit Lerp (5, 60, 0.5)
miles. En fait, j’ai peut-être accéléré, ralenti et attendu dans différentes zones du trajet, mais la fonction d’interpolation ne peut en tenir compte, car elle ne dispose que de deux valeurs. Par conséquent, le résultat est une approximation linéaire et non une réponse exacte.
// Conserve 2 valeurs récentes pour interpoler. private var valueA: Number = NaN; private var valueB: Number = NaN; // Et les instances dans le temps auxquelles les valeurs se réfèrent. private var secA: Number = NaN; private var secB: Number = NaN; fonction publique Write (valeur: Number, date: Date): void var secC: Number = date.time / 1000.0; // Si la nouvelle valeur est raisonnablement éloignée de la dernière //, définissez a comme b et b comme nouvelle valeur. if (isNaN (secB) || secC -secB> 0,1) valeurA = valeurB; secA = secB; valeurB = valeur; secB = secC; fonction publique Read (): Number if (isNaN (valeurA)) renvoie la valeurB; var secC: Number = new Date (). time / 1000.0; var x: nombre = (secC-secA) / (secB-secA); retourne Lerp (valeurA, valeurB, x);
Si vous implémentez le code ci-dessus, vous remarquerez qu'il fonctionne presque correctement mais semble avoir un problème - chaque fois que l'aiguille effectue une rotation, elle semble soudainement revenir dans la direction opposée. Avons-nous oublié quelque chose? La documentation de la propriété de rotation du DisplayObject
classe révèle ce qui suit:
Indique la rotation de l'occurrence de DisplayObject, en degrés, à partir de son orientation d'origine. Les valeurs de 0 à 180 représentent la rotation dans le sens des aiguilles d'une montre; les valeurs de 0 à -180 représentent une rotation dans le sens antihoraire. Les valeurs en dehors de cette plage sont ajoutées ou soustraites de 360 pour obtenir une valeur comprise dans la plage..
C'était naïf - nous avons supposé une ligne de numéro unique à partir de laquelle nous pourrions choisir deux points et interpoler. Au lieu de cela, nous ne traitons pas avec une ligne, mais avec un cercle des valeurs. Si nous dépassons +180, nous revenons à -180. C'est pourquoi l'aiguille se comportait étrangement. Nous avons encore besoin d'interpoler, mais nous avons besoin d'une forme d'interpolation qui puisse s'enrouler correctement autour d'un cercle..
Imaginez-vous en train de regarder deux images distinctes de quelqu'un qui fait du vélo. Dans la première image, les pédales sont positionnées vers le haut du vélo; dans la deuxième image, les pédales sont positionnées vers l'avant du vélo. À partir de ces deux images et sans connaissances supplémentaires, il est impossible de déterminer si le cycliste pédale en avant ou en arrière. Les pédales pourraient avoir avancé d'un quart de cercle en avant ou de trois quarts de cercle en arrière. Dans l’application que nous avons créée, les aiguilles «pédalent» toujours vers l’avant, mais nous aimerions coder pour le cas général..
La méthode standard pour résoudre ce problème consiste à supposer que la distance la plus courte autour du cercle correspond à la direction correcte et à espérer que les mises à jour arrivent assez rapidement pour qu'il y ait une différence inférieure à un