内容简介:文章首发于我的上一篇文章:《定义小恐龙类:
文章首发于我的 个人博客
前言
上一篇文章:《 Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替 》实现了游戏昼夜模式的交替,这一篇文章中,将实现:1、小恐龙的绘制 2、键盘对小恐龙的控制 3、页面失焦后,重新聚焦会重置小恐龙的状态
绘制静态的小恐龙
定义小恐龙类:
/** * 小恐龙类 * @param {HTMLCanvasElement} canvas 画布 * @param {Object} spritePos 图片在雪碧图中的坐标 */ function Trex(canvas, spritePos) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.spritePos = spritePos; this.xPos = 0; this.yPos = 0; this.groundYPos = 0; // 小恐龙在地面上时的 y 坐标 this.currentFrame = 0; // 当前的动画帧 this.currentAnimFrames = []; // 存储当前状态的动画帧在雪碧图中的 x 坐标 this.blinkDelay = 0; // 眨眼间隔的时间(随 机) this.blinkCount = 0; // 眨眼次数 this.animStartTime = 0; // 小恐龙眨眼动画开始时间 this.timer = 0; // 计时器 this.msPerFrame = 1000 / FPS; // 帧率 this.status = Trex.status.WAITING; // 当前的状态 this.config = Trex.config; this.jumping = false; // 是否跳跃 this.ducking = false; // 是否闪避(俯身) this.jumpVelocity = 0; // 跳跃的速度 this.reachedMinHeight = false; // 是否达到最低高度 this.speedDrop = false; // 是否加速下降 this.jumpCount = 0; // 跳跃的次数 this.jumpspotX = 0; // 跳跃点的 x 坐标 this.init(); }
相关的配置参数:
Trex.config = { GRAVITY: 0.6, // 引力 WIDTH: 44, // 站立时的宽度 HEIGHT: 47, WIDTH_DUCK: 59, // 俯身时的宽度 HEIGHT_DUCK: 25, MAX_JUMP_HEIGHT: 30, // 最大跳跃高度 MIN_JUMP_HEIGHT: 30, // 最小跳跃高度 SPRITE_WIDTH: 262, // 站立的小恐龙在雪碧图中的总宽度 DROP_VELOCITY: -5, // 下落的速度 INITIAL_JUMP_VELOCITY: -10, // 初始跳跃速度 SPEED_DROP_COEFFICIENT: 3, // 下落时的加速系数(越大下落的越快) INTRO_DURATION: 1500, // 开场动画的时间 START_X_POS: 50, // 开场动画结束后,小恐龙在 canvas 上的 x 坐标 }; Trex.BLINK_TIMING = 7000; // 眨眼最大间隔的时间 // 小恐龙的状态 Trex.status = { CRASHED: 'CRASHED', // 撞到障碍物 DUCKING: 'DUCKING', // 正在闪避(俯身) JUMPING: 'JUMPING', // 正在跳跃 RUNNING: 'RUNNING', // 正在奔跑 WAITING: 'WAITING', // 正在等待(未开始游戏) }; // 为不同的状态配置不同的动画帧 Trex.animFrames = { WAITING: { frames: [44, 0], msPerFrame: 1000 / 3 }, RUNNING: { frames: [88, 132], msPerFrame: 1000 / 12 }, CRASHED: { frames: [220], msPerFrame: 1000 / 60 }, JUMPING: { frames: [0], msPerFrame: 1000 / 60 }, DUCKING: { frames: [264, 323], msPerFrame: 1000 / 8 }, };
补充本篇文章中会用到的一些数据:
Runner.config = { // ... BOTTOM_PAD: 10, // 小恐龙距 canvas 底部的距离 MAX_BLINK_COUNT: 3, // 小恐龙的最大眨眼次数 }; Runner.spriteDefinition = { LDPI: { // ... TREX: {x: 848, y: 2}, // 小恐龙 }, };
然后来看下 Trex 原型链上的方法。我们首先来绘制静态的小恐龙:
Trex.prototype = { init: function() { // 获取小恐龙站在地面上时的 y 坐标 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - Runner.config.BOTTOM_PAD; this.yPos = this.groundYPos; // 小恐龙的 y 坐标初始化 this.draw(0, 0); // 绘制小恐龙的第一帧图片 }, /** * 绘制小恐龙 * @param {Number} x 当前帧相对于第一帧的 x 坐标 * @param {Number} y 当前帧相对于第一帧的 y 坐标 */ draw: function(x, y) { // 在雪碧图中的坐标 var sourceX = x + this.spritePos.x; var sourceY = y + this.spritePos.y; // 在雪碧图中的宽高 var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ? this.config.WIDTH_DUCK : this.config.WIDTH; var sourceHeight = this.config.HEIGHT; // 绘制到 canvas 上时的高度 var outputHeight = sourceHeight; // 躲避状态. if (this.ducking && this.status != Trex.status.CRASHED) { this.ctx.drawImage( Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, this.xPos, this.yPos, this.config.WIDTH_DUCK, outputHeight ); } else { // 躲闪状态下撞到障碍物 if (this.ducking && this.status == Trex.status.CRASHED) { this.xPos++; } // 奔跑状态 this.ctx.drawImage( Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, this.xPos, this.yPos, this.config.WIDTH, outputHeight ); } this.ctx.globalAlpha = 1; }, };
前面进入街机模式那一章中,用到了 Trex 类中的数据,临时定义了 Trex 类,别忘了将其删除。
接下来需要通过 Runner 类调用 Trex 类。添加属性用于存储小恐龙类的实例:
function Runner(containerSelector, opt_config) { // ... + this.tRex = null; // 小恐龙 }
初始化小恐龙类:
Runner.prototype = { init: function () { // ... // 加载小恐龙类 + this.tRex = new Trex(this.canvas, this.spriteDef.TREX); }, };
这样在游戏初始化时就绘制出了静态的小恐龙,如图:
实现眨眼效果
游戏初始化之后,小恐龙会随机眨眼睛。默认的是最多只能眨三次。下面将实现这个效果。
添加更新小恐龙的方法:
Trex.prototype = { /** * 更新小恐龙 * @param {Number} deltaTime 间隔时间 * @param {String} opt_status 小恐龙的状态 */ update: function(deltaTime, opt_status) { this.timer += deltaTime; // 更新状态的参数 if (opt_status) { this.status = opt_status; this.currentFrame = 0; this.msPerFrame = Trex.animFrames[opt_status].msPerFrame; 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; } }, // 设置眨眼间隔的时间 setBlinkDelay: function() { this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); }, // 小恐龙眨眼 blink: function (time) { var deltaTime = time - this.animStartTime; // 间隔时间大于随机获取的眨眼间隔时间才能眨眼 if (deltaTime >= this.blinkDelay) { this.draw(this.currentAnimFrames[this.currentFrame], 0); // 正在眨眼 if (this.currentFrame == 1) { console.log('眨眼'); this.setBlinkDelay(); // 重新设置眨眼间隔的时间 this.animStartTime = time; // 更新眨眼动画开始的时间 this.blinkCount++; // 眨眼次数加一 } } }, };
然后将小恐龙初始更新为等待状态:
Trex.prototype = { init: function () { // ... this.update(0, Trex.status.WAITING); // 初始为等待状态 }, };
最后在 Runner 的 update
方法中调用 Trex 的 update
方法来实现小恐龙眨眼:
Runner.prototype = { update: function () { // ... // 游戏变为开始状态或小恐龙还没有眨三次眼 - if (this.playing) { + if (this.playing || (!this.activated && + this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { + this.tRex.update(deltaTime); // 进行下一次更新 this.scheduleNextUpdate(); } }, };
效果如下:
可以看到,眨眼的代码逻辑触发了 3 次,但是实际小恐龙只眨眼了 1 次。这就是前面说的,小恐龙默认最多只能眨三次眼。具体原因如下:
先来看下 Trex 的 update
方法中的这段代码:
if (this.timer >= this.msPerFrame) { // 更新当前动画帧,如果处于最后一帧就更新为第一帧,否则更新为下一帧 this.currentFrame = this.currentFrame == this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; // 重置计时器 this.timer = 0; }
这段代码会将当前动画帧不断更新为下一帧。对于小恐龙来说就是不断切换 睁眼 和 闭眼 这两帧。如果当前帧为 “睁眼”,那么执行 blink
函数后小恐龙还是睁眼,也就是说实际小恐龙没眨眼;同理,只有当前帧为 “闭眼” 时,执行 blink
函数后,小恐龙才会真正的眨眼。
至于这样做的目的,就是为了防止小恐龙不停的眨眼睛。例如,将 blink
函数修改为:
// 小恐龙眨眼 blink: function () { this.draw(this.currentAnimFrames[this.currentFrame], 0); },
这样小恐龙会不停的眨眼睛。所以需要对其进行限制,这里 Chrome 开发人员的做法就是:设置一个间隔时间,当小恐龙眨眼的间隔时间大于这个设置的间隔时间,并且当前动画帧为 “闭眼” 时,才允许小恐龙眨眼睛。然后每次眨完眼后,重新设置眨眼间隔(默认设置为 0~7 秒),就实现了小恐龙的随机眨眼。
小恐龙的开场动画
下面来实现小恐龙对键盘按键的响应。
首先,当触发游戏彩蛋后,小恐龙会跳跃一次,并向右移动 50 像素(默认设置的是 50 像素)。
添加让小恐龙开始跳跃的方法:
Trex.prototype = { // 开始跳跃 startJump: function(speed) { if (!this.jumping) { // 更新小恐龙为跳跃状态 this.update(0, Trex.status.JUMPING); // 根据游戏的速度调整跳跃的速度 this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10); this.jumping = true; this.reachedMinHeight = false; this.speedDrop = false; } }, };
进行调用:
Runner.prototype = { onKeyDown: function (e) { if (!this.crashed && !this.paused) { if (Runner.keyCodes.JUMP[e.keyCode]) { e.preventDefault(); // ... // 开始跳跃 + if (!this.tRex.jumping && !this.tRex.ducking) { + this.tRex.startJump(this.currentSpeed); + } } } }, };
这样,按下空格键后,小恐龙仍然会静止在地面上。接下来还需要更新动画帧才能实现小恐龙的奔跑动画。
添加更新小恐龙动画帧的方法:
Trex.prototype = { // 更新小恐龙跳跃时的动画帧 updateJump: function(deltaTime) { var msPerFrame = Trex.animFrames[this.status].msPerFrame; // 获取当前状态的帧率 var framesElapsed = deltaTime / msPerFrame; // 加速下落 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++; // 跳跃次数加一 } }, // 跳跃结束 endJump: function() { if (this.reachedMinHeight && this.jumpVelocity < this.config.DROP_VELOCITY) { this.jumpVelocity = this.config.DROP_VELOCITY; // 下落速度重置为默认 } }, // 重置小恐龙状态 reset: function() { this.yPos = this.groundYPos; this.jumpVelocity = 0; this.jumping = false; this.ducking = false; this.update(0, Trex.status.RUNNING); this.speedDrop = false; this.jumpCount = 0; }, };
其中 minJumpHeight
的属性值为:
Trex.prototype = { init: function() { // 最低跳跃高度 + this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; // ... }, }
然后进行调用:
Runner.prototype = { update: function () { // ... if (this.playing) { this.clearCanvas(); + if (this.tRex.jumping) { + this.tRex.updateJump(deltaTime); + } this.runningTime += deltaTime; var hasObstacles = this.runningTime > this.config.CLEAR_TIME; // 刚开始 this.playingIntro 未定义 !this.playingIntro 为真 - if (!this.playingIntro) { + if (this.tRex.jumpCount == 1 && !this.playingIntro) { this.playIntro(); // 执行开场动画 } // ... } // ... }, };
这样在按下空格键后,小恐龙就会跳跃一次并进行奔跑动画。如图:
下面来实现效果:小恐龙第一次跳跃后,向右移动 50 像素。
修改 Trex 的 update
方法。当判断到正在执行开场动画时,移动小恐龙:
Trex.prototype = { update: function(deltaTime, opt_status) { this.timer += deltaTime; // 更新状态的参数 if (opt_status) { // ... } // 正在执行开场动画,将小恐龙向右移动 50 像素 + if (this.playingIntro && this.xPos < this.config.START_X_POS) { + this.xPos += Math.round((this.config.START_X_POS / + this.config.INTRO_DURATION) * deltaTime); + } // ... }, };
可以看出当 playingIntro
属性为 true
时,小恐龙就会向右移动。所以需要通过控制这个属性的值来控制小恐龙第一次跳跃后的移动。
修改 Runner 上的 playIntro
方法,将小恐龙标记为正在执行开场动画:
Runner.prototype = { playIntro: function () { if (!this.activated && !this.crashed) { + this.tRex.playingIntro = true; // 小恐龙执行开场动画 // ... } }, };
然后需要在开始游戏后也就是执行 startGame
方法时,结束小恐龙的开场动画:
Runner.prototype = { startGame: function () { this.setArcadeMode(); // 进入街机模式 + this.tRex.playingIntro = false; // 小恐龙的开场动画结束 // ... }, };
效果如下:
可以很明显的看到,小恐龙在第一次跳跃后向右移动了一段距离(默认 50 像素)。
使用键盘控制小恐龙
在这个游戏中,当按下 ↓
键后,如果小恐龙正在跳跃,就会快速下落,如果小恐龙在地上,就会进入躲闪状态,下面来实现这些效果。
加速下落:
Trex.prototype = { // 设置小恐龙为加速下落,立即取消当前的跳跃 setSpeedDrop: function() { this.speedDrop = true; this.jumpVelocity = 1; }, };
设置小恐龙是否躲闪:
Trex.prototype = { // 设置小恐龙奔跑时是否躲闪 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; } }, };
在 onKeyDown
方法中调用:
Runner.prototype = { onKeyDown: function () { if (!this.crashed && !this.paused) { if (Runner.keyCodes.JUMP[e.keyCode]) { // ... + } else if (this.playing && Runner.keyCodes.DUCK[e.keyCode]) { + e.preventDefault(); + + if (this.tRex.jumping) { + this.tRex.setSpeedDrop(); // 加速下落 + } else if (!this.tRex.jumping && !this.tRex.ducking) { + this.tRex.setDuck(true); // 进入躲闪状态 + } + } } }, };
这样就实现了前面所说的效果。但是小恐龙进入躲闪状态后,如果松开按键并不会重新站起来。因为现在还没有定义松开键盘按键时响应的事件。下面来定义:
Runner.prototype = { onKeyUp: function(e) { var keyCode = String(e.keyCode); if (Runner.keyCodes.DUCK[keyCode]) { // 躲避状态 this.tRex.speedDrop = false; this.tRex.setDuck(false); } }, };
然后调用,修改 handleEvent
方法:
Runner.prototype = { handleEvent: function (e) { return (function (eType, events) { switch (eType) { // ... + case events.KEYUP: + this.onKeyUp(e); + break; default: break; } }.bind(this))(e.type, Runner.events); }, };
效果如下:
第一次跳是正常下落,第二次跳是加速下落
处理小恐龙的跳跃
小恐龙的跳跃分为大跳和小跳,如图:
要实现这个效果,只需要在 ↑
键被松开时,立即结束小恐龙的跳跃即可。
修改 onKeyUp
方法:
Runner.prototype = { onKeyUp: function(e) { var keyCode = String(e.keyCode); + var isjumpKey = Runner.keyCodes.JUMP[keyCode]; + if (this.isRunning() && isjumpKey) { // 跳跃 + this.tRex.endJump(); } else if (Runner.keyCodes.DUCK[keyCode]) { // 躲避状态 this.tRex.speedDrop = false; this.tRex.setDuck(false); } }, };
其中 isRunning
方法定义如下:
Runner.prototype = { // 是否游戏正在进行 isRunning: function() { return !!this.raqId; }, };
这样就实现了小恐龙的大跳和小跳。
最后是要实现的效果是:如果页面失焦时,小恐龙正在跳跃,就重置小恐龙的状态(也就是会立即回到地面上)。这个效果实现很简单,直接调用前面定义的 reset
方法即可:
Runner.prototype = { play: function () { if (!this.crashed) { // ... + this.tRex.reset(); } }, };
效果如下:
查看添加的代码, 戳这里
Demo 体验地址: https://liuyib.github.io/pages/demo/games/google-dino/dino-gogogo/
上一篇 | 下一篇 | Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替 | TODO |
以上所述就是小编给大家介绍的《Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Chrome 小恐龙游戏源码探究六 -- 记录游戏分数
- Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍
- Chrome 小恐龙游戏源码探究九 -- 游戏碰撞检测
- Chrome 小恐龙游戏源码探究二 -- 让地面动起来
- Chrome 小恐龙游戏源码探究完 -- 游戏结束和其他要素
- 用语言模型命名恐龙
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript从入门到精通
明日科技 / 清华大学出版社 / 2012-9 / 69.80元
《JavaScript从入门到精通》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用JavaScript语言进行程序开发应该掌握的各方面技术。全书共分24章,包括初识JavaScript、JavaScript基础、流程控制、函数、JavaScript对象与数组、字符串与数值处理对象、正则表达式、程序调试与错误处理、事件处理、处理文档(document对象)、文档对象模型(DOM......一起来看看 《JavaScript从入门到精通》 这本书的介绍吧!