php开发h5游戏教程,HTML5游戏框架cnGameJS开发实录-实现动画原理

在游戏中,游戏角色的动画效果是一个游戏必不可少的一部分。这节我们以构造超级马里奥的角色为例,讲解cnGameJS里动画的实现。

1.原理:

一个动画如果要实现一连串动作,我们可以把每个动作的快照保留起来,并放在一个大图上面,然后每次帧更新的时候,就在每个动作的快照之间循环显示,最终得出一个动画。因此我们首先要准备一个类似下面的这种图片:

c5d7c35309d08b5b8a46c1c6fa70bcb2.png

看到不?把每个动作放在图片的不同位置上,之后就可以通过改变显示位置实现动画效果了。

当cnGameJS调用start方法开始游戏后,将会调用传入的gameObj的initialize方法进行初始化,并且生成一个游戏循环,循环里每次调用gameObj的update和draw方法。因此我们可以把动画的初始化放在gameObj的initialize中,update和draw分别放在gameObj的update和draw中,实现动画播放。

效果:

4a4ea6341b3dc756de6a30d1c441b7c5.png

代码:

请使用支持canvas的浏览器查看

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);

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;

});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值