Chrome自带恐龙小游戏的源码研究(五)
在上一篇《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);
})();
};
// ]]>
Chrome自带恐龙小游戏的源码研究(五)的更多相关文章
- Chrome自带恐龙小游戏的源码研究(六)
在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来 ...
- Chrome自带恐龙小游戏的源码研究(一)
目录 Chrome自带恐龙小游戏的源码研究(一)——绘制地面 Chrome自带恐龙小游戏的源码研究(二)——绘制云朵 Chrome自带恐龙小游戏的源码研究(三)——昼夜交替 Chrome自带恐龙小游戏 ...
- Chrome自带恐龙小游戏的源码研究(七)
在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较 ...
- Chrome自带恐龙小游戏的源码研究(完)
在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每 ...
- Chrome自带恐龙小游戏的源码研究(四)
在上一篇<Chrome自带恐龙小游戏的源码研究(三)>中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物. 障碍物有两种:仙人掌和翼龙.仙人掌有大小两种类型,可以同时并列多个:翼龙按高. ...
- Chrome自带恐龙小游戏的源码研究(三)
在上一篇<Chrome自带恐龙小游戏的源码研究(二)>中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替. 昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制. ...
- Chrome自带恐龙小游戏的源码研究(二)
在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: Cloud.co ...
- WinFom中经典小游戏(含源码)
最近整理了若干经典的小游戏,无聊时可以打发时间.程序本身不大,练手非常不错,主要是GDI编程,主界面地址如下图所示 源码下载方式 1,关注微信公众号:小特工作室(也可直接扫描签名处二维码) 2,发送: ...
- github下载下来的C#控制台小游戏[含源码]
早就听说了github是世界最大的源码库,但自己却不是很懂,今天去研究了下,注册了一个帐号,然后在上面搜索了一下C# game,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来 ...
随机推荐
- 修车(bzoj 1070)
Description 同一时刻有N位车主带着他们的爱车来到了汽车维修中心.维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的.现在需要安排这M位技术人员所维修的车及顺序,使 ...
- HttpHandler,HttpApplication, HttpModule
选择HttpHandler还是HttpModule? HttpHandler和HttpModule之间有什么差别 之所以有这个疑问,是因为在这二类对象中都可以访问Request, Response对象 ...
- Codeforces 920D Tanks
题目链接 题意 有 \(n\) 个容积无限的水缸,初始时水量为\(a_1,a_2,...,a_n\),有一把容积为\(k\)的勺子,可以从一个水缸中舀水倒入另一个水缸中.问能否给出操作序列,使得最终某 ...
- 安装glibc错误链接导致系统崩溃,u盘启动紧急救援模式下修复系统。
Sln 命令 创建动态符号链接 用法 sln source dest 故障案例:一个误操作 导致了一个不小的故障,输入所有命令都无效,直接系统无法启动. 故障描述 sln /usr/lib64/l ...
- hdu 2145(迪杰斯特拉)
zz's Mysterious Present Time Limit: 10000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java ...
- Cryptography I 学习笔记 --- 分组密码
1. 伪随机置换(PRF,Rseudo Random Permutaion)3DES/AES,K*X -> X(a. 可以高效计算,b. PRF函数是一个一一映射的函数,c. 存在有效的逆向算法 ...
- Linux 中/etc/profile、~/.bash_profile 环境变量配置及执行过程
环境变量是和Shell紧密相关的,用户登录系统后就启动了一个Shell.对于Linux来说一般是bash,但也可以重新设定或切换到其它的 Shell.对于UNIX,可能是CShelll.环境变量是通过 ...
- Codeforces 766E Mahmoud and a xor trip(树形DP)
题目链接 Mahmoud and a xor trip 树形DP.先考虑每个点到他本身的距离和,再算所有点两两距离和. 做的时候考虑二进制拆位即可. #include <bits/stdc++. ...
- Linux系统日常运维-修改IP地址
分享下高手写的很好的文章 IP地址.子网掩码.网络号.主机号.网络地址.主机地址 step 0: check the iptables.selinux service iptables iptable ...
- 全栈一路坑(4)——创建博客的API
上一篇博客:全站之路一路坑(3)——使用百度站长工具提交站点地图 这一篇要搭建一个API平台,一是为了给博客补充一些功能,二是为以后做APP提供数据接口. 首先需要安装Django REST Fram ...