在作canvas動畫時,精靈封裝的好壞,直接影響後續的編程體驗。編程
下文的封裝方法來自《HTML5 Canvas核心技術 圖形、動畫與遊戲開發》,實現了精靈與繪製對象的解耦,很好用,平時本身也用這種寫法。canvas
一個完整的Sprite包含兩部分:數組
Sprite的基本封裝函數
Sprite Painter實現動畫
拆開Sprite和Painter,實現瞭解耦,能夠隨意調整Painter功能。Pinter對象只須要實現: void paint(sprite, context)方法便可。這好像也叫策略模式。this
// name:精靈名字 // painter: 繪製器,需另外封裝 // behaviors: 行爲,精靈行爲 var Sprite = function(name, painter, behaviors){ this.left = 0, this.top = 0, this.width = 10, this.height = 10, this.velocityX = 0, this.velocityY = 0, this.visible = true, this.animating = false, this.painter = undefined, // object with paint(sprite, context) this.behaviors = [], // objects with execute(sprite, context, time) if(name){ this.name = name; } if(painter){ this.painter = painter; } } Sprite.prototype = { // painter屬性是一個指向Painter對象的引用,使用paint(sprite, context)方法來繪製精靈。 // behaviors屬性指向一個對象數組,數組的每一個對象都以execute(sprite, context, time)方法來對精靈進行操做 paint: function(context){ if(this.painter.paint !== undefined && this.visible){ this.painter.paint(this, context); } }, update: function(context, time){ for(var i = this.behaviors.length; i > 0; --i){ this.behaviors[i-1].execute(this, context, time); } } }
behavior一開始有些難理解。書上的解釋是:精靈的行爲。prototype
什麼叫行爲呢?code
我的以爲,改變了Sprite基本屬性的動做,都叫精靈的行爲,改變了top、width、velocityX等balabala的都叫行爲。對象
behavior裏面會實現一個excute方法 void excute(sprite, context, time){}。在方法裏面會修改各種的值。到Pianter繪製的時候,會實現修改後的效果。也就實現了精靈的多種行爲。遊戲
即提供給Sprite對象的Painter對象,和Sprite解耦。
目前Painter對象分爲三類:
<li>描邊及填充繪製器</li> <li>圖像繪製器</li> <li>精靈圖繪製器</li>
描邊繪製器能夠隨意控制,只要實現了 void paint(sprite, context)就能夠了。
1.圖像繪製器
var ImagePainter = function(imgUrl){ this.image = new Image(); this.image.src = imgUrl; } ImagePainter.prototype = { paint: function(sprite, context){ if(this.image.complete){ context.drawImage(this.image, sprite.left, sprite.top, sprite.width, sprite.height); } else{ this.iamge.onload = function(e){ context.drawImage(this.image, sprite.left, sprite.top, sprite.width, sprite.height); } } } }
2.精靈繪製器
var SpriteSheetPainter = function(cells){ this.cells = cells; }; SpriteSheetPainter.prototype = { cells: [], cellIndex: 0, advance: function(){ if(this.cellInde === this.cells.length -1 ){ this.cellIndex = 0; } else{ this.cellIndex++; } }, paint: function(sprite, context){ var cell = this.cells[this.cellIndex]; context.drawImage(spritesheet, cell.x, cell.y, cell.w, cell.h, sprite.left, sprite.top, cell.w, cell.h); } }
SpriteAnimator包含兩個參數,Painter數組和回調函數。
var SpriteAnimator = function(painters, elapsedCallback){ this.painters = painters; if(elapsedCallback){ this.elapsedCallback = elapsedCallback; } }; SpriteAnimator.prototype = { painters: [], duration: 1000, startTime: 0, index: 0, elapsedCallback: undefined, end: function(sprite, originalPainter){ sprite.animating = false; if(this.elapsedCallback){ this.elapsedCallback(sprite); } else{ sprite.painter = originalPainter; } }, start: function(sprite, duration){ var endTime = +new Date() + duration, period = duration / (this.painters.length), interval = undefined, animator = this, // for setInterval() function originalPainter = sprite.painter; this.index = 0; sprite.animating = true; sprite.painter = this.painter[this.index]; interval = setInterval(){ if(+new Date() < endTime){ sprite.painter = animator.painters[++animator.index]; } else{ animator.end(sprite, originalPainter); clearIntercal(interval); } }, period); } }