在上一篇《Chrome自带恐龙小游戏的源码研究(四)》中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现。

会眨眼睛的恐龙

  在游戏开始前的待机界面,如果仔细观察会发现恐龙会时不时地眨眼睛。这是通过交替绘制这两个图像实现的:

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAALIAAABmCAYAAABvJctRAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAkbSURBVHhe7dBRDuuoEkXRnuOd/zjuZ79nKW2Vt7YhGGMKK5HWD8HFOfXP/3///vy8gB7+/KxGD39+VqOHaf39+zc1y7wS65SJZf7Qw7SsXCaWeSXWKRPL/KGHaVm5TCzzSqxTJpb5Qw/TsnKZWOaVWKdMLPOHHqZhZaI/f/5MxTzWITPmJ+v8JOaxDh96mAaLkJV/EvNYh8yYn6zzk5jHOnzoYRosQlb+ScxjHTJjfrLOT2Ie6/Chh2mwCFn5JzGPdciM+ck6P4l5rMOHHqbBIlY2E+a1Tpkwr3XKhHlDl2OxbBjcymXCvNYpE+a1Tpkwb+hyLJYNg1u5iD+7MxLzWqdMmNc6ZcK8ocuxWDYMbuUi/uzOSMxrnTJhXuuUCfOGLsdi2TC4lYv4szsjMa91yoR5rVMmzBu6HItlw+BWLuLP7ozEvNYpE+a1Tpkwb+hyLJYNg1u5TJjXOmXCvNYpE+YNXY7FsmFwK5cJ81qnTJjXOmXCvKHLsVg2DG7lMmFe65QJ81qnTJg3dDkWy4bBrVwmzGudMmFe65QJ84Yux2LZMLiVy4R5rVMmzGudMmHe0OVYLBsGt3KZMK91yoR5rVMmzBu6HItlw+BWLhPmtU6ZMK91yoR5Q5djsWwY3MplwrzWKRPmtU6ZMG/ociyWDYNbuUyY1zplwrzWKRPmDV2OxbJhcCuXCfNap0yY1zplwryhy7FYNgxu5TJhXuuUCfNap0yYN3Q5FsuGwa1cJsxrnTJhXuuUCfOGLsdi2TC4levB+XezTpkwr+2oB+ffLXQ5FsuGwW1ZPTj/btYpE+a1HfXg/LuFLsdi2TC4LasH59/NOmXCvLajHpx/t9DlWCwbBrdl9eD8u1mnTJjXdtSD8+8WupSLEe/fzd6MbFlPYh7rUMLvyb65k70ZWecnMY91OHE84CDi/bvZm5GVfxLzWIcSfk/2zZ3szcg6P4l5rMOJ4wEHEe/fzd6MrPyTmMc6lPB7sm/uZG9G1vlJzGMdThwPOIh4/272ZmTln8Q81qGE35N9cyd7M7LOT2Ie66D44W2DKzj3DPP0sjd6WLeI92t5bMYVnHuGeXrZGz2sW7Tfix9tasFs2BWce4Z5etkbPaxbxPu1PDbjCs49wzy97I0e1i3a78WPNrVgNuwKzj3DPL3sjR7WLeL9Wh6bcQXnnmGeXvZGD+sW7ffiR5taMBt2BeeeYZ5e9kYP6xbxfi2PzbiCc88wTy97o4d1i/Z78aNNLZgNK+H3Z/juKPZ2iXVqwXm1PDajhN+f4buj2Nsl1qnFPicO3dSC2bASfn+G745ib5dYpxacV8tjM0r4/Rm+O4q9XWKdWuxz4tBNLZgNK+H3Z/juKPZ2iXVqwXm1PDajhN+f4buj2Nsl1qnFPicO3dSC2bASfn+G745ib5dYpxacV8tjM0r4/Rm+O4q9XWKdWuxz4tCNhYt4v5XNzIR5bXktOM/ejHi/lc3MhHltZy32OXHoxh6PeL+VzcyEeW15LTjP3ox4v5XNzIR5bWct9jlx6MYej3i/lc3MhHlteS04z96MeL+VzcyEeW1nLfY5cejGHo94v5XNzIR5bXktOM/ejHi/lc3MhHltZy32OXHoxh4v4fdk38xUy8f/bXktOI/v1fB7sm9mquXj/7azFvucOHTDh2v4Pdk3M9Xy8X9bXgvO43s1/J7sm5lq+fi/7azFPicO3fDhGn5P9s1MtXz835bXgvP4Xg2/J/tmplo+/m87a7HPiUM3fLiG35N9M1MtH/+35bXgPL5Xw+/Jvpmplo//285a7HPi0A0ffhv2rbHlteA8y/Qm7FtjO2uxz4lDNxbuTdi3xpbXgvMs05uwb43trMU+Jw7dWLg3Yd8aW14LzrNMb8K+NbazFvucOHRj4d6EfWtseS04zzK9CfvW2M5a7HPi0I2FexP2teXcie9ZpjdhX9vJnfZ34qMbC/cm7GvLuRPfs0xvwr62kzvt78RHNxbuTdjXlnMnvmeZ3oR9bSd32t+Jj24s3Juwry3nTnzPMr0J+9pO7hTe8T/+Y2FXxn7sPxrft4wrYz/bwSDHAwaxsCtjP/Yfje9bxpWxn+1gkOMBg1jYlbAP+z6NeSzzStjHOj/keMBgFn4l7MO+T2Mey7wS9rHOD9HDHYNamcyY3zrOxHzWITPmt44P0cMdg1qZzJjfOs7EfNYhM+a3jg/Rwx2DWpnMmN86zsR81iEz5reOD9HDHYNamcyY3zrOxHzWITPmt44P0cNTDG7lZmI+sk6ZMK91nIn5yDo9RA9PMbiVnYn5yDplwrzWcSbmI+v0ED08xeBWdibmI+uUCfNax5mYj6zTQ/TwFINb2ZmYj6xTJsxrHWdiPrJOD9HDr7GIlR+J75NlXgn72A5G4vtkmSfRw6+xmC1jJL5Plnkl7GM7GInvk2WeRA+/xmK2jJH4PlnmlbCP7WAkvk+WeRI9/BqL2TJG4vtkmVfCPraDkfg+WeZJ9PBrVi6y5ZS0fs/7ZJlXYp0i20lJ6/e8T5Z5Ej38mpWLbDklrd/zPlnmlVinyHZS0vo975NlnkQPv2blIltOSev3vE+WeSXWKbKdlLR+z/tkmSfRw69ZuciWU9L6Pe+TZV6JdYpsJyWt3/M+WeZJ9PAyFrXlRLzfOo//8/u3YV/ug3i/dR7/5/eJ6OFlLM7FEO+3zuP//P5t2Jf7IN5vncf/+X0iengZi3MxxPut8/g/v38b9uU+iPdb5/F/fp+IHl7G4lwM8X7rPP7P79+GfbkP4v3Wefyf3yeih5exeO9ieL933urYt3cfvN87byI9vIzFexfD+73zVse+vfvg/d55E+nhZSzeuxje7523Ovbt3Qfv986bSA8vY/HexfB+77zVsW/vPni/d95EengZi3MxxPuj562O/WwHEe+PnjeRHl7GoraMiPdHz1sd+9kOIt4fPW8iPbyMRW0ZEe+Pnrc69rMdRLw/et5EengZi9oyIt4fPW917Gc7iHh/9LyJ9PAyFm01et7qrGOL0fMm0sPLrGyL0fNWZx1bjJ43kR5eZmVbjJ63OuvYYvS8ifTwMivbYvS81VnHFqPnTaSHPz+r0cOfn9Xo4c/PavTw52c1evjzs5B//v0f0zA5kqU1TugAAAAASUVORK5CYII=" alt="" width="178" height="102" />

可以通过一张图片来了解这个过程:

  为实现图片的切换,需要一个计时器timer,并且需要知道两张图片切换的时间间隔msPerFrame。当计时器timer的时间大于切换的时间间隔msPerFrame时,将图片切换到下一张,到达最后一张时又从第一张开始,如此反复。下面是实现代码:

 Trex.config = {
BLINK_TIMING:3000, //眨眼间隔
WIDTH: 44, //站立时宽度
WIDTH_DUCK: 59, //闪避时宽度
HEIGHT: 47, //站立时高度
BOTTOM_PAD: 10,
MIN_JUMP_HEIGHT: 30 //最小起跳高度
};
//状态
Trex.status = {
CRASHED: 'CRASHED', //与障碍物发生碰撞
DUCKING: 'DUCKING', //闪避
JUMPING: 'JUMPING', //跳跃
RUNNING: 'RUNNING', //跑动
WAITING: 'WAITING' //待机
};
//元数据(metadata),记录各个状态的动画帧和帧率
Trex.animFrames = {
WAITING: {//待机状态
frames: [44, 0],//动画帧x坐标在44和0之间切换,由于在雪碧图中的y坐标是0所以不用记录
msPerFrame: 1000 / 3 //一秒3帧
},
RUNNING: {
frames: [88, 132],
msPerFrame: 1000 / 12
},
CRASHED: {
frames: [220],
msPerFrame: 1000 / 60
},
JUMPING: {
frames: [0],
msPerFrame: 1000 / 60
},
DUCKING: {
frames: [262, 321],
msPerFrame: 1000 / 8
}
}; function Trex(canvas,spritePos){
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.spritePos = spritePos; //在雪碧图中的位置
this.xPos = 0; //在画布中的x坐标
this.yPos = 0; //在画布中的y坐标
this.groundYPos = 0; //初始化地面的高度
this.currentFrame = 0; //初始化动画帧
this.currentAnimFrames = []; //记录当前状态的动画帧
this.blinkDelay = 0; //眨眼延迟(随机)
this.animStartTime = 0; //动画开始的时间
this.timer = 0; //计时器
this.msPerFrame = 1000 / FPS; //默认帧率
this.config = Trex.config; //拷贝一个配置的副本方便以后使用
this.jumpVelocity = 0; //跳跃的初始速度 this.status = Trex.status.WAITING; //初始化默认状态为待机状态 //为各种状态建立标识
this.jumping = false; //角色是否处于跳跃中
this.ducking = false; //角色是否处于闪避中
this.reachedMinHeight = false; //是否到达最小跳跃高度
this.speedDrop = false; //是否加速降落
this.jumpCount = 0; //跳跃次数 this.init();
}

  首先还是和以往一样,对Trex这个构造函数进行基本的配置,然后在原型链中添加操作方法:

 Trex.prototype = {
init:function() {
this.groundYPos = DEFAULT_HEIGHT - this.config.HEIGHT - this.config.BOTTOM_PAD;
this.yPos = this.groundYPos;
//计算出最小起跳高度
this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; this.draw(0,0);
this.update(0,Trex.status.WAITING);
},
setBlinkDelay:function () {
//设置随机眨眼间隔时间
this.blinkDelay = Math.ceil(Math.random() * Trex.config.BLINK_TIMING);
},
update:function (deltaTime,opt_status) {
this.timer += deltaTime; if(opt_status) {
this.status = opt_status;
this.currentFrame = 0;
//得到对应状态的帧率 e.g. WAITING 1000ms / 3fps = 333ms/fps
this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
//对应状态的动画帧 e.g. WAITING [44,0]
this.currentAnimFrames = Trex.animFrames[opt_status].frames; if(opt_status === Trex.status.WAITING) {
//开始计y时
this.animStartTime = getTimeStamp();
//设置延时
this.setBlinkDelay();
}
} //计时器超过一帧的运行时间,切换到下一帧
if (this.timer >= this.msPerFrame) {
this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
0 : this.currentFrame + 1;
this.timer = 0; //重置计时器
} //待机状态
if(this.status === Trex.status.WAITING) {
//执行眨眼动作
this.blink(getTimeStamp());
}
},
blink:function (time) {
var deltaTime = time - this.animStartTime; if(deltaTime >= this.blinkDelay) {
this.draw(this.currentAnimFrames[this.currentFrame],0); if (this.currentFrame === 1) {//0闭眼 1睁眼
//设置新的眨眼间隔时间
this.setBlinkDelay();
this.animStartTime = time;
}
}
},
draw:function (x,y) {
var sourceX = x;
var sourceY = y;
var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
this.config.WIDTH_DUCK : this.config.WIDTH;
var sourceHeight = this.config.HEIGHT;
sourceX += this.spritePos.x;
sourceY += this.spritePos.y; this.ctx.drawImage(imgSprite,
sourceX, sourceY,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
this.config.WIDTH, this.config.HEIGHT);
}
};

  先来看update方法中的这段代码:

 if (this.timer >= this.msPerFrame) {
this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}

  这段代码实现了两个帧之间的切换,但如果只是单纯地以相同时间间隔来切换两张图片,那么得到的效果是不正确的,会出现频繁眨眼的情况。而实际情况是,闭眼只是一瞬间,睁开眼睛的时间则比较长。Chrome开发人员非常巧妙地解决了这个问题:

 blink:function (time) {
var deltaTime = time - this.animStartTime; if(deltaTime >= this.blinkDelay) {
this.draw(this.currentAnimFrames[this.currentFrame],0); if (this.currentFrame === 1) {//0闭眼 1睁眼
//设置新的眨眼间隔时间
this.setBlinkDelay();
this.animStartTime = time;
}
}
}

  只要计时器没有超过blinkDelay就不绘制新的图片,这样图片就会停留在上一次绘制的状态,恐龙此时是睁着眼睛的。当时间超过了blinkDelay,即执行眨眼的时间到了,这时会绘制this.currentFrame这一帧。如果这一帧是0(闭眼),由于之前设置了this.timer >= this.msPerFrame时会切换帧,当时间再次超过blinkDelay时,这时就会绘制帧1(睁眼),我们看到的效果就是眼睛闭上只有一瞬然后立刻睁开了。 如果当前帧是1(睁眼),重新设置blinkDelay,于是在deltaTime没有超过重新设置blinkDelay的情况下,都不会绘制新图片(始终保持在帧1(睁眼)),这样我们看到的效果就是睁眼的时间稍长。

下面是运行后的效果:

 

// this.bumpThreshold ? this.dimensions.WIDTH : 0;
},
draw:function() {
this.ctx.drawImage(imgSprite,
this.sourceXPos[0], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[0],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

this.ctx.drawImage(imgSprite,
this.sourceXPos[1], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[1],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

},
updateXPos:function(pos,increment) {
var line1 = pos,
line2 = pos === 0 ? 1 : 0;

this.xPos[line1] -= increment;
this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;

if(this.xPos[line1] = this.msPerFrame) {
this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}

//待机状态
if(this.status === Trex.status.WAITING) {
//执行眨眼动作
this.blink(getTimeStamp());
}
},
blink:function (time) {
var deltaTime = time - this.animStartTime;

if(deltaTime >= this.blinkDelay) {
this.draw(this.currentAnimFrames[this.currentFrame],0);

if (this.currentFrame === 1) {//0闭眼 1开眼
//设置新的眨眼间隔时间
this.setBlinkDelay();
this.animStartTime = time;
}
}
},
draw:function (x,y) {
var sourceX = x;
var sourceY = y;
var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
this.config.WIDTH_DUCK : this.config.WIDTH;
var sourceHeight = this.config.HEIGHT;
sourceX += this.spritePos.x;
sourceY += this.spritePos.y;

this.ctx.drawImage(imgSprite,
sourceX, sourceY,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
this.config.WIDTH, this.config.HEIGHT);
}
};

window.onload = function () {
var h = new HorizonLine(c,spriteDefinition.HORIZON);
var trex = new Trex(c,spriteDefinition.TREX);
var startTime = 0;
var deltaTime;
var speed = 3;

(function draw(time) {
time = time || 0;
deltaTime = time - startTime;
trex.update(deltaTime);
startTime = time;
requestAnimationFrame(draw,c);
})();
};
// ]]>

Chrome自带恐龙小游戏的源码研究(五)的更多相关文章

  1. Chrome自带恐龙小游戏的源码研究(六)

    在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来 ...

  2. Chrome自带恐龙小游戏的源码研究(一)

    目录 Chrome自带恐龙小游戏的源码研究(一)——绘制地面 Chrome自带恐龙小游戏的源码研究(二)——绘制云朵 Chrome自带恐龙小游戏的源码研究(三)——昼夜交替 Chrome自带恐龙小游戏 ...

  3. Chrome自带恐龙小游戏的源码研究(七)

    在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较 ...

  4. Chrome自带恐龙小游戏的源码研究(完)

    在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每 ...

  5. Chrome自带恐龙小游戏的源码研究(四)

    在上一篇<Chrome自带恐龙小游戏的源码研究(三)>中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物. 障碍物有两种:仙人掌和翼龙.仙人掌有大小两种类型,可以同时并列多个:翼龙按高. ...

  6. Chrome自带恐龙小游戏的源码研究(三)

    在上一篇<Chrome自带恐龙小游戏的源码研究(二)>中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替. 昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制. ...

  7. Chrome自带恐龙小游戏的源码研究(二)

    在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: Cloud.co ...

  8. WinFom中经典小游戏(含源码)

    最近整理了若干经典的小游戏,无聊时可以打发时间.程序本身不大,练手非常不错,主要是GDI编程,主界面地址如下图所示 源码下载方式 1,关注微信公众号:小特工作室(也可直接扫描签名处二维码) 2,发送: ...

  9. github下载下来的C#控制台小游戏[含源码]

    早就听说了github是世界最大的源码库,但自己却不是很懂,今天去研究了下,注册了一个帐号,然后在上面搜索了一下C# game,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来 ...

随机推荐

  1. 《Java性能权威指南》笔记----JIT编译器

    概览 编译型语言(C++,Fortran等):运行程序前,需要用编译器将代码静态编译成CPU可执行的汇编码.汇编码针对特定的CPU. 优点:只需编译一次,且有足够的程序信息来优化汇编码.执行速度快: ...

  2. [论文]A Link-Based Cluster Ensemble Approach for Categorical Data Clustering

    http://www.cnblogs.com/Azhu/p/4137131.html 这篇论文建议先看了上面这一遍,两篇作者是一样的,方法也一样,这一片论文与上面的不同点在于,使用的数据集是目录数据, ...

  3. DNS解析过程详解【转】

    转自:http://blog.chinaunix.net/uid-28216282-id-3757849.html 先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的网址www. ...

  4. javascript实现复选框的全选全不选

    通过复选框的id获取到复选框 元素 对复选框绑定点击事件 每个checkbox都设置相同的name checkOne 通过得到的元素获取checkbox的状态 当点击全选全不选checkbox时,检查 ...

  5. C语言联合体

    C语言联合体Unions实例代码教程 - 联合是一种特殊的数据类型在C语言中,使您可以存储不同的数据类型相同的内存位置. 联合是一种特殊的数据类型在C语言中,使您可以存储不同的数据类型相同的内存位置. ...

  6. html5手机返回按钮跳转到指定页面问题

    最近在做活动的时候有一个这样的场景,在主页面点击跳出一个弹层表单,填写完信息后,点击确认跳转到指定的展示页面了.这时候在手机端点击浏览器自带的返回按钮后,回到主页面,这时候主页面无法刷新,弹层信息还在 ...

  7. nfc是什么?nfc功能是什么?

    http://android.tgbus.com/lab/software/201208/447420.shtml nfc是什么?nfc功能是什么?出现在我们手机上的nfc功能怎么用?下面请看今天小编 ...

  8. dubbo框架的简单介绍

    以下的官网的介绍. dubbo是SOA.小例子是简单的远程调用(生产者消费者的模式出现).http://blog.csdn.net/huangyekan/article/details/4217267 ...

  9. JDBC-oracle(登陆)

    题目: 第一步:创建用户表,并插入数据(插入后记得commit) create table users ( name ), password ) ); '); '); 第二步:编写登陆界面(index ...

  10. PHP利用lua实现Redis Sorted set的zPop操作

    function zPop($key) { $script = <<<EOD local v = redis.call('zrange', KEYS[1], 0, 0); if v[ ...