在游戏中,游戏角色的动画效果是一个游戏必不可少的一部分。这节我们以构造超级马里奥的角色为例,讲解cnGameJS里动画的实现。
1.原理:
一个动画如果要实现一连串动作,我们可以把每个动作的快照保留起来,并放在一个大图上面,然后每次帧更新的时候,就在每个动作的快照之间循环显示,最终得出一个动画。因此我们首先要准备一个类似下面的这种图片:
看到不?把每个动作放在图片的不同位置上,之后就可以通过改变显示位置实现动画效果了。
当cnGameJS调用start方法开始游戏后,将会调用传入的gameObj的initialize方法进行初始化,并且生成一个游戏循环,循环里每次调用gameObj的update和draw方法。因此我们可以把动画的初始化放在gameObj的initialize中,update和draw分别放在gameObj的update和draw中,实现动画播放。
效果:
代码:
<body> <canvas id="gameCanvas">请使用支持canvas的浏览器查看</canvas> </body> <script src="http://files.cnblogs.com/Cson/cnGame_v1.0.js"></script> <script> var Src="http://pic002.cnblogs.com/images/2012/273330/2012021312050269.png"; /* 初始化 */ cnGame.init('gameCanvas',{width:50,height:60}); var gameObj={ initialize:function(){ this.marie=cnGame.SpriteSheet("marie",Src,{frameSize:[50,60],width:150,height:60,loop:true}); }, update:function(){ this.marie.update(); }, draw:function(){ this.marie.draw(); } } cnGame.loader.start([Src],gameObj); </script>
2.实现
正如上面看到的,我们只需要用很少的代码量,就可以实现一个帧动画的播放,接下来将介绍cnGameJS里的帧动画是怎样封装的。
大家很容易可以发现,cnGameJS都遵循一个特定的模式,把对象的阶段分为三个:initialize(初始化),update(帧更新)和draw(绘制)。这样我们可以很方便地把不同功能的代码写在对应的阶段内。spriteSheet帧动画也不例外,同样按照这种模式来写。
初始化:用户对一些必要的信息进行设定。
spriteSheet.prototype={ /** *初始化 **/ init:function(id,src,options){ /** *默认对象 **/ var defaultObj={ x:0, y:0, width:120, height:40, frameSize:[40,40], frameDuration:100, direction:"right", //从左到右 beginX:0, beginY:0, loop:false, bounce:false }; options=options||{}; options=cg.core.extend(defaultObj,options); this.id=id; //spriteSheet的id this.src=src; //图片地址 this.x=options.x; //动画X位置 this.y=options.y; //动画Y位置 this.width=options.width; //图片的宽度 this.height=options.height; //图片的高度 this.image=cg.loader.loadedImgs[this.src]; //图片对象 this.frameSize=options.frameSize; //每帧尺寸 this.frameDuration=options.frameDuration; //每帧持续时间 this.direction=options.direction; //读取帧的方向(从做到右或从上到下) this.currentIndex=0; //目前帧索引 this.beginX=options.beginX; //截取图片的起始位置X this.beginY=options.beginY; //截图图片的起始位置Y this.loop=options.loop; //是否循环播放 this.bounce=options.bounce; //是否往返播放 this.onFinsh=options.onFinsh; //播放完毕后的回调函数 this.frames=caculateFrames(options); //帧信息集合 this.now=new Date().getTime(); //当前时间 this.last=new Date().getTime(); //上一帧开始时间 },
上面的参数比较多,都是一些对帧动画属性的预设置。需要注意的是我们调用了私有方法caculateFrames来计算每个帧的信息,并保存到frames内,为帧绘制做准备。
帧更新:
在每一帧的更新过程中,我们首先获取当前时间作为帧的开始时间,并且和上一次帧的开始时间相减,就得出上一次帧的用时。如果用时超过之前设置的每帧的用时,则可以进行帧更新。然后判断是否循环或者往返播放动画,按情况更新对应的帧索引。在最终确定帧的索引后,就可以从frames数组中获取该帧的信息,并返回。
/** *更新帧 **/ update:function(){ this.now=new Date().getTime(); var frames=this.frames; if((this.now-this.last)>this.frameDuration){//如果间隔大于帧间间隔,则update var currentIndex=this.currentIndex; var length=this.frames.length; this.last=this.now; if(currentIndex>=length-1){ if(this.loop){ //循环 return frames[this.currentIndex=0]; } else if(!this.bounce){//没有循环并且没有往返滚动,则停止在最后一帧 this.onFinsh&&this.onFinsh(); this.onFinsh=undefined; return frames[currentIndex]; } } if((this.bounce)&&((currentIndex>=length-1&&path>0)||(currentIndex<=0&&path<0))){ //往返 path*=(-1); } this.currentIndex+=path; } return frames[this.currentIndex]; },
帧绘制:
在帧更新后,已经获取到当前帧的索引,因此draw方法就可以从保存所有帧信息的frames获取到当前帧的信息(包括图像截取的起始位置等),从而在指定位置截取大图片,并画出该图片区域的图像:
/** *在特定位置绘制该帧 **/ draw:function(){ var currentFrame=this.getCurrentFrame(); var width=this.frameSize[0]; var height=this.frameSize[1]; cg.context.drawImage(this.image,currentFrame.x,currentFrame.y,width,height,this.x,this.y,width,height); }
最后,还提供跳到特定帧等方法。
动画模块所有源码:
/** *包含多帧图像的大图片 **/ spriteSheet=function(id,src,options){ if(!(this instanceof arguments.callee)){ return new arguments.callee(id,src,options); } this.init(id,src,options); } spriteSheet.prototype={ /** *初始化 **/ init:function(id,src,options){ /** *默认对象 **/ var defaultObj={ x:0, y:0, width:120, height:40, frameSize:[40,40], frameDuration:100, direction:"right", //从左到右 beginX:0, beginY:0, loop:false, bounce:false }; options=options||{}; options=cg.core.extend(defaultObj,options); this.id=id; //spriteSheet的id this.src=src; //图片地址 this.x=options.x; //动画X位置 this.y=options.y; //动画Y位置 this.width=options.width; //图片的宽度 this.height=options.height; //图片的高度 this.image=cg.loader.loadedImgs[this.src]; //图片对象 this.frameSize=options.frameSize; //每帧尺寸 this.frameDuration=options.frameDuration; //每帧持续时间 this.direction=options.direction; //读取帧的方向(从做到右或从上到下) this.currentIndex=0; //目前帧索引 this.beginX=options.beginX; //截取图片的起始位置X this.beginY=options.beginY; //截图图片的起始位置Y this.loop=options.loop; //是否循环播放 this.bounce=options.bounce; //是否往返播放 this.onFinsh=options.onFinsh; //播放完毕后的回调函数 this.frames=caculateFrames(options); //帧信息集合 this.now=new Date().getTime(); //当前时间 this.last=new Date().getTime(); //上一帧开始时间 }, /** *更新帧 **/ update:function(){ this.now=new Date().getTime(); var frames=this.frames; if((this.now-this.last)>this.frameDuration){//如果间隔大于帧间间隔,则update var currentIndex=this.currentIndex; var length=this.frames.length; this.last=this.now; if(currentIndex>=length-1){ if(this.loop){ //循环 return frames[this.currentIndex=0]; } else if(!this.bounce){//没有循环并且没有往返滚动,则停止在最后一帧 this.onFinsh&&this.onFinsh(); this.onFinsh=undefined; return frames[currentIndex]; } } if((this.bounce)&&((currentIndex>=length-1&&path>0)||(currentIndex<=0&&path<0))){ //往返 path*=(-1); } this.currentIndex+=path; } return frames[this.currentIndex]; }, /** *跳到特定帧 **/ index:function(index){ this.currentIndex=index; return this.frames[this.currentIndex]; }, /** *获取现时帧 **/ getCurrentFrame:function(){ return this.frames[this.currentIndex]; }, /** *在特定位置绘制该帧 **/ draw:function(){ var currentFrame=this.getCurrentFrame(); var width=this.frameSize[0]; var height=this.frameSize[1]; cg.context.drawImage(this.image,currentFrame.x,currentFrame.y,width,height,this.x,this.y,width,height); } } this.SpriteSheet=spriteSheet; });
以上がアニメーション原理を実現するHTML5ゲームフレームワークcnGameJSの開発実績の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。