3 votes

AS3 : Quelle est la précision de la méthode getTimer() et de la classe Timer ?

Je suis en train de créer un jeu (un shmup) et j'ai commencé à me poser des questions sur la précision des minuteurs dans ActionScript. Bien sûr, ils sont assez précis lorsque l'on veut chronométrer des choses de l'ordre de quelques secondes ou décisecondes, mais il semble que les performances soient plutôt médiocres lorsque l'on passe à des plages plus fines. Il est donc difficile de faire des choses comme un vaisseau spatial qui tire une centaine de lasers par seconde.

Dans l'échantillon suivant, j'ai testé la durée (en moyenne) des intervalles entre 1000 ticks de timer prévus pour 30ms. À chaque fois, les résultats sont de ~35-36ms. En diminuant le temps, j'ai trouvé que le plancher sur le délai de la minuterie était de ~16-17ms. Cela me donne une vitesse maximale de 60 images par seconde, ce qui est excellent visuellement mais signifie aussi que je ne peux pas tirer plus de 60 lasers par seconde :-(. J'ai fait ce test plusieurs fois à 100 et 1000 boucles, mais pour le test de 30 ms et le test de 1 ms, les résultats n'ont pas changé. J'imprime dans un textField à la fin parce que l'utilisation de trace() et le lancement du swf en mode débogage semblent affecter négativement le test. Je me demande donc ce qui se passe :

  • Ce test est-il une mesure décente des performances de la classe Timer, ou mes résultats sont-ils discutables ?
  • Ces résultats changeront-ils radicalement sur d'autres machines ?

Je comprends que cela dépend de la précision de la méthode getTimer(), mais les discussions que je trouve sur ce sujet portent généralement sur la précision de getTimer() sur des intervalles plus grands.

package 
{  
    import flash.display.Sprite;
    import flash.events.TimerEvent;
    import flash.text.TextField;
    import flash.utils.getTimer;
    import flash.utils.Timer;

public class testTimerClass extends Sprite
{
    private var testTimer:Timer = new Timer(30, 1000);
    private var testTimes:Array = new Array();
    private var testSum:int = 0;
    private var testAvg:Number;
    private var lastTime:int;
    private var thisTime:int;

    public function testTimerClass()
    {
        testTimer.addEventListener(TimerEvent.TIMER, step);
        testTimer.addEventListener(TimerEvent.TIMER_COMPLETE, printResults);
        lastTime = getTimer();
        testTimer.start();
    }

    private function step(event:TimerEvent):void
    {
        thisTime = getTimer();
        testTimes.push(thisTime - lastTime);
        lastTime = thisTime;
    }

    private function printResults(event:TimerEvent):void
    {
        while (testTimes.length > 0)
        {
            testSum += testTimes.pop();
        }
        testAvg = testSum / Number(testTimer.repeatCount);              
        var txtTestResults:TextField = new TextField();
        txtTestResults.text = testAvg.toString();
        this.addChild(txtTestResults);  
    }       
}

}

Je pense que la meilleure solution serait de dessiner plusieurs lasers dans la même image avec des positions différentes et d'éviter d'avoir plus d'un objet Timer.

edit : J'ai utilisé stage.frameRate pour changer le frameRate du rendu et j'ai fait le test sur plusieurs framerates mais il n'y a pas de changement.

4voto

Luke Points 6072

Tinic Uro (ingénieur de Flash Player) a écrit une blog intéressant à ce sujet il y a quelque temps.

2voto

Au lieu d'utiliser la classe timer, je mettrais mon code de mise à jour dans un eventlistener enterframe.

Utilisez ensuite getTimer(), pour savoir combien de temps s'est écoulé depuis la dernière mise à jour, puis un peu de mathématiques pour calculer les mouvements, le "spawning" des lasers, etc. De cette façon, le jeu devrait fonctionner de la même manière, que vous obteniez 60FPS ou 10FPS.

Cela permet également d'éviter un comportement bizarre du jeu si le FPS chute temporairement, ou si le jeu tourne sur une machine lente.

Dans la plupart des cas, les calculs sont assez simples, et lorsqu'ils ne le sont pas, vous pouvez généralement "arrondir les angles" et obtenir un résultat suffisamment bon pour un jeu. Pour un mouvement simple, vous pouvez simplement multiplier l'offset de mouvement par le temps écoulé depuis la dernière mise à jour. Pour l'accélération, vous avez besoin d'une équation du second degré, mais vous pouvez simplifier en multipliant à nouveau.

En réponse au commentaire de Jorelli ci-dessous :

Le problème de la détection des collisions peut être résolu par un meilleur code, qui ne vérifie pas seulement l'état actuel, mais aussi ce qui s'est passé entre l'état précédent et l'état actuel. Je n'ai jamais eu de problèmes avec le clavier. Vous devriez peut-être essayer cette petite classe astucieuse : http://www.bigroom.co.uk/blog/polling-the-keyboard-in-actionscript-3

1voto

fenomas Points 9565

Je viens d'essayer d'exécuter votre exemple de code, exactement tel quel, sauf en tant que frame script, et là où je suis assis, Timer fonctionne exactement comme vous vous y attendez. Avec un timer de 30 ms, la moyenne se situe autour de 33-34, et avec un timer de 3 ms, elle se situe autour de 3,4 ou 3,5. Avec une minuterie de 1 ms, j'obtiens entre 1,4 et 1,6 sur un millier d'essais. Cela fonctionne de cette façon dans Flash et dans le navigateur.

Pour ce qui est de l'exactitude, voir le billet de Tinic dans la réponse de Luke. Pour ce qui est de la limite supérieure de la fréquence, si vous obtenez des événements espacés d'au moins 16 ms avec l'exemple de code que vous avez publié, il y a quelque chose de bizarre ou c'est peut-être la limite supérieure de la vitesse à laquelle votre navigateur envoie les messages de synchronisation de Flash. Si vous obtenez ces résultats dans votre jeu réel, je pense que vous avez simplement un code synchrone qui bloque les événements de la minuterie.

Une dernière chose - je sais que ce n'est pas ce que vous avez demandé, mais il serait vraiment plus sage de gérer votre logique de jeu dans un gestionnaire ENTER_FRAME. Peu importe que le code qui génère les lasers s'exécute toutes les 30 ms ou toutes les 3 ms, l'écran n'est redessiné qu'une fois par image (à moins que vous ne le forciez à se mettre à jour plus souvent, ce qui ne devrait probablement pas être le cas). Ainsi, tout ce qui se passe entre les rafraîchissements de l'écran n'est qu'un surcoût qui réduit votre framerate global, puisqu'avec un peu d'ingéniosité, il devrait être possible d'obtenir exactement les mêmes résultats qu'avec un timer qui s'exécute plus fréquemment. Par exemple, au lieu de faire apparaître un laser toutes les 3 ms, vous pourriez faire apparaître 10 lasers toutes les 30 ms et déplacer chacun d'eux d'une certaine distance le long de sa trajectoire, comme s'il avait été créé 3, 6 ou 9 ms plus tôt.

Une autre façon naturelle de faire fonctionner votre logique en fonction des événements de l'image serait de créer une boucle de jeu qui s'exécute "théoriquement" toutes les T millisecondes. Ensuite, dans votre ENTER_FRAME il suffit d'itérer cette boucle F/T fois, où F est le nombre de ms qui se sont écoulées depuis le dernier événement de trame. Ainsi, si vous voulez que le jeu se mette à jour théoriquement toutes les 5 ms, et que vous voulez que l'écran se mette à jour à 30FPS, il vous suffit de publier à 30FPS, et dans votre gestionnaire ENTER_FRAME vous appelez votre boucle de jeu principale 5 à 7 fois de suite, en fonction du temps écoulé depuis la dernière image. À l'écran, vous obtiendrez les mêmes résultats que si vous aviez utilisé un événement de temporisation de 5 ms, et vous vous passerez d'une grande partie de l'overhead.

1voto

Tim Kerchmar Points 11

BitmapData.scroll et BitmapData.copyPixels sont les méthodes les plus rapides pour effectuer un rendu en flash actuellement, bien que sur certains systèmes, l'utilisation de movieclips avec accélération du processeur soit légèrement plus rapide. Chaque quad est rendu comme une texture séparée dans OpenGL, donc ce n'est pas ce que vous attendez d'une application ordinaire accélérée par le GPU. Je le sais, car j'ai profilé les deux méthodes dans mon jeu. Une chose que vous pouvez faire pour améliorer le temps de réponse est d'appeler la mise à jour de votre jeu à partir des gestionnaires d'événements d'entrée. De cette façon, vous entendrez des sons correctement synchronisés même si le taux de rafraîchissement de votre jeu est plus faible, et l'application sera plus réactive.

0voto

Ryan Guill Points 6115

Cela me donne une vitesse maximale de 60 images par seconde, ce qui est génial visuellement mais signifie aussi que je ne peux pas tirer plus de 60 lasers par seconde :-(.

Je dirais que vous avez beaucoup de chance d'obtenir ce type de FPS et que la majorité de vos utilisateurs (en supposant que votre public est l'Internet au sens large) n'obtiendront probablement pas ce type de framerate.

Je suis d'accord pour dire que les capacités du lecteur flash ne sont probablement pas suffisantes pour ce que vous essayez de faire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X