Chrome自带恐龙小游戏的源码研究(五)
2024-08-24 09:14:55 241人阅读
在上一篇《Chrome自带恐龙小游戏的源码研究(四)》中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现。
会眨眼睛的恐龙
在游戏开始前的待机界面,如果仔细观察会发现恐龙会时不时地眨眼睛。这是通过交替绘制这两个图像实现的:
可以通过一张图片来了解这个过程:
为实现图片的切换,需要一个计时器timer,并且需要知道两张图片切换的时间间隔msPerFrame。当计时器timer的时间大于切换的时间间隔msPerFrame时,将图片切换到下一张,到达最后一张时又从第一张开始,如此反复。下面是实现代码:
1 Trex.config = { 2 BLINK_TIMING:3000, //眨眼间隔 3 WIDTH: 44, //站立时宽度 4 WIDTH_DUCK: 59, //闪避时宽度 5 HEIGHT: 47, //站立时高度 6 BOTTOM_PAD: 10, 7 MIN_JUMP_HEIGHT: 30 //最小起跳高度 8 }; 9 //状态10 Trex.status = {11 CRASHED: ‘CRASHED‘, //与障碍物发生碰撞12 DUCKING: ‘DUCKING‘, //闪避13 JUMPING: ‘JUMPING‘, //跳跃14 RUNNING: ‘RUNNING‘, //跑动15 WAITING: ‘WAITING‘ //待机16 };17 //元数据(metadata),记录各个状态的动画帧和帧率18 Trex.animFrames = {19 WAITING: {//待机状态20 frames: [44, 0],//动画帧x坐标在44和0之间切换,由于在雪碧图中的y坐标是0所以不用记录21 msPerFrame: 1000 / 3 //一秒3帧22 },23 RUNNING: {24 frames: [88, 132],25 msPerFrame: 1000 / 1226 },27 CRASHED: {28 frames: [220],29 msPerFrame: 1000 / 6030 },31 JUMPING: {32 frames: [0],33 msPerFrame: 1000 / 6034 },35 DUCKING: {36 frames: [262, 321],37 msPerFrame: 1000 / 838 }39 };40 41 function Trex(canvas,spritePos){42 this.canvas = canvas;43 this.ctx = canvas.getContext(‘2d‘);44 this.spritePos = spritePos; //在雪碧图中的位置45 this.xPos = 0; //在画布中的x坐标46 this.yPos = 0; //在画布中的y坐标47 this.groundYPos = 0; //初始化地面的高度48 this.currentFrame = 0; //初始化动画帧49 this.currentAnimFrames = []; //记录当前状态的动画帧50 this.blinkDelay = 0; //眨眼延迟(随机)51 this.animStartTime = 0; //动画开始的时间52 this.timer = 0; //计时器53 this.msPerFrame = 1000 / FPS; //默认帧率54 this.config = Trex.config; //拷贝一个配置的副本方便以后使用55 this.jumpVelocity = 0; //跳跃的初始速度56 57 this.status = Trex.status.WAITING; //初始化默认状态为待机状态58 59 //为各种状态建立标识60 this.jumping = false; //角色是否处于跳跃中61 this.ducking = false; //角色是否处于闪避中62 this.reachedMinHeight = false; //是否到达最小跳跃高度63 this.speedDrop = false; //是否加速降落64 this.jumpCount = 0; //跳跃次数65 66 this.init();67 }
View Code 首先还是和以往一样,对Trex这个构造函数进行基本的配置,然后在原型链中添加操作方法:
1 Trex.prototype = { 2 init:function() { 3 this.groundYPos = DEFAULT_HEIGHT - this.config.HEIGHT - this.config.BOTTOM_PAD; 4 this.yPos = this.groundYPos; 5 //计算出最小起跳高度 6 this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; 7 8 this.draw(0,0); 9 this.update(0,Trex.status.WAITING);10 },11 setBlinkDelay:function () {12 //设置随机眨眼间隔时间13 this.blinkDelay = Math.ceil(Math.random() * Trex.config.BLINK_TIMING);14 },15 update:function (deltaTime,opt_status) {16 this.timer += deltaTime;17 18 if(opt_status) {19 this.status = opt_status;20 this.currentFrame = 0;21 //得到对应状态的帧率 e.g. WAITING 1000ms / 3fps = 333ms/fps22 this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;23 //对应状态的动画帧 e.g. WAITING [44,0]24 this.currentAnimFrames = Trex.animFrames[opt_status].frames;25 26 if(opt_status === Trex.status.WAITING) {27 //开始计y时28 this.animStartTime = getTimeStamp();29 //设置延时30 this.setBlinkDelay();31 }32 }33 34 //计时器超过一帧的运行时间,切换到下一帧35 if (this.timer >= this.msPerFrame) {36 this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?37 0 : this.currentFrame + 1;38 this.timer = 0; //重置计时器39 }40 41 //待机状态42 if(this.status === Trex.status.WAITING) {43 //执行眨眼动作44 this.blink(getTimeStamp());45 }46 },47 blink:function (time) {48 var deltaTime = time - this.animStartTime;49 50 if(deltaTime >= this.blinkDelay) {51 this.draw(this.currentAnimFrames[this.currentFrame],0);52 53 if (this.currentFrame === 1) {//0闭眼 1睁眼54 //设置新的眨眼间隔时间55 this.setBlinkDelay();56 this.animStartTime = time;57 }58 }59 },60 draw:function (x,y) {61 var sourceX = x;62 var sourceY = y;63 var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?64 this.config.WIDTH_DUCK : this.config.WIDTH;65 var sourceHeight = this.config.HEIGHT;66 sourceX += this.spritePos.x;67 sourceY += this.spritePos.y;68 69 this.ctx.drawImage(imgSprite,70 sourceX, sourceY,71 sourceWidth, sourceHeight,72 this.xPos, this.yPos,73 this.config.WIDTH, this.config.HEIGHT);74 }75 };
View Code 先来看update方法中的这段代码:
1 if (this.timer >= this.msPerFrame) {2 this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?3 0 : this.currentFrame + 1;4 this.timer = 0;5 }
这段代码实现了两个帧之间的切换,但如果只是单纯地以相同时间间隔来切换两张图片,那么得到的效果是不正确的,会出现频繁眨眼的情况。而实际情况是,闭眼只是一瞬间,睁开眼睛的时间则比较长。Chrome开发人员非常巧妙地解决了这个问题:
1 blink:function (time) { 2 var deltaTime = time - this.animStartTime; 3 4 if(deltaTime >= this.blinkDelay) { 5 this.draw(this.currentAnimFrames[this.currentFrame],0); 6 7 if (this.currentFrame === 1) {//0闭眼 1睁眼 8 //设置新的眨眼间隔时间 9 this.setBlinkDelay();10 this.animStartTime = time;11 }12 }13 }
View Code 只要计时器没有超过blinkDelay就不绘制新的图片,这样图片就会停留在上一次绘制的状态,恐龙此时是睁着眼睛的。当时间超过了blinkDelay,即执行眨眼的时间到了,这时会绘制this.currentFrame这一帧。如果这一帧是0(闭眼),由于之前设置了this.timer >= this.msPerFrame时会切换帧,当时间再次超过blinkDelay时,这时就会绘制帧1(睁眼),我们看到的效果就是眼睛闭上只有一瞬然后立刻睁开了。 如果当前帧是1(睁眼),重新设置blinkDelay,于是在deltaTime没有超过重新设置blinkDelay的情况下,都不会绘制新图片(始终保持在帧1(睁眼)),这样我们看到的效果就是睁眼的时间稍长。
下面是运行后的效果:
<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 //region utils function getRandomNum(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function getTimeStamp() { return performance.now(); } //状态 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.blinkDelay = this.setBlinkDelay(); 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); } };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); })(); };// ]]></script>Chrome自带恐龙小游戏的源码研究(五)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉:
投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。