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.实现

NeoAgent
NeoAgent

销售易推出的AI‑CRM智能体平台

下载

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
苹果官方查询网站 苹果手机正品激活查询入口
苹果官方查询网站 苹果手机正品激活查询入口

苹果官方查询网站主要通过 checkcoverage.apple.com/cn/zh/ 进行,可用于查询序列号(SN)对应的保修状态、激活日期及技术支持服务。此外,查找丢失设备请使用 iCloud.com/find,购买信息与物流可访问 Apple (中国大陆) 订单状态页面。

0

2026.01.26

npd人格什么意思 npd人格有什么特征
npd人格什么意思 npd人格有什么特征

NPD(Narcissistic Personality Disorder)即自恋型人格障碍,是一种心理健康问题,特点是极度夸大自我重要性、需要过度赞美与关注,同时极度缺乏共情能力,背后常掩藏着低自尊和不安全感,影响人际关系、工作和生活,通常在青少年时期开始显现,需由专业人士诊断。

1

2026.01.26

windows安全中心怎么关闭 windows安全中心怎么执行操作
windows安全中心怎么关闭 windows安全中心怎么执行操作

关闭Windows安全中心(Windows Defender)可通过系统设置暂时关闭,或使用组策略/注册表永久关闭。最简单的方法是:进入设置 > 隐私和安全性 > Windows安全中心 > 病毒和威胁防护 > 管理设置,将实时保护等选项关闭。

0

2026.01.26

2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】
2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】

铁路12306提供起售时间查询、起售提醒、购票预填、候补购票及误购限时免费退票五项服务,并强调官方渠道唯一性与信息安全。

3

2026.01.26

个人所得税税率表2026 个人所得税率最新税率表
个人所得税税率表2026 个人所得税率最新税率表

以工资薪金所得为例,应纳税额 = 应纳税所得额 × 税率 - 速算扣除数。应纳税所得额 = 月度收入 - 5000 元 - 专项扣除 - 专项附加扣除 - 依法确定的其他扣除。假设某员工月工资 10000 元,专项扣除 1000 元,专项附加扣除 2000 元,当月应纳税所得额为 10000 - 5000 - 1000 - 2000 = 2000 元,对应税率为 3%,速算扣除数为 0,则当月应纳税额为 2000×3% = 60 元。

1

2026.01.26

oppo云服务官网登录入口 oppo云服务登录手机版
oppo云服务官网登录入口 oppo云服务登录手机版

oppo云服务https://cloud.oppo.com/可以在云端安全存储您的照片、视频、联系人、便签等重要数据。当您的手机数据意外丢失或者需要更换手机时,可以随时将这些存储在云端的数据快速恢复到手机中。

1

2026.01.26

抖币充值官方网站 抖币性价比充值链接地址
抖币充值官方网站 抖币性价比充值链接地址

网页端充值步骤:打开浏览器,输入https://www.douyin.com,登录账号;点击右上角头像,选择“钱包”;进入“充值中心”,操作和APP端一致。注意:切勿通过第三方链接、二维码充值,谨防受骗

3

2026.01.26

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

25

2026.01.26

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

76

2026.01.23

热门下载

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

精品课程

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

共102课时 | 6.8万人学习

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

共132课时 | 9.7万人学习

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

共60课时 | 3.9万人学习

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

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