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

会眨眼睛的恐龙

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

aaarticlea/png;base64,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" 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);
})();
};
// ]]>

最新文章

  1. max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
  2. BZOJ1001 [BeiJing2006]狼抓兔子(平面图最小割转最短路)
  3. 浅谈html语义化标签,Html5新增语义化标签
  4. EWS小记
  5. dom 删除和清除
  6. MIT 计算机科学及编程导论 Python 笔记 1
  7. nodejs将JSON字符串转化为JSON对象
  8. (转)JAVA堆栈操作
  9. powershell 常用命令之取磁盘分区信息
  10. 本地复现Flash 0day漏洞(CVE-2018-4878)
  11. Xcode9模拟器隐藏边框
  12. JSOUP如何优秀的下载JPEG等二进制图像
  13. Springboot学习问题记录
  14. 从0移植uboot (四) _点亮调试LED
  15. Ionic 添加java原生代码 报support.v4不存在问题
  16. ActiveMQ之spring集成消息转换器MessageConverter
  17. tiny4412 启动方式
  18. vue ,v-for循环对象,不是深度克隆? 数据改变了但是页面元素没有更新。问题解决
  19. C# 开发者代码审查清单
  20. [LuoguP1438]无聊的数列(差分+线段树/树状数组)

热门文章

  1. 如何在MySQL中导入大容量SQL文件
  2. input输入框与元素间有间隙
  3. ASP.NET的最新安全漏洞Important: ASP.NET Security Vulnerability
  4. .net web api返回结果为json
  5. 什麼是 usb upstream port
  6. android的动态代码
  7. android实现多条件筛选列表菜单筛选菜单
  8. 调用Thread.interrupt()方法到底会发生什么?
  9. HDU 1269.迷宫城堡-Tarjan or 双向DFS
  10. HDU 5869.Different GCD Subarray Query-区间gcd+树状数组 (神奇的标记右移操作) (2016年ICPC大连网络赛)