zoukankan      html  css  js  c++  java
  • 【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="https://files.cnblogs.com/Cson/cnGame_v1.0.js"></script>
    <script>
    var Src="http://pic002.cnblogs.com/images/2012/273330/2012021312050269.png";
    /* 初始化 */
    cnGame.init(
    'gameCanvas',{50,height:60});
    var gameObj={
    initialize:
    function(){
    this.marie=cnGame.SpriteSheet("marie",Src,{frameSize:[50,60],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,
    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,
    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;

    });



     
      
     

  • 相关阅读:
    服务器的Redis连接不上解决方案
    给大家推荐一个很好的自学网站
    简单说下HashMap的实现原理
    LinkedList源码解析
    你要了解的jvm
    单例设计
    百度编辑器删除旧的图片
    Ueditor 单图、多图、视频、附件的上传及在线管理总结
    上传新图片删除旧图片
    webapi发布IIS时出现500.19错误:不能在此路径中使用此配置节。如果在父级别上锁定了该节,便会出现这种情况。锁定是默认设置的(overrideModeDefault="Deny")或者是通过包含overrideModeDefault="Deny"....
  • 原文地址:https://www.cnblogs.com/Cson/p/2349166.html
Copyright © 2011-2022 走看看