在作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); } }