Chrome自带恐龙小游戏的源码研究(六)
在上一篇《Chrome自带恐龙小游戏的源码研究(五)》中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃。
恐龙的跳跃
游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃。先用一张图来表示整个跳跃的过程:

- 首先规定向下为正方向,即重力加速度(g)为正,起跳的速度(v)为负,恐龙距离画布上方的距离为yPos;
- 每一帧动画中,速度都会与重力加速度相加得到新的速度,再用新的速度与yPos相加得到新的yPos,改变恐龙的位置为新的yPos,表现出来为yPos不断减小;
- 当恐龙升至最高点,此时速度为0,并且仍具有向下的重力加速度。
- 速度仍与重力加速度相加得到新的速度,此时速度方向向下,为正值,表现为yPos逐渐增加;
- 落地,并使yPos不超过地面的高度,将速度重置为0,更新状态jumping为false。
下面通过代码来实现。首先注册键盘事件:
document.addEventListener('keydown',onKeyDown);
document.addEventListener('keyup',onKeyUp);
function onKeyDown(e) {
if(keycode.JUMP[e.keyCode]) {
if(!trex.jumping) {
trex.startJump(6);
}
}
}
按下跳跃键后,执行startJump方法:
startJump: function(speed) {
if (!this.jumping) {
//切换到jump状态
this.update(0, Trex.status.JUMPING);
//设置跳跃速度
this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
this.jumping = true;
this.reachedMinHeight = false;
}
}
之后在每次GameLoop中更新状态:
if (trex.jumping) {
ctx.clearRect(0, 0, 600, 150);
trex.updateJump(deltaTime);
}
updateJump: function(deltaTime) {
//帧切换速率
var msPerFrame = Trex.animFrames[this.status].msPerFrame;
//经过的帧数
var framesElapsed = deltaTime / msPerFrame;
//更新y轴坐标
this.yPos += Math.round(this.jumpVelocity * framesElapsed);
//由于速度受重力影响,需要对速度进行修正
this.jumpVelocity += this.config.GRAVITY * framesElapsed;
//达到最小跳跃高度
if (this.yPos < this.minJumpHeight) {
this.reachedMinHeight = true;
}
//达到最大高度后停止跳跃
if (this.yPos < this.config.MAX_JUMP_HEIGHT) {
this.endJump();
}
if (this.yPos > this.groundYPos) {
this.reset();
this.jumpCount++;
}
this.update(deltaTime);
},
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) {
//开始计时
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());
} else {
this.draw(this.currentAnimFrames[this.currentFrame], 0);
}
}
这样就实现了跳跃的过程。
轻跳
如果持续按住Spacebar或者Up不放,跳跃总是能达到最大高度的,但很多情况下我们只是轻轻敲击一下键盘然后就放手了,这时的游戏表现为恐龙只跳起一个很低的高度,然后开始下落,一般称之为“轻跳”、“小跳”。这看起来是根据按键时长来决定跳跃高度,实现起来有一定的难度,但实际情况却比较简单,只监听键盘的onkeyup事件即可。
function onKeyUp(e) {
if (keycode.JUMP[e.keyCode]) {
trex.endJump();
}
}
当键盘抬起时,执行endJump方法,而endJump方法也十分简单:
endJump: function() {
if (this.reachedMinHeight && this.jumpVelocity < this.config.DROP_VELOCITY) {
this.jumpVelocity = this.config.DROP_VELOCITY;
}
}
首先要判断是否达到了最小跳跃高度,this.reachedMinHeight这个变量非常有用,它避免了游戏角色只跳起数像素然后落地这样的无意义跳跃。此时如果向上的速度仍比较大的话,则强制减小为this.config.DROP_VELOCITY以便能够更快地下落。
下图分别是“大跳”和“小跳”的区别:

快速落地
在跳跃过程中如果按下了Down键,恐龙会加速下降。
function onKeyDown(e) {
//......
if(keycode.DUCK[e.keyCode]) {//Down
if(trex.jumping) {
trex.setSpeedDrop(); //加速下降
}
}
}
松开键位时取消加速:
function onKeyUp(e) {
//......
if (keycode.DUCK[e.keyCode]) {
trex.speedDrop = false;
}
}
在构造函数中添加setSpeedDrop方法:
setSpeedDrop: function() {
this.speedDrop = true;
this.jumpVelocity = 1; //将速度设置为1,正方向(向下为正方向)
}
还需要对updateJump方法做一些更新:
updateJump:function (deltaTime) {
//......
//更新y轴坐标
if (this.speedDrop) {
//SPEED_DROP_COEFFICIENT为加速倍数,初始设定为3
this.yPos += Math.round(this.jumpVelocity * this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
} else {
this.yPos += Math.round(this.jumpVelocity * framesElapsed);
}
//达到最小跳跃高度
//speedDrop也能触发reachedMinHeight
if (this.yPos < this.minJumpHeight || this.speedDrop) {
this.reachedMinHeight = true;
}
//达到最大高度后停止跳跃
//speedDrop也能触发endJump
if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
this.endJump();
}
//......
}
效果如下图所示,在跳跃过程中按住Down,可以发现下落速度比平时快:

闪避
在地面上按住Down键,恐龙会进入闪避状态。首先还是从keydown方法入手:
if (keycode.DUCK[e.keyCode]) {
e.preventDefault();
if (trex.jumping) {
trex.setSpeedDrop();
} else if (!trex.jumping && !trex.ducking) {
trex.setDuck(true); //闪避
}
}
keyup方法取消闪避:
function onKeyUp(e) {
if (keycode.JUMP[e.keyCode]) {
trex.endJump();
}
if (keycode.DUCK[e.keyCode]) {
trex.speedDrop = false;
trex.setDuck(false); //取消闪避
}
}
setDuck方法:
setDuck: function(isDucking) {
if (isDucking && this.status !== Trex.status.DUCKING) {
this.update(0, Trex.status.DUCKING);
this.ducking = true;
} else if (this.status === Trex.status.DUCKING) {
this.update(0, Trex.status.RUNNING);
this.ducking = false;
}
}
最终效果如下(Spacebar或Up跳跃;Down快速下降/闪避):
// 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.speedDrop && this.yPos === this.groundYPos) {
this.speedDrop = false;
this.setDuck(true);
}
},
//开始跳跃
startJump:function (speed) {
if(!this.jumping) {
//切换到jump状态
this.update(0,Trex.status.JUMPING);
//设置跳跃速度
this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
this.jumping = true;
this.reachedMinHeight = false;
this.speedDrop = false;
}
},
updateJump:function (deltaTime, speed) {
//帧切换速率
var msPerFrame = Trex.animFrames[this.status].msPerFrame;
//经过的帧数
var framesElapsed = deltaTime / msPerFrame;
//更新y轴坐标
if(this.speedDrop) {
this.yPos += Math.round(this.jumpVelocity *
this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
} else {
this.yPos += Math.round(this.jumpVelocity * framesElapsed);
}
//由于速度受重力影响,需要对速度进行修正
this.jumpVelocity += this.config.GRAVITY * framesElapsed;
//达到最小跳跃高度
if (this.yPos this.groundYPos) {
this.reset();
this.jumpCount++;
}
this.update(deltaTime);
},
endJump: function() {
if (this.reachedMinHeight && this.jumpVelocity = 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.ducking ? this.config.WIDTH_DUCK : this.config.WIDTH, this.config.HEIGHT);
}
};
window.onload = function () {
var h = new HorizonLine(c,spriteDefinition.HORIZON);
trex = new Trex(c,spriteDefinition.TREX);
var startTime = 0;
var deltaTime;
var speed = 3;
(function draw(time) {
gameFrame++;
time = time || 0;
deltaTime = time - startTime;
if(trex.jumping) {
ctx.clearRect(0,0,600,150);
trex.updateJump(deltaTime);
h.update(deltaTime,speed);
} else {
ctx.clearRect(0,0,600,150);
h.update(deltaTime,speed);
trex.update(deltaTime);
}
startTime = time;
requestAnimationFrame(draw,c);
})();
};
// ]]>
Chrome自带恐龙小游戏的源码研究(六)的更多相关文章
- Chrome自带恐龙小游戏的源码研究(七)
在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较 ...
- 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,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来 ...
随机推荐
- JAVA神操作--使用Arthas线上热更新实战
热更不规范,同事两行泪 背景 C君是一个javaer,最近在开发用户登出接口的时候,不小心把接口参数拼错了 正确的是: /api/v1/user/logout?referrer=www.javaer. ...
- T-SQL百万记录中分组取最大值方法ROW_NUMBER() OVER()
SELECT SysUserID, UserID, ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY AddTime DESC) AS nums AND S ...
- 【LA5059】Playing With Stones (SG函数)
题意:有n堆石子,分别有a[i]个.两个游戏者轮流操作,每次可以选一堆,拿走至少一个石子,但不能拿走超过一半的石子. 谁不能拿石子就算输,问先手胜负情况 n<=100,1<=a[i]< ...
- 从无序序列中求这个序列排序后邻点间最大差值的O(n)算法
标题可能比较绕口,简单点说就是给你一个无序数列A={a1,a2,a3……an},如果你把这个序列排序后变成序列B,求序列B中相邻两个元素之间相差数值的最大值. 注意:序列A的元素的大小在[1,2^31 ...
- WSDL协议简单介绍
WSDL – WebService Description Language – Web服务描述语言 通过XML形式说明服务在什么地方-地址. 通过XML形式说明服务提供什么样的方法 – 如何调用. ...
- 调试UPX压缩的notepad
@date: 2016/11/29 @author: dlive 0x01 运行时压缩 对比upx压缩前后的notepad可以看到如下特点 PE头的大小一样 节区名称改变(.text -> .U ...
- Scrapy笔记:持久化,Feed exports的使用
首先要明确的是,其实所有的FeedExporter都是类,里面封装了一般进行io操作的方法.因此,要怎么输出呢?其实从技术实现来说,在生成item的每一步调用其进行储存都是可以的,只不过为了更加符合s ...
- FZU 1078 计算循环冗余码【模拟】
计算机网络中采用循环冗余码来校验数据的正确性.其原理是:发送方计算出待发送的二进制数据的循环冗余码,并随同原数据一起发送到接收方:接收方通过重新计算接收到的数据的循环冗余码,并和收到的循环冗余码进行比 ...
- ActiveMQ spring (一)
在5.8.0版本下 配置成功. 参考文档:http://yinbinhome.iteye.com/blog/1273228
- 解决百度ueditor配置上传目录为外部目录时,项目启动访问不到图片的问题。
如图所示,公司项目用到了百度的ueditor,配置的上传目录并不在项目根目录下,而是在外部目录中.于是在上传图片时,出现了无法获取图片的问题. 解决方法:添加该目录至tomcat项目部署目录中,如下图 ...