C’est un fait avéré que l’animation de clips vectoriels peut être coûteuse en CPU et en mémoire. Ce phénomène s’accentue d’autant plus lors de développements sur mobile où le processeur et la mémoire vive sont extrêmement limités par rapport à des machines de bureau.
Pour optimiser les animations et augmenter le frame rate de nos .swf, il existe des techniques de mise en cache natives des éléments vectoriels (cacheAsBitmap, cacheAsBitmapMatrix…) ou « manuelles » (méthode draw() de la classe BitmapData) qui sont efficaces. Le principe est toujours le même : il s’agit de faire un rendu bitmap (une rasterisation) à l’exécution, autrement dit de générer un png transparent à partir des données vectorielles, plus lourd en mémoire, mais beaucoup plus rapide à animer en terme de CPU.
J’ai dressé un panorama de ces techniques de mise en cache et des gains associés lors de ma mini-conf des lightning talks. Toutefois, lorsque l’on souhaite mettre en cache des éléments animés de type MovieClip, ces techniques sont totalement inefficaces – voire contre-performantes.
C’est pourquoi je vous livre ici deux classes, AnimatedBitmap et AnimatedBitmapData qui permettent de mettre en cache à l’exécution les movie clips animés pour un gain de performance spectaculaire.
Un petit benchmark pour commencer, décochez « use Cache » pour passer à la version vectorielle (beaucoup plus lente).
Sur ma machine, je peux créer 2000 chiens animés et le frame rate reste à 30 ips. En mode vectoriel (use cache décoché), au-dessus de 100 chiens, le frame rate commence à diminuer.
Classes AnimatedBitmap et AnimatedBitmapData
Ces deux classes s’utilisent à la manière des classes Bitmap et BitmapData : une classe contient les data (AnimatedBitmapData) et l’autre gère l’affichage des data (AnimatedBitmap). De cette manière, on peut ré-utiliser les mêmes data pour x objets animés sans les dupliquer, d’où un gain de mémoire et de temps de rendu.
AnimatedBitmapData
La première chose à faire, avant tout rendu, c’est d’initialiser la classe en appelant dessus la méthode statique init() au moins une fois. La classe a en effet besoin d’une référence au Stage pour fonctionner.
1 2 3 4 5 6 | // Initialisation de la classe AnimatedBitmapData.init(this.stage); // le clip à mettre en cache var dog:Dog = new Dog(); // création des data var abd:AnimatedBitmapData = new AnimatedBitmapData(dog); |
En interne, la classe va parcourir la time line du clip et faire un draw() sur chaque frame, puis stocker les bitmapData obtenus dans un Vector.
Quelques infos sur le fonctionnement de cette classe :
- Si le clip a des couleurs modifiées (teinte, alpha…), mais que vous ne souhaitez pas les conserver, il faudra passer le second param « preserveColor » du constructeur à false.
- Dans certains cas, notamment avec des filtres flous, les images du rendu peuvent être coupées sur les bords. Cela est dû aux méthodes getBounds() et generateFilterRect() de Flash qui renvoient parfois des valeurs imprécises. Si cela arrive, vous pouvez passer en 3e param du constructeur une marge en pixel qui agrandira la zone de rendu.
- Enfin, si votre clip possède des étiquettes (frameLabels) elles seront conservées et accessibles.
- Les transformations « matricielles » du clips sont supportées (rotation, scale…), ainsi que les filtres.
Lorsque vous souhaitez détruire l’instance, appelez dessus la méthode destroy(), qui libèrera automatiquement la mémoire occupée via la méthode dispose() de la classe BitmapData.
1 2 | // destruction des data et libération de la mémoire abd.destroy(); |
AnimatedBitmap
C’est la classe d’affichage, qui a besoin d’un AnimatedBitmapData pour fonctionner.
On l’instancie de la façon suivante.
1 2 3 4 | // instanciation : on passe en 1er param un AnimatedBitmapData var ab:AnimatedBitmap = new AnimatedBitmap(abd, AnimatedBitmap.ENTER_FRAME, 30); // affichage this.addChild(ab); |
Les params du constructeurs sont les suivants :
- source:AnimatedBitmapData > une instance d’AnimatedBitmapData
- ticker:String > le type « d’horloge » utilisé pour animer l’instance. Vous avez le choix entre « timer », « enterFrame » et « external ». Vous pouvez utiliser les constantes statiques de la classe pour renseigner ce param : AnimatedBitmap.TIMER, AnimatedBitmap.ENTER_FRAME ou AnimatedBitmap.EXTERNAL.
- frameRate:uint > la cadence d’animation de votre objet. Comme elle n’est pas liée au frameRate de votre swf, elle peut être différente de celui-ci (plus lente ou plus rapide).
- updateAfterEvent:Boolean > passez true si vous utilisez un ticker de type « timer » et que la cadence de l’objet est plus rapide que celle du document. Cela forcera le rendu du player à chaque nouvelle image pour plus de fluidité (et plus de process).
- pixelSnapping:Boolean > pour que les coordonnées des bitmaps du cache soit ou non sur des pixels entiers.
- smoothing:Boolean > activation ou non du lissage, utile lorsque l’objet subit une rotation ou un scale.
Quelques infos sur le type de ticker (2nd param) :
AnimatedBitmap.TIMER (défaut) : une instance de Timer est utilisée. Pratique si l’on souhaite aller plus vite que le frame rate de l’animation, mais moins optimisé qu’un enterFrame.
AnimatedBitmap.ENTER_FRAME : un enterFrame est utilisé. A choisir si votre objet animé doit avoir le même frameRate que votre animation.
AnimatedBitmap.EXTERNAL : aucune horloge interne ne sera mise en place, ce sera à vous d’implémenter votre propre horloge externe et d’appeler la méthode update() sur vos AnimatedBitmap. C’est la méthode la plus optimisée (pas de multiplication des horloges), à choisir si vous avez beaucoup d’objets animés ou si vous faites du jeu.
Exemple pour le type « external » :
1 2 3 4 5 6 7 8 9 10 11 12 | this.addEventListener(Event.ENTER_FRAME, loop); function loop(event:Event):void { var lng:int = dogs.numChildren; var dog:AnimatedBitmap; for (var i:int = 0; i < lng; i++) { dog = dogs.getChildAt(i) as AnimatedBitmap; dog.update(); } } |
Méthodes et propriétés de la classe AnimatedBitmap
J’ai repris l’API de la classe MovieClip, donc on retrouve toutes les méthodes et propriétés classiques d’un MC : totalFrames, currentFrame, currentLabel, currentFrameLabel, play(), stop(), gotoAndPlay(frame/label) …
Des méthodes spécifiques existent cependant :
- playTo(frame/label) : la tête de lecture part de sa position actuelle et s’arrêtera à la frame/étiquette passée en paramètre.
- playFromTo(startFrame, endFrame) : joue la tête de lecture depuis une frame/étiquette de départ jusqu’à une frame/étiquette d’arrivée.
- replace(target:MovieClip) : une petite méthode qui permet de remplacer automatiquement un clip affiché par un AnimatedBitmap. Pratique pour faire du remplacement automatique.
Des propriétés aussi :
- frameRate:uint > pour modifier à la volée le frameRate de votre instance.
- isPlaying:Boolean > pour savoir si le clip est en cours de lecture ou non.
Un petit aperçu :
Pour l’exemple, voici le code du swf ci-dessus :
1 2 3 4 5 6 7 8 9 10 11 | AnimatedBitmapData.init(this.stage); var dog:Dog = new Dog(); dog.scaleX = dog.scaleY = 2.2; dog.x = stage.stageWidth * 0.5 + 100; dog.y = stage.stageHeight * 0.5; addChild(dog); var abd:AnimatedBitmapData = new AnimatedBitmapData(dog); var ab: = new AnimatedBitmap(abd, AnimatedBitmap.TIMER, stage.frameRate, true); ab.replace(dog); |
Destruction des instances :
La classe possède une méthode destroy(), qui supprime tous les écouteurs et retire l’instance de la display list.
Par ailleurs, il existe une propriété statique destroyData:Boolean, réglée par défaut sur false. Passée à true, elle met en place une destruction automatique et générale des AnimatedBitmapData au moment de la destruction d’un AnimatedBitmap ou lors de l’écrasement d’un animatedBitmapData par un autre via la propriété du même nom.
Conclusion
Ces classes se montreront particulièrement utiles pour du dev AIR mobile. Lorsqu’elles sont couplées au mode GPU (renderMode) le frame rate bondit !
Par contre les limites sont le temps de rendu et la mémoire occupée. Je vous renvoie aux slides de ma mini-conf pour cela. Je pense peut-être implémenter une classe SpriteSheet à coupler avec AnimatedBitmapData pour utiliser des sprite sheets et réduire le temps de rendu.
Faites-moi part de vos remarques, questions, et éventuels bugs.
La classe est documentée (en anglais) > accéder à la doc
Voici les sources : AnimatedBitmap_sources
Et merci à Thibault et son composant Banana Slice pour l’inspiration : http://www.bytearray.org/?p=117

Super Nico!
J’ai quand même une question, en tant que clones, pourquoi les chiens ne sont-ils pas synchronisés? c’est voulu?
08 avr 2011 @ 3:54
Ha ben oui c’est voulu, y a un random() sur le gotoAndPlay, c’est plus joli comme ça. Dans le premier exemple, j’utilise un ticker de type EXTERNAL, donc y’aurait pas de soucis pour tout synchroniser
08 avr 2011 @ 7:08
Alors la … vraiment impressionnant ! Merci je cherchais depuis longtemps comment optimiser les rendus vectoriels et bien je suis bouche bée !
Extrêmement pratique et très propre ! merci beaucoup
16 avr 2011 @ 11:57
@cedric : Merci ! Content que tu sois content
17 avr 2011 @ 7:31
Merci pour ces exemples édifiants !
Et puis c’est cool de retrouver ce bon chien-chien =)
03 mai 2011 @ 12:53
Merci de nous avoir fait découvrire ces 2 CLASSES ANIMATEDBITMAP ET ANIMATEDBITMAPDATA.
je trouve cela très propre et bien pratique.
Bien à toi…
11 mai 2011 @ 11:11
Bonjour,
bravo pour ces class, que je vais sans doute utiliser activement…
Toutefois j’ai trois questions :
- pourquoi ne pas passer la class du movieclip à la class animatedbitmap directement, plutot que de lui passer une instance, puis faire le replace (cette instanciation puis replace ne pourrait il pas etre fait automatiquement dans votre class) ? ca me paraitrait plus simple…
- pour des raisons d’optimisation, ne serait il pas préférable d’utiliser un grand bitmapdata pour tout l’ecran et de venir y « copier » les differents animatedbitmap a chaque image plutot que de jouer avec les coordonnées x et y des animatedbitmap ? ou est ce que ce genre de pratique n’apporte pas grand chose ?
- est ce vous qui faite la formation « as3 et mobile » chez regard.net ? si oui y a t-il un moyen de vous contacter directement au sujet de cette formation ?
Merci
Henri B
http://www.opium-bleu.com
http://www.henri-blum.com
30 mai 2011 @ 9:58
Salut HB,
« - pourquoi ne pas passer la class du movieclip à la class animatedbitmap directement, plutot que de lui passer une instance, puis faire le replace (cette instanciation puis replace ne pourrait il pas etre fait automatiquement dans votre class) ? ca me paraitrait plus simple… »
Tu pourrais écrire cela :
var abd:AnimatedBitmapData = new AnimatedBitmapData(new Dog());
var ab: = new AnimatedBitmap(abd, AnimatedBitmap.TIMER, stage.frameRate, true);
ab.scaleX = ab.scaleY = 2.2;
ab.x = stage.stageWidth * 0.5 + 100;
ab.y = stage.stageHeight * 0.5;
addChild(ab);
L’utilité de la méthode replace, c’est que si tu travailles avec Flash et des assets posés « en dur » sur la scène, tu peux faire un « replace » directement de ces assets. Si tu travailles en créant dynamiquement tes clips à la volée, pas besoin du replace.
Pour l’autre point, le fait de faire l’instanciation à l’extérieur de la classe permet de modifier les propriétés graphiques du clip (scale, rotation, filtres…) avant sa mise en cache. C’est beaucoup plus souple – à mon avis – ainsi.
« - pour des raisons d’optimisation, ne serait il pas préférable d’utiliser un grand bitmapdata pour tout l’ecran et de venir y « copier » les differents animatedbitmap a chaque image plutot que de jouer avec les coordonnées x et y des animatedbitmap ? ou est ce que ce genre de pratique n’apporte pas grand chose ? »
La méthode que tu évoques s’appelle le blitting et effectivement cela pourrait améliorer pas mal les choses pour une application de bureau. Toutefois, j’ai crée cette classe à l’origine pour du dev mobile Android / iOS, et j’ai pu me rendre compte que cette technique était plus optimisée que de créer un gros bitmapData. Je te renvoie à la conf que j’ai pu faire à ce sujet :
http://www.flashxpress.net/ressources-flash/mise-en-cache-des-assets-vectoriels-pour-air-mobile/
Par ailleurs, le fait de « jouer » avec les x et y permet simplement d’avoir exactement le même rendu qu’un clip vectoriel, ce qui nécessite de repositionner le bitmap par rapport au point d’origine.
« - est ce vous qui faite la formation « as3 et mobile » chez regard.net ? si oui y a t-il un moyen de vous contacter directement au sujet de cette formation ? »
Oui nous proposons cette formation depuis peu. Tu peux nous appeler au 01 48 24 96 45 pour + d’infos et consulter le déroulé de la formation ici :
http://www.regart.net/63-46—Formation-iPhone-iPad-Android-iPhone-iPad-Android-Creation-d-applications-en-ActionScript-3.html
J’espère avoir répondu à tes questions
30 mai 2011 @ 10:28
re,
merci pour ces reponses si rapides…
effectivmeent :
var abd:AnimatedBitmapData = new AnimatedBitmapData(new Dog());
permet de gagner une ligne… mais l’instance existe toujours ? donc si elle contient un son on l’a toujours… d’ailleurs c’est peut etre une piste d’amelioration ? si le clip contient du son, le sortir pour le mettre dans ton animatedbitmap (mais la j’ai pas trop reflechi aux consequences…je sais meme pas si c’est possible).
par contre ca m’amene une autre question du coup… moi en general je fais un sprite (ou autre elmeent graphique) dans le fla, et un fichier .as a coté pour gerer son comportement. comment garder un systeme proche avec les animatedbitmap ? c’est a dire l’animated pour le graphique, et un fichier .as pour le comportement (code) ? c’est dans le ;as que je dois creer un animatedbitmap ? et donc avoir un .as d’un nom different du nom de liaison de sa partie graphique dans le fla ? je suis pas sur d’eetre clair…
« blitting »… je ne connaissais pas le mot… c’est juste comme ca que je faisais quand je faisais des gens en assembleur (oui je suis pas tout jeune…).
pour la formation j’ai vu, mais 5 jours ca me parait impoortant… j’ai reuni une dizaine de personnes (meme profil de dev flash niveau « moyen » qui souhaitent une telle
formation « mobile »), mais je doute qu’ils s’engagent pour 5 jours… je vais voir avec regard.net directement donc…
30 mai 2011 @ 14:54
ps :
… »quand je faisais des jeux en assembleur » … et non « quand je faisais des gens en assembleur »
30 mai 2011 @ 14:56
D’un point de vue mémoire, que le clip soit crée à l’extérieur ou dans la classe, ça ne change pas grand chose, l’instance existe tant que le garbage collector n’est pas passé.
Avec l’option que j’ai choisi pour cette classe (clip crée à l’extérieur), c’est à toi de rendre éligible l’instance pour le garbage collector en passant toutes ses références à null.
Pour ton autre question, la solution consiste à faire de la composition. C’est à dire que tu feras une classe « logique » qui contient le code. Ta classe « logique » peux hériter de la classe Sprite et contenir son AnimatedBitmap via une propriété.
Par exemple (code simplifié)
public class DogLogic extends Sprite
{
// propriété pour la skin
private var _skin:AnimatedBitmap;
public function DogLogic():void
{
_skin = new AnimatedBitmap();
this.addChild(_skin);
}
}
C’est évidemment très simplifié. Dans tous les cas, cette classe n’est à utiliser que si le besoin de performances est inhérent à ton application. Dans le cas d’un jeu pour mobile, ça le sera… dans le cas d’un site flash desktop, ça le sera moins.
Enfin, le blitting est effectivement une technique héritée de la programmation des jeu « à l’ancienne ». Elle peut être complexe à mettre en place, et on perd tout les avantages liés à la displayList de Flash…
30 mai 2011 @ 15:18
Pour comprendre la technique de la composition avec les assets graphiques, je te renvoie à cet article :
http://www.flashxpress.net/ressources-flash/separation-du-design-et-du-code-dans-flash-part-3/
31 mai 2011 @ 6:50
[...] Gans (http://www.flashxpress.net) for his AnimatedBitmapData [...]
05 oct 2011 @ 23:45
Fan-Tas-Tic! Merci bien!
18 nov 2011 @ 12:55
Great work!
I have some problems. HitTestPoint with AnimatedBitmap seems doesn’t work properly, any idea how to work with it?
31 jan 2012 @ 0:37
Ok, I made _bitmap public and now I canget bitmapData of it and check hitTest. But maybe it was designed to to it in other way?
31 jan 2012 @ 3:34