Small codes

HTML5, Javascript and AS3

Découverte de EaselJS : POO et création d’objets complexes

| 12 Commentaires

EaselJS permet donc de retrouver dans le HTML5 canvas certains réflexes et habitudes de travail pour des développeurs venant de l’actionscript, avec un objet Stage, une liste d’affichage, et des objets qui s’imbriquent les uns dans les autres.
Pour des développeurs qui découvrent ces principes, ils auront peut-être commencé à apercevoir les grandes possibilités et la flexibilité qu’ils peuvent apporter.

Mais il manque quelque chose d’essentiel : la Programmation Orientée Objet (POO), avec l’héritage, les classes personnalisées, et ses avantages en terme de maintenance et de réutilisation du code.
Attendez un peu… Dans mon post précédent, j’ai parlé du fait que tous les objets d’affichage que j’avais décrits héritaient tous de la classe DisplayObject.
Du coup, ne pourrions-nous pas étendre des classes comme Container, pour créer des objets complexes et réutilisables ?
La réponse est oui ! C’est ce que je vais vous présenter dans ce post.

Étendre la classe Container

L’idée est de créer un nouvel objet d’affichage, une icone personnalisable que nous pourrons réutiliser dans différents types de menus sans jamais retoucher au code.
Nous allons créer une classe Icon, qui va hériter de la classe Container. Cette classe prendra en paramètre une taille en pixels, un chemin vers une image, une couleur, et un texte. Nous l’utiliserons de cette façon :

var myIcon = new Icon(50,pathToMyPicture,"#fff","Home");
stage.addChild(icon);

La classe héritera de l’ensemble des propriétés et des fonctions de la classe Container, et donc de la classe DisplayObject.

Si on regarde d’un peu plus près la classe Container, ce qui est possible ici, nous pourrons voir plusieurs choses :

  • Tout est annoté, y compris au niveau de la structure de la classe. Nous allons pouvoir nous inspirer de cette structure pour créer notre propre classe
  • Container hérite de DisplayObject
  • Une fonction initialize() est présente : elle est importante car nous allons devoir exécuter cette méthode dans notre classe enfant
  • Une fonction _tick() est présente : cette méthode de type protected est appelée par le Ticker
  • Bien sur, toutes les autres méthodes de la classe Container sont lisibles est annotées;

La structure de la classe

Ici, nous n’allons rien inventer. La structure de notre classe doit être identique à celle de la classe Container(). Nous allons juste changer son nom, et changer les références vers DisplayObject en références vers Container;
Voici ce que cela donne :

(function (window) {
	function Icon() {
        this.initialize();
    }
	//Héritage de la classe container() 
	Icon.prototype = new Container(); 
	Icon.prototype.Container_initialize = Icon.prototype.initialize;
	Icon.prototype.Container_tick = Icon.prototype._tick; 
    
    Icon.prototype.initialize = function () {
                //appel à la méthode initialize de la classe parente
		this.Container_initialize();
               console.log("Icon initialized");
	}
     Icon.prototype._tick = function () {
	       //Appel à la méthode tick() de la classe parente
               this.Container_tick();
               console.log("Icon Ticked");
    }
    window.Icon= Icon;
} (window));

Sauvegardez sous le nom Icon.js dans le dossier /js de notre projet, juste à côté du fichier main.js.
Vous pouvez le garder aussi comme modèle : nos classes d’affichage personnalisées seront toujours construites selon cette architecture.

Il faut aussi inclure ce nouveau fichier dans notre page index.html, en ajoutant son importation avec les autres juste en dessous du footer:

	<script src="js/libs/easel.js"></script>
	<script src="js/main.js"></script>
	<script src="js/Icon.js"></script> 

Nous pouvons tester notre nouvelle classe en modifiant notre fichier main.js ainsi :

(function(){
var stage;
var myIcon;
var myCanvas = $("#stageCanvas").get(0);

this.init = function() {
	
	stage = new Stage(myCanvas);
	myIcon = new Icon();
	stage.addChild(myIcon);
	stage.update();
    
        Ticker.setFPS(24);
        Ticker.addListener(this);
}
this.tick = function(){
       stage.tick();
}
})();

Vous devriez voir les messages « Icon initialized » s’afficher une fois et « Icon Ticked » de multiples fois dans la console de votre navigateur.
C’est le signe que l’objet Icon a bien été instancié, et qu’il réagit bien à l’évènement tick().

Ajout de propriétés

Nous avons donc une classe Icon, qui est une copie conforme de la classe Container(). Nous pourrions utiliser l’une ou l’autre pour faire les mêmes choses. Ce serait transparent.
Mais ce n’est pas ce qui nous intéresse : nous souhaitons ajouter des propriétés et des méthodes spécifiques à notre classe enfant.

Nous allons donc créer les paramètres dont nous avons parlé : une taille en pixels, un chemin vers une image, une couleur, et un texte.
Voici comment procéder :

(function (window) {
	//La classe reçoit désormais des paramètres, qui sont transmis à la methode intialize (le constructeur).
        function Icon(iconWidth,imgSrc,color,titleText)  {
        this.initialize(iconWidth,imgSrc,color,titleText);
    }
	
	Icon.prototype = new Container();
	Icon.prototype.Container_initialize = Icon.prototype.initialize;
	Icon.prototype.Container_tick = Icon.prototype._tick; 
    
    //La methode initialize sert donc à enregistrer les variables de classe avec les paramètres passés
    Icon.prototype.initialize = function (iconWidth,imgSrc,color,titleText) {
		this.Container_initialize();
		this._iconWidth = iconWidth; //Taille de l'icône
		this._imgSrc = imgSrc; //chemin vers l'image
		this._color = color;  //Couleur de fond et de texte
		this._titleText = titleText;    //Texte de l'icône
        console.log("Icon initialized : " + this._iconWidth+" - "+this._imgSrc+" - "+this._color+" - "+this._titleText);
	}
	
     Icon.prototype._tick = function () {
		this.Container_tick();
               console.log("Icon Ticked");
    }
    window.Icon= Icon;
} (window));

Il ne nous reste plus qu’à mettre ces variables à profit : nous allons faire exactement la même icone que nous avions créé dans le post précédent, mais nous allons ajouter les différents éléments dans notre classe Icon() au lieu du banal Container().

(function (window) {
	function Icon(iconWidth,imgSrc,color,titleText)  {
        this.initialize(iconWidth,imgSrc,color,titleText);
    }
	
	Icon.prototype = new Container();
	Icon.prototype.Container_initialize = Icon.prototype.initialize;
	Icon.prototype.Container_tick = Icon.prototype._tick; 
    
    Icon.prototype.initialize = function (iconWidth,imgSrc,color,titleText) {
		this.Container_initialize();
		this._iconWidth = iconWidth;
		this._imgSrc = imgSrc;
		this._color = color;
		this._titleText = titleText;
        console.log("Icon initialized : " + this._iconWidth+" - "+this._imgSrc+" - "+this._color+" - "+this._titleText);
		
		this.graphic = new Graphics();
		this.graphic .beginFill(this._color);
		this.graphic.drawRoundRect(0,0,this._iconWidth,this._iconWidth,this._iconWidth*.1);
		this.roundRectangle= new Shape(this.graphic);
		this.addChild(this.roundRectangle);
		
		this.text = new Text(this._titleText, "bold 16px Courier",this._color);
		this.text.textAlign ="center";
		this.text.y = this._iconWidth+15;
		this.text.x = this._iconWidth*0.5;
		this.addChild(this.text);
			
		this.bitmap = new Bitmap(this._imgSrc);
		this.bitmap.x = 2;//this._iconWidth*0.5;
		this.bitmap.y = 6;//this._iconWidth*0.5;
		this.addChild(this.bitmap);	
	}
	
     Icon.prototype._tick = function () {
		this.Container_tick();
    }
    window.Icon= Icon;
} (window));

Nous avons maintenant un objet Icon() réutilisable, personnalisable, et pratique.
La voici !

Mais il pourrait être encore mieux, non ? 
Rajoutons des comportements ! 

Ajout des comportements

Notre classe Icon() hérite de toutes les caractéristiques de la classe container(), qui elle-même hérite de la classe DisplayObject(), et de ses évènements : onClick, onDoubleClick, onMouseOut, onMouseOver, onPress et tick.

Nous pouvons donc ajouter la gestion de certains de ces évenements à notre classe Icon() :

(function (window) {
	function Icon(iconWidth,imgSrc,color,titleText)  {
        this.initialize(iconWidth,imgSrc,color,titleText);
    }
	
	Icon.prototype = new Container();
	Icon.prototype.Container_initialize = Icon.prototype.initialize;
	Icon.prototype.Container_tick = Icon.prototype._tick; 
	
    Icon.prototype.initialize = function (iconWidth,imgSrc,color,titleText) {
		this.Container_initialize();
		this._iconWidth = iconWidth;
		this._imgSrc = imgSrc;
		this._color = color;
		this._titleText = titleText;
        console.log("Icon initialized : " + this._iconWidth+" - "+this._imgSrc+" - "+this._color+" - "+this._titleText);
	
		this.graphic = new Graphics();
		this.graphic.beginFill("#777");
		this.graphic.drawRoundRect(0,0,this._iconWidth,this._iconWidth,this._iconWidth*.1);
		this.roundRectangle= new Shape(this.graphic);
		this.addChild(this.roundRectangle);
			
		this.text = new Text(this._titleText, "bold 16px Courier",this._color);
		this.text.textAlign ="center";
		this.text.y = this._iconWidth+15;
		this.text.x = (this._iconWidth*0.5);
		this.addChild(this.text);
			
		this.bitmap = new Bitmap(this._imgSrc);
		console.log(this.bitmap.image.width+"-"+this._iconWidth);
		
		this.bitmap.regX = this._iconWidth*0.5;
		this.bitmap.regY = this._iconWidth*0.5;
		this.bitmap.x =  (this._iconWidth*0.5);
		this.bitmap.y =  (this._iconWidth*0.5);
		this.addChild(this.bitmap);
		
		this.roundRectangle.onClick = function(){
			console.log("click "+this.graphics);
			
		}
		this.roundRectangle.onMouseOver = function(){
			console.log("mouseOver ");
			this.parent.graphic.clear();
			this.parent.graphic.beginFill(this.parent._color);
			this.parent.graphic.beginStroke(Graphics.getRGB(255,255,255));
			this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
			this.parent.roundRectangle.graphics = this.parent.graphic;
		}
		this.roundRectangle.onMouseOut = function(){
			console.log("mouseOut ");
			this.parent.graphic.clear();
			this.parent.graphic.beginFill("#777");
			this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
			this.parent.roundRectangle.graphics = this.parent.graphic;
		}
	}
	
    Icon.prototype._tick = function () {
		this.Container_tick();
        //console.log("Icon Ticked");
    }
    window.Icon= Icon;
} (window));
 

Important : pour que les évènements onMouseOver et onMouseOut soient pris en compte, il est nécessaire d’activer leur prise en charge au niveau du Stage.
Pour cela, il suffit de rajouter stage.enableMouseOver(); dans le fichier main.js :

(function(){
var stage;
var myIcon;
var myImage;
var myCanvas = $("#stageCanvas").get(0);

this.init = function() {
	
	stage = new Stage(myCanvas);
    myIcon = new Icon(60,"img/car.png","#fff","Title");
	myIcon.x = 400;
	myIcon.y = 300;
	
	stage.addChild(myIcon);
	stage.update();
    stage.enableMouseOver();
	
	Ticker.setFPS(24);
    Ticker.addListener(this);
}
this.tick = function(){
       stage.tick();
}
})();
 

Voilà le résultat.

Et à partir de là ?

A partir de là, imaginez.
Ce concept orienté objet va vous permettre de créer de nombreux objets que vous pourrez réutiliser. Vous pourrez rassembler ces nouvelles classes en paquets, ou améliorer notre icone en lui donnant des paramètres supplémentaires : un lien, une option de forme, la possibilité pour les images trop grandes de s’adapter automatiquement à la taille ?

Vous pourriez changer ce vaisseau très moche que j’utilise dans mes exemples ? (Vous devriez… Vraiment.)

Vous pouvez aussi désormais schématiser correctement en UML un composant, ou même une application qui utiliserai EaselJS (Bien que la bibliothèque soit encore un peu jeune pour une utilisation aussi avancée, à mon avis) .

Vous pouvez aussi étendre d’autres classes comme SpriteSheet, ou créer d’autres objets héritant de Container() pour faire un menu animé, par exemple.
C’est ce que nous ferons dans le prochain post, où je vous montrerai deux exemples développés avec EaseJS.

12 Commentaires

  1. hello,
    couldnot understand this two line. will u please help me understand.
    Icon.prototype.Container_initialize = Icon.prototype.initialize;
    Icon.prototype.Container_tick = Icon.prototype._tick;

    • This line create a reference to the initialize property from the parent class of the Icon class : which is the Container class from EaselJS library:

      Icon.prototype.Container_initialize = Icon.prototype.initialize;

      So in the Icon class initialize method, we can call the initialize method from the parent, and override it.

      It the same story for the Tick method.

  2. Hi I looked at your method above, and I’m not a Js guy or easel guy, so I might be wrong..but I looked at the platypus game as well and came up with this wrapper for inheritance which seems to work..example below

    var p; // shortcut to reference prototypes
    (SimpleButton=function(entry){
    this.initialize(entry);
    this.texter=new createjs.Text(entry, »12px Arial », « #FFF »);
    this.grp= new createjs.Shape();
    this.copy=entry;
    this.grp.graphics.beginFill(‘#171515′).drawRect(0, 0,120,30).endFill();
    this.grp.alpha1;
    this.addChild(this.grp);
    this.addChild(this.texter);
    this.cache=true;
    }).prototype = p = new createjs.Container();

    • This piece of code is what you obtains when you export an animation from Flash IDE with de Toolkit for CreateJS tool.

      Yes it works.
      But… What if you want to inherit from your simple button ?
      How would you do that ?

      This is why I think you have to stick with the EaselJS class architecture.

  3. Avec la version 0.5.0, ton exemple ne fonctionne plus.

    Un message d’erreur signal dans l’objet Icon que Container() n’existe pas.

    Où alors j’ai merdé quelque chose ^^

    • La version 5.0 de EaselJS a été placée dans le namespace « createjs ».
      Pour instancier un container, il faut donc écrire :
      var container = new createjs.Container();

      Je vais dans les prochains jours faire un update de toutes les entrées du blog sur CreateJS pour que les exemples fonctionnement avec la version 5.0

  4. Hi Fred,
    I’m fairly new to js, and your tutorials have been very helpful.

    Are these two lines:

    Icon.prototype.Container_initialize = Icon.prototype.initialize;
    Icon.prototype.Container_tick = Icon.prototype._tick;

    Basically a work around to calling super()?

  5. Pingback : EaselJS : 継承 | mikelito

  6. Hi jack,

    I’m sorry but Im new to easel or JavaScript for that matter.
    What I don’t understand is how does the ticker works? Does each object inherit a ticker?

    From what I understand ticker is kind off like the update function for easeljs? So does each object have their own updates?

    Also the ticker inherits the event dispatcher function addEventListener, how does that work?

  7. Hi Fred,

    I’m sorry but Im new to easel or JavaScript for that matter.
    What I don’t understand is how does the ticker works? Does each object inherit a ticker?

    From what I understand ticker is kind off like the update function for easeljs? So does each object have their own updates?

    Also the ticker inherits the event dispatcher function addEventListener, how does that work?

  8. Hi Fred,

    I’m new to easeljs so pardon my lack of knowledge of the framework

    I don’t understand how the ticker object, I know it works as kind of an…update for objects in easel? But is it a static object or does every object has their own update? If so how is the update of objects managed??

    The second thing I cannot grasp is ticker inherits addEventListener which is inherited from event dispatcher, how does that function works?

Laisser un commentaire

Champs Requis *.


Social Widgets powered by AB-WebLog.com.