0

0

HTML5游戏框架cnGameJS开发实录-实现动画原理

黄舟

黄舟

发布时间:2017-03-24 16:08:51

|

2720人浏览过

|

来源于php中文网

原创

 

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

1.原理:

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

立即学习前端免费学习笔记(深入)”;

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

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

效果:

 1278.png

代码:


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


2.实现

Replit Agent
Replit Agent

Replit最新推出的AI编程工具,可以帮助用户从零开始自动构建应用程序。

下载

  正如上面看到的,我们只需要用很少的代码量,就可以实现一个帧动画的播放,接下来将介绍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;
                                        
});

相关文章

HTML速学教程(入门课程)
HTML速学教程(入门课程)

HTML怎么学习?HTML怎么入门?HTML在哪学?HTML怎么学才快?不用担心,这里为大家提供了HTML速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

72

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

132

2026.01.16

C++ 单元测试与代码质量保障
C++ 单元测试与代码质量保障

本专题系统讲解 C++ 在单元测试与代码质量保障方面的实战方法,包括测试驱动开发理念、Google Test/Google Mock 的使用、测试用例设计、边界条件验证、持续集成中的自动化测试流程,以及常见代码质量问题的发现与修复。通过工程化示例,帮助开发者建立 可测试、可维护、高质量的 C++ 项目体系。

54

2026.01.16

java数据库连接教程大全
java数据库连接教程大全

本专题整合了java数据库连接相关教程,阅读专题下面的文章了解更多详细内容。

39

2026.01.15

Java音频处理教程汇总
Java音频处理教程汇总

本专题整合了java音频处理教程大全,阅读专题下面的文章了解更多详细内容。

19

2026.01.15

windows查看wifi密码教程大全
windows查看wifi密码教程大全

本专题整合了windows查看wifi密码教程大全,阅读专题下面的文章了解更多详细内容。

85

2026.01.15

浏览器缓存清理方法汇总
浏览器缓存清理方法汇总

本专题整合了浏览器缓存清理教程汇总,阅读专题下面的文章了解更多详细内容。

43

2026.01.15

ps图片相关教程汇总
ps图片相关教程汇总

本专题整合了ps图片设置相关教程合集,阅读专题下面的文章了解更多详细内容。

11

2026.01.15

ppt一键生成相关合集
ppt一键生成相关合集

本专题整合了ppt一键生成相关教程汇总,阅读专题下面的的文章了解更多详细内容。

49

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.7万人学习

HTML+CSS基础与实战
HTML+CSS基础与实战

共132课时 | 9.6万人学习

前端开发(基础+实战项目合集)
前端开发(基础+实战项目合集)

共60课时 | 3.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号