【CSON原創】HTML5游戲框架cnGameJS開發實錄(動畫篇)


返回目錄 

 在游戲中,游戲角色的動畫效果是一個游戲必不可少的一部分。這節我們以構造超級馬里奧的角色為例,講解cnGameJS里動畫的實現。

1.原理:

  一個動畫如果要實現一連串動作,我們可以把每個動作的快照保留起來,並放在一個大圖上面,然后每次幀更新的時候,就在每個動作的快照之間循環顯示,最終得出一個動畫。因此我們首先要准備一個類似下面的這種圖片:

  看到不?把每個動作放在圖片的不同位置上,之后就可以通過改變顯示位置實現動畫效果了。

  當cnGameJS調用start方法開始游戲后,將會調用傳入的gameObj的initialize方法進行初始化,並且生成一個游戲循環,循環里每次調用gameObj的update和draw方法。(詳見(HTML5游戲框架cnGameJS開發實錄(資源加載篇)和 HTML5游戲框架cnGameJS開發實錄(游戲循環篇)))因此我們可以把動畫的初始化放在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;

});



 
  
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM