首页 > 代码库 > Chrome自带恐龙小游戏的源码研究(六)

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

  在上一篇《Chrome自带恐龙小游戏的源码研究(五)》中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃。

恐龙的跳跃

  游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃。先用一张图来表示整个跳跃的过程:

技术分享

  1. 首先规定向下为正方向,即重力加速度(g)为正,起跳的速度(v)为负,恐龙距离画布上方的距离为yPos
  2. 每一帧动画中,速度都会与重力加速度相加得到新的速度,再用新的速度与yPos相加得到新的yPos,改变恐龙的位置为新的yPos,表现出来为yPos不断减小;
  3. 当恐龙升至最高点,此时速度为0,并且仍具有向下的重力加速度。
  4. 速度仍与重力加速度相加得到新的速度,此时速度方向向下,为正值,表现为yPos逐渐增加;
  5. 落地,并使yPos不超过地面的高度,将速度重置为0,更新状态jumping为false。

  下面通过代码来实现。首先注册键盘事件:

1 document.addEventListener(‘keydown‘,onKeyDown);2 document.addEventListener(‘keyup‘,onKeyUp);
1         function onKeyDown(e) {2             if(keycode.JUMP[e.keyCode]) {3                 if(!trex.jumping) {4                     trex.startJump(6);5                 }6             }7         }

按下跳跃键后,执行startJump方法:

技术分享
 1 startJump: function(speed) { 2     if (!this.jumping) { 3         //切换到jump状态 4         this.update(0, Trex.status.JUMPING); 5         //设置跳跃速度 6         this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10); 7         this.jumping = true; 8         this.reachedMinHeight = false; 9     }10 }
View Code

之后在每次GameLoop中更新状态:

技术分享
1 if (trex.jumping) {2     ctx.clearRect(0, 0, 600, 150);3     trex.updateJump(deltaTime);4 }
View Code
技术分享
 1 updateJump: function(deltaTime) { 2     //帧切换速率 3     var msPerFrame = Trex.animFrames[this.status].msPerFrame; 4     //经过的帧数 5     var framesElapsed = deltaTime / msPerFrame; 6     //更新y轴坐标 7     this.yPos += Math.round(this.jumpVelocity * framesElapsed); 8     //由于速度受重力影响,需要对速度进行修正 9     this.jumpVelocity += this.config.GRAVITY * framesElapsed;10 11     //达到最小跳跃高度12     if (this.yPos < this.minJumpHeight) {13         this.reachedMinHeight = true;14     }15     //达到最大高度后停止跳跃16     if (this.yPos < this.config.MAX_JUMP_HEIGHT) {17         this.endJump();18     }19     if (this.yPos > this.groundYPos) {20         this.reset();21         this.jumpCount++;22     }23     this.update(deltaTime);24 },25 26 update: function(deltaTime, opt_status) {27     this.timer += deltaTime;28 29     if (opt_status) {30         this.status = opt_status;31         this.currentFrame = 0;32         //得到对应状态的帧率 e.g. WAITING 1000ms / 3fps = 333ms/fps33         this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;34         //对应状态的动画帧 e.g. WAITING [44,0]35         this.currentAnimFrames = Trex.animFrames[opt_status].frames;36 37         if (opt_status === Trex.status.WAITING) {38             //开始计时39             this.animStartTime = getTimeStamp();40             //设置延时41             this.setBlinkDelay();42         }43     }44 45     //计时器超过一帧的运行时间,切换到下一帧46     if (this.timer >= this.msPerFrame) {47         this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;48         this.timer = 0;49     }50 51     //待机状态52     if (this.status === Trex.status.WAITING) {53         //执行眨眼动作54         this.blink(getTimeStamp());55     } else {56         this.draw(this.currentAnimFrames[this.currentFrame], 0);57     }58 }
View Code

 这样就实现了跳跃的过程。

 

轻跳

  如果持续按住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键,恐龙会加速下降。

1 function onKeyDown(e) {2     //......3     if(keycode.DUCK[e.keyCode]) {//Down4         if(trex.jumping) {5             trex.setSpeedDrop();   //加速下降6         }7     }8 }

松开键位时取消加速:

1 function onKeyUp(e) {2     //......3     if (keycode.DUCK[e.keyCode]) {4         trex.speedDrop = false;5     }6 }

在构造函数中添加setSpeedDrop方法:

1 setSpeedDrop: function() {2     this.speedDrop = true;3     this.jumpVelocity = 1;    //将速度设置为1,正方向(向下为正方向)4 }

还需要对updateJump方法做一些更新:

技术分享
 1 updateJump:function (deltaTime) { 2     //...... 3      4     //更新y轴坐标 5     if (this.speedDrop) { 6         //SPEED_DROP_COEFFICIENT为加速倍数,初始设定为3 7     this.yPos += Math.round(this.jumpVelocity *       this.config.SPEED_DROP_COEFFICIENT * framesElapsed); 8     } else { 9     this.yPos += Math.round(this.jumpVelocity * framesElapsed);10     }  11 12 13     //达到最小跳跃高度14     //speedDrop也能触发reachedMinHeight15     if (this.yPos < this.minJumpHeight || this.speedDrop) {16     this.reachedMinHeight = true;17     }18 19     //达到最大高度后停止跳跃20     //speedDrop也能触发endJump21     if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop)         {22     this.endJump();23     }24 //......25 26 }
View Code

效果如下图所示,在跳跃过程中按住Down,可以发现下落速度比平时快:

技术分享

闪避

  在地面上按住Down键,恐龙会进入闪避状态。首先还是从keydown方法入手:

1 if (keycode.DUCK[e.keyCode]) {2     e.preventDefault();3     if (trex.jumping) {4         trex.setSpeedDrop();5     } else if (!trex.jumping && !trex.ducking) {6         trex.setDuck(true);    //闪避7     }8 }

keyup方法取消闪避:

1 function onKeyUp(e) {2     if (keycode.JUMP[e.keyCode]) {3         trex.endJump();4     }5     if (keycode.DUCK[e.keyCode]) {6         trex.speedDrop = false;7         trex.setDuck(false);   //取消闪避8     }9 }

setDuck方法:

1 setDuck: function(isDucking) {2     if (isDucking && this.status !== Trex.status.DUCKING) {3         this.update(0, Trex.status.DUCKING);4         this.ducking = true;5     } else if (this.status === Trex.status.DUCKING) {6         this.update(0, Trex.status.RUNNING);7         this.ducking = false;8     }9 }

最终效果如下(SpacebarUp跳跃;Down快速下降/闪避):

 

<script type="text/javascript">// 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.dimensions.WIDTH) { this.xPos[line1] += this.dimensions.WIDTH * 2; this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x; } }, update:function(deltaTime,speed) { var increment = Math.floor(speed * (FPS / 1000) * deltaTime); if(this.xPos[0] <= 0) { this.updateXPos(0, increment); } else { this.updateXPos(1, increment); } this.draw(); }, reset:function() { this.xPos[0] = 0; this.xPos[1] = this.dimensions.WIDTH; } }; //endregion function onKeyUp(e) { if(keycode.JUMP[e.keyCode]) { trex.endJump(); } if(keycode.DUCK[e.keyCode]) { trex.speedDrop = false; trex.setDuck(false); } } //todo Trex.config = { BLINK_TIMING:3000, //眨眼间隔 WIDTH: 44, //站立时宽度 WIDTH_DUCK: 59, //闪避时宽度 HEIGHT: 47, //站立时高度 BOTTOM_PAD: 10, GRAVITY: 0.6, //重力 INIITAL_JUMP_VELOCITY: -10,//初始起跳速度 DROP_VELOCITY: -5, //下落速度 SPEED_DROP_COEFFICIENT:3, //加速下降系数 MIN_JUMP_HEIGHT: 30, //最小起跳高度 MAX_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.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.RUNNING); }, setBlinkDelay:function () { //设置随机眨眼间隔时间 this.blinkDelay = Math.ceil(Math.random() * Trex.config.BLINK_TIMING); }, 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; } }, 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.status === Trex.status.WAITING) { //执行眨眼动作 this.blink(getTimeStamp()); } else { this.draw(this.currentAnimFrames[this.currentFrame], 0); } //计时器超过一帧的运行时间,切换到下一帧 if (this.timer >= 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.minJumpHeight || this.speedDrop) { this.reachedMinHeight = true; } //达到最大高度后停止跳跃 if(this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) { this.endJump(); } if(this.yPos > this.groundYPos) { this.reset(); this.jumpCount++; } this.update(deltaTime); }, endJump: function() { if (this.reachedMinHeight && this.jumpVelocity < this.config.DROP_VELOCITY) { this.jumpVelocity = this.config.DROP_VELOCITY; } }, setSpeedDrop:function () { this.speedDrop = true; this.jumpVelocity = 1; }, reset:function () { this.yPos = this.groundYPos; this.jumpVelocity = 0; this.jumping = false; this.jumpCount = 0; this.ducking = false; this.update(0, Trex.status.RUNNING); this.speedDrop = false; }, 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.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); })(); };// ]]></script>

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