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

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

  在上一篇《Chrome自带恐龙小游戏的源码研究(三)》中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物。

  障碍物有两种:仙人掌和翼龙。仙人掌有大小两种类型,可以同时并列多个;翼龙按高、中、低的随机飞行高度出现,不可并行。仙人掌和地面有着相同的速度向左移动,翼龙则快一些或慢一些,因为添加了随机的速度修正。我们使用一个障碍物列表管理它们,当它们移出屏幕外时则将其从列表中移除。同时再用一个列表记录它们的类型:

1 Obstacle.obstacles = [];    //存储障碍物的数组2 Obstacle.obstacleHistory = [];  //记录障碍物数组中障碍物的类型

障碍物的出现不能太频繁,也不能太稀少,太频繁立刻就gameover了,太稀少则没有挑战性,因此需要一定的规则来生成障碍物。每组障碍物之间应该有一段间隔作为落脚点,新生成的障碍物在这个间隔之外生成。如示意图所示:

技术分享

因此,先定义一个最大间距系数,下面会用这个系数生成随机间距:

Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障碍物最大间距系数

另外,还需要对障碍物进行一些约束及配置:

 1 //每组障碍物的最大数量 2 Obstacle.MAX_OBSTACLE_LENGTH = 3;    3 //相邻的障碍物类型的最大重复数 4 Obstacle.MAX_OBSTACLE_DUPLICATION = 2; 5  6 Obstacle.types = [ 7     { 8         type: ‘CACTUS_SMALL‘, //小仙人掌 9         width: 17,  //10         height: 35, //11         yPos: 105,  //在画布上的y坐标12         multipleSpeed: 4, 13         minGap: 120,    //最小间距14         minSpeed: 0    //最低速度15     },16     {17         type: ‘CACTUS_LARGE‘,   //大仙人掌18         width: 25,19         height: 50,20         yPos: 90,21         multipleSpeed: 7,22         minGap: 120,23         minSpeed: 024     },25     {26         type: ‘PTERODACTYL‘,    //翼龙27         width: 46,28         height: 40,29         yPos: [ 100, 75, 50 ], //有高、中、低三种高度30         multipleSpeed: 999,31         minSpeed: 8.5,  32         minGap: 150,33         numFrames: 2,   //有两个动画帧34         frameRate: 1000/6,  //动画帧的切换速率,这里为一秒6帧35         speedOffset: .8 //速度修正36     }37 ];

障碍物的所有实现由构造函数Obstacle完成,下面是它的实现代码:

技术分享
  1 /**  2  * 绘制障碍物构造函数  3  * @param canvas  4  * @param type 障碍物的类型  5  * @param spriteImgPos 雪碧图坐标  6  * @param dimensions 屏幕尺寸  7  * @param gapCoefficient 障碍物间隙  8  * @param speed 障碍物移动速度  9  * @param opt_xOffset 障碍物水平偏移量 10  * @constructor 11  */ 12 function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) { 13     this.ctx = canvas.getContext(‘2d‘); 14     this.spritePos = spriteImgPos; 15     //障碍物类型(仙人掌、翼龙) 16     this.typeConfig = type; 17     this.gapCoefficient = gapCoefficient; 18     //每个障碍物的数量(1~3) 19     this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH); 20     this.dimensions = dimensions; 21     //表示该障碍物是否可以被移除 22     this.remove = false; 23     //水平坐标 24     this.xPos = dimensions.WIDTH + (opt_xOffset || 0); 25     this.yPos = 0; 26     this.width = 0; 27     this.gap = 0; 28     this.speedOffset = 0;   //速度修正 29  30     //障碍物的动画帧 31     this.currentFrame = 0; 32     //动画帧切换的计时器 33     this.timer = 0; 34  35     this.init(speed); 36 } 37 ``` 38  39 实例方法: 40 ```javascript 41 Obstacle.prototype = { 42     init:function(speed) { 43         //如果随机障碍物是翼龙,则只出现一只 44         //翼龙的multipleSpeed是999,远大于speed 45         if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { 46             this.size = 1; 47         } 48         //障碍物的总宽度等于单个障碍物的宽度乘以个数 49         this.width = this.typeConfig.width * this.size; 50  51         //若障碍物的纵坐标是一个数组 52         //则随机选取一个 53         if (Array.isArray(this.typeConfig.yPos))  { 54             var yPosConfig = this.typeConfig.yPos; 55             this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; 56         } else { 57             this.yPos = this.typeConfig.yPos; 58         } 59  60         this.draw(); 61  62         //对翼龙的速度进行修正,让它看起来有的飞得快一些,有些飞得慢一些 63         if (this.typeConfig.speedOffset) { 64             this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : 65                 -this.typeConfig.speedOffset; 66         } 67  68         //障碍物之间的间隙,与游戏速度有关 69         this.gap = this.getGap(this.gapCoefficient, speed); 70     }, 71     //障碍物之间的间隔,gapCoefficient为间隔系数 72     getGap: function(gapCoefficient, speed) { 73         var minGap = Math.round(this.width * speed + 74             this.typeConfig.minGap * gapCoefficient); 75         var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT); 76         return getRandomNum(minGap, maxGap); 77     }, 78     //判断障碍物是否移出屏幕外 79     isVisible: function() { 80         return this.xPos + this.width > 0; 81     }, 82     draw:function() { 83         //障碍物宽高 84         var sourceWidth = this.typeConfig.width; 85         var sourceHeight = this.typeConfig.height; 86  87         //根据障碍物数量计算障碍物在雪碧图上的x坐标 88         //this.size的取值范围是1~3 89         var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + 90             this.spritePos.x; 91  92         // 如果当前动画帧大于0,说明障碍物类型是翼龙 93         // 更新翼龙的雪碧图x坐标使其匹配第二帧动画 94         if (this.currentFrame > 0) { 95             sourceX += sourceWidth * this.currentFrame; 96         } 97         this.ctx.drawImage(imgSprite, 98             sourceX, this.spritePos.y, 99             sourceWidth * this.size, sourceHeight,100             this.xPos, this.yPos,101             sourceWidth * this.size, sourceHeight);102     },103     //单个障碍物的移动104     update:function(deltaTime, speed) {105         //如果障碍物还没有移出屏幕外106         if (!this.remove) {107             //如果有速度修正则修正速度108             if (this.typeConfig.speedOffset) {109                 speed += this.speedOffset;110             }111             //更新x坐标112             this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);113 114             // Update frame115             if (this.typeConfig.numFrames) {116                 this.timer += deltaTime;117                 if (this.timer >= this.typeConfig.frameRate) {118                     //在两个动画帧之间来回切换以达到动画效果119                     this.currentFrame =120                         this.currentFrame == this.typeConfig.numFrames - 1 ?121                             0 : this.currentFrame + 1;122                     this.timer = 0;123                 }124             }125             this.draw();126 127             if (!this.isVisible()) {128                 this.remove = true;129             }130         }131     },132     //管理多个障碍物移动133     updateObstacles: function(deltaTime, currentSpeed) {134         //保存一个障碍物列表的副本135         var updatedObstacles = Obstacle.obstacles.slice(0);136 137         for (var i = 0; i < Obstacle.obstacles.length; i++) {138             var obstacle = Obstacle.obstacles[i];139             obstacle.update(deltaTime, currentSpeed);140 141             //移除被标记为删除的障碍物142             if (obstacle.remove) {143                 updatedObstacles.shift();144             }145         }146         Obstacle.obstacles = updatedObstacles;147 148         if(Obstacle.obstacles.length > 0) {149             //获取障碍物列表中的最后一个障碍物150             var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];151 152             //若满足条件则添加障碍物153             if (lastObstacle &&154                 lastObstacle.isVisible() &&155                 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <156                 this.dimensions.WIDTH) {157                 this.addNewObstacle(currentSpeed);158             }159         } else {//若障碍物列表中没有障碍物则立即添加160             this.addNewObstacle(currentSpeed);161         }162     },163     //随机添加障碍164     addNewObstacle:function (currentSpeed) {165         //随机选取一种类型的障碍166         var obstacleTypeIndex = getRandomNum(0,Obstacle.types.length - 1);167         var obstacleType = Obstacle.types[obstacleTypeIndex];168 169         //检查随机取到的障碍物类型是否与前两个重复170         //或者检查其速度是否合法,这样可以保证游戏在低速时不出现翼龙171         //如果检查不通过,则重新再选一次直到通过为止172         if(this.duplicateObstacleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) {173             this.addNewObstacle(currentSpeed);174         } else {175             //检查通过后,获取其雪碧图中的坐标176             var obstacleSpritePos = this.spritePos[obstacleType.type];177             //生成新的障碍物并存入数组178             Obstacle.obstacles.push(new Obstacle(c,obstacleType,obstacleSpritePos,this.dimensions,179                 this.gapCoefficient,currentSpeed,obstacleType.width));180             //同时将障碍物的类型存入history数组181             Obstacle.obstacleHistory.unshift(obstacleType.type);182         }183 184         //若history数组的长度大于1,则清空最前面的两个185         if (Obstacle.obstacleHistory.length > 1) {186             Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);187         }188     },189     //检查障碍物是否超过允许的最大重复数190     duplicateObstacleCheck:function(nextObstacleType) {191         var duplicateCount = 0;192         //与history数组中的障碍物类型比较,最大只允许重得两次193         for(var i = 0; i < Obstacle.obstacleHistory.length; i++) {194             duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;195         }196         return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION;197     }198 };
View Code

最后在此前的基础上添加一段测试代码:

技术分享
 1 window.onload = function () { 2             var h = new HorizonLine(c,spriteDefinition.HORIZON); 3             var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH); 4             var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH); 5             var obstacle = new Obstacle(c,Obstacle.types[0],spriteDefinition,{WIDTH:600},0.6,1); 6             var startTime = 0; 7             var deltaTime; 8             var speed = 3; 9             (function draw(time) {10                 gameFrame++;11                 if(speed < 13.5) {12                     speed += 0.01;13                 }14                 ctx.clearRect(0,0,600,150);15                 time = time || 0;16                 deltaTime = time - startTime;17                 h.update(deltaTime,speed);18                 cloud.updateClouds(0.2);19                 night.invert(deltaTime);20                 obstacle.updateObstacles(deltaTime,speed);21                 startTime = time;22                 window.requestAnimationFrame(draw,c);23             })();24         };
View Code

最终得到的效果:

<style></style>
 

<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(); } //endregion //region Cloud Cloud.config = { HEIGHT:14, //云朵sprite的高度 MAX_CLOUD_GAP:400, //两朵云之间的最大间隙 MAX_SKY_LEVEL:30, //云朵的最大高度 MIN_CLOUD_GAP:100, //两朵云之间的最小间隙 MIN_SKY_LEVEL:71, //云朵的最小高度 WIDTH:46, //云朵sprite的宽度 MAX_CLOUDS:6,//最大云朵数量 CLOUD_FREQUENCY:.5 //云朵出现频率 }; Cloud.clouds = []; function Cloud(canvas,spritePos,containerWidth) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.spritePos = spritePos; this.containerWidth = containerWidth; this.xPos = containerWidth; //云朵初始x坐标在屏幕外 this.yPos = 0; //云朵初始高度 this.remove = false; //是否移除 //云间隙 this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,Cloud.config.MAX_CLOUD_GAP); this.init(); } Cloud.prototype = { init:function () { //云朵高度随机 this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,Cloud.config.MIN_SKY_LEVEL); this.draw(); }, draw:function () { this.ctx.save(); var sourceWidth = Cloud.config.WIDTH, sourceHeight = Cloud.config.HEIGHT; this.ctx.drawImage(imgSprite, this.spritePos.x,this.spritePos.y, sourceWidth,sourceHeight, this.xPos,this.yPos, sourceWidth,sourceHeight); this.ctx.restore(); }, updateClouds:function(speed) { var numClouds = Cloud.clouds.length; if(numClouds) { for(var i = numClouds - 1; i >= 0; i--) { Cloud.clouds[i].update(speed); } var lastCloud = Cloud.clouds[numClouds - 1]; if(numClouds < Cloud.config.MAX_CLOUDS && (DEFAULT_WIDTH - lastCloud.xPos) > lastCloud.cloudGap && Cloud.config.CLOUD_FREQUENCY > Math.random()) { this.addCloud(); } Cloud.clouds = Cloud.clouds.filter(function(obj){ return !obj.remove; }); } else { this.addCloud(); } }, update:function(speed) { if(!this.remove) { //向左移动 this.xPos -= Math.ceil(speed); this.draw(); if(!this.isVisible()) { this.remove = true; } } }, //判断云朵是否移出屏幕外 isVisible:function() { return this.xPos + Cloud.config.WIDTH > 0; }, addCloud:function () { var cloud = new Cloud(this.canvas,spriteDefinition.CLOUD,DEFAULT_WIDTH); Cloud.clouds.push(cloud); } }; //endregion //region NightMode NightMode.config = { FADE_SPEED: 0.035, //淡入淡出速度 HEIGHT: 40, //月亮高度 MOON_SPEED: 0.25, //月亮移动速度 NUM_STARS: 2, //星星数量 STAR_SIZE: 9, //星星宽度 STAR_SPEED: 0.3,//星星速度 STAR_MAX_Y: 70, //星星在画布上出现的位置 WIDTH: 20 //半个月度宽度 }; //月亮在不同时期有不同的位置 NightMode.phases = [140,120,100,60,40,20,0]; NightMode.invertTimer = 0; NightMode.inverted = false; NightMode.invertTrigger = false; NightMode.INVERT_FADE_DURATION = 5000; function NightMode(canvas,spritePos,containerWidth) { this.spritePos = spritePos; this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.containerWidth = containerWidth; this.xPos = containerWidth - 50; //月亮的x坐标 this.yPos = 30; //月亮的y坐标 this.currentPhase = 0; this.opacity = 0; this.stars = []; //用于存储星星 this.drawStars = false; //是否绘制星星 this.placeStars(); //放置星星 } NightMode.prototype = { update:function(activated) { //若夜晚模式处于激活状态且opacity为0时 //对月亮周期进行更新 if(activated && this.opacity == 0) { this.currentPhase++; if(this.currentPhase >= NightMode.phases.length) { this.currentPhase = 0; } } //淡入 if(activated && (this.opacity < 1 || this.opacity == 0)) { this.opacity += NightMode.config.FADE_SPEED; } else if(this.opacity > 0) {//淡出 this.opacity -= NightMode.config.FADE_SPEED; } //当opacity大于0时移动月亮位置 if(this.opacity > 0) { this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); //移动星星 if(this.drawStars) { for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.stars[i].x = this.updateXPos(this.stars[i].x,NightMode.config.STAR_SPEED); } } this.draw(); } else { this.opacity = 0; this.placeStars(); } this.drawStars = true; }, updateXPos: function(currentPos, speed) { if (currentPos < -NightMode.config.WIDTH) { currentPos = this.containerWidth; } else { currentPos -= speed; } return currentPos; }, draw:function() { //周期为3时画满月 var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : NightMode.config.WIDTH; var moonSourceHeight = NightMode.config.HEIGHT; //从雪碧图上获取月亮正确的形状 var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; var moonOutputWidth = moonSourceWidth; var starSize = NightMode.config.STAR_SIZE; var starSourceX = spriteDefinition.STAR.x; this.ctx.save(); //画布透明度也随之变化 this.ctx.globalAlpha = this.opacity; if (this.drawStars) { for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.ctx.drawImage(imgSprite, starSourceX, this.stars[i].sourceY, starSize, starSize, Math.round(this.stars[i].x), this.stars[i].y, NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE); } } this.ctx.drawImage(imgSprite, moonSourceX, this.spritePos.y, moonSourceWidth, moonSourceHeight, Math.round(this.xPos), this.yPos, moonOutputWidth, NightMode.config.HEIGHT); this.ctx.globalAlpha = 1; this.ctx.restore(); }, placeStars:function() { //将画布分为若干组 var segmentSize = Math.round(this.containerWidth /NightMode.config.NUM_STARS); for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.stars[i] = {}; //每组星星位置随机 this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); this.stars[i].sourceY = spriteDefinition.STAR.y + NightMode.config.STAR_SIZE * i; } }, invert:function(deltaTime) { this.update(NightMode.inverted); //黑夜持续时间5秒 if(NightMode.invertTimer > NightMode.INVERT_FADE_DURATION) { NightMode.invertTimer = 0; NightMode.invertTrigger = false; NightMode.inverted = a.classList.toggle(‘inverted‘,NightMode.invertTrigger); } else if(NightMode.invertTimer) { NightMode.invertTimer += deltaTime; } else { //每500帧触发黑夜,这里只是为了模拟效果,完整游戏中是每700米触发一次黑夜 NightMode.invertTrigger = !(gameFrame % 500); if(NightMode.invertTrigger && NightMode.invertTimer === 0) { NightMode.invertTimer += deltaTime; NightMode.inverted = a.classList.toggle(‘inverted‘,NightMode.invertTrigger); } } }, reset: function() { this.currentPhase = 0; this.opacity = 0; this.update(false); } }; //endregion //region Obstacle Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障碍物最大间距系数 Obstacle.MAX_OBSTACLE_LENGTH = 3; //障碍物的最大数量 Obstacle.obstacles = []; //存储障碍物的数组 Obstacle.obstacleHistory = []; //记录障碍物数组中障碍物的类型 Obstacle.MAX_OBSTACLE_DUPLICATION = 2; //障碍物的最大重复数量 //障碍物类型的相关配置 Obstacle.types = [ { type: ‘CACTUS_SMALL‘, //小仙人掌 width: 17, height: 35, yPos: 105, multipleSpeed: 4, minGap: 120, //最小间距 minSpeed: 0 //最低速度 }, { type: ‘CACTUS_LARGE‘, //大仙人掌 width: 25, height: 50, yPos: 90, multipleSpeed: 7, minGap: 120, minSpeed: 0 }, { type: ‘PTERODACTYL‘, //翼龙 width: 46, height: 40, yPos: [ 100, 75, 50 ], //有高、中、低三种高度 multipleSpeed: 999, minSpeed: 8.5, //最小速度 minGap: 150, numFrames: 2, //有两个动画帧 frameRate: 1000/6, //动画帧的切换速率 speedOffset: .8 //速度修正 } ]; /** * 绘制障碍物构造函数 * @param canvas * @param type 障碍物的类型 * @param spriteImgPos 雪碧图坐标 * @param dimensions 屏幕尺寸 * @param gapCoefficient 障碍物间隙 * @param speed 障碍物移动速度 * @param opt_xOffset 障碍物水平偏移量 * @constructor */ function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) { this.ctx = canvas.getContext(‘2d‘); this.spritePos = spriteImgPos; //障碍物类型(仙人掌、翼龙) this.typeConfig = type; this.gapCoefficient = gapCoefficient; //每个障碍物的数量(1~3) this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH); this.dimensions = dimensions; //表示该障碍物是否可以被移除 this.remove = false; //水平坐标 this.xPos = dimensions.WIDTH + (opt_xOffset || 0); this.yPos = 0; this.width = 0; this.gap = 0; this.speedOffset = 0; //速度修正 //障碍物的动画帧 this.currentFrame = 0; //动画帧切换的计时器 this.timer = 0; this.init(speed); } Obstacle.prototype = { init:function(speed) { //如果随机障碍物是翼龙,则只出现一只 //翼龙的multipleSpeed是999,远大于speed if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { this.size = 1; } //障碍物的总宽度等于单个障碍物的宽度乘以个数 this.width = this.typeConfig.width * this.size; //若障碍物的纵坐标是一个数组 //则随机选取一个 if (Array.isArray(this.typeConfig.yPos)) { var yPosConfig = this.typeConfig.yPos; this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; } else { this.yPos = this.typeConfig.yPos; } this.draw(); //对翼龙的速度进行修正,让它看起来有的飞得快一些,有些飞得慢一些 if (this.typeConfig.speedOffset) { this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : -this.typeConfig.speedOffset; } //障碍物之间的间隙,与游戏速度有关 this.gap = this.getGap(this.gapCoefficient, speed); }, //障碍物之间的间隔,gapCoefficient为间隔系数 getGap: function(gapCoefficient, speed) { var minGap = Math.round(this.width * speed + this.typeConfig.minGap * gapCoefficient); var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT); return getRandomNum(minGap, maxGap); }, //判断障碍物是否移出屏幕外 isVisible: function() { return this.xPos + this.width > 0; }, draw:function() { //障碍物宽高 var sourceWidth = this.typeConfig.width; var sourceHeight = this.typeConfig.height; //根据障碍物数量计算障碍物在雪碧图上的x坐标 //this.size的取值范围是1~3 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x; // 如果当前动画帧大于0,说明障碍物类型是翼龙 // 更新翼龙的雪碧图x坐标使其匹配第二帧动画 if (this.currentFrame > 0) { sourceX += sourceWidth * this.currentFrame; } this.ctx.drawImage(imgSprite, sourceX, this.spritePos.y, sourceWidth * this.size, sourceHeight, this.xPos, this.yPos, sourceWidth * this.size, sourceHeight); }, //单个障碍物的移动 update:function(deltaTime, speed) { //如果障碍物还没有移出屏幕外 if (!this.remove) { //如果有速度修正则修正速度 if (this.typeConfig.speedOffset) { speed += this.speedOffset; } //更新x坐标 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); // Update frame if (this.typeConfig.numFrames) { this.timer += deltaTime; if (this.timer >= this.typeConfig.frameRate) { //在两个动画帧之间来回切换以达到动画效果 this.currentFrame = this.currentFrame == this.typeConfig.numFrames - 1 ? 0 : this.currentFrame + 1; this.timer = 0; } } this.draw(); if (!this.isVisible()) { this.remove = true; } } }, //管理多个障碍物移动 updateObstacles: function(deltaTime, currentSpeed) { //保存一个障碍物列表的副本 var updatedObstacles = Obstacle.obstacles.slice(0); for (var i = 0; i < Obstacle.obstacles.length; i++) { var obstacle = Obstacle.obstacles[i]; obstacle.update(deltaTime, currentSpeed); //移除被标记为删除的障碍物 if (obstacle.remove) { updatedObstacles.shift(); } } Obstacle.obstacles = updatedObstacles; if(Obstacle.obstacles.length > 0) { //获取障碍物列表中的最后一个障碍物 var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1]; //若满足条件则添加障碍物 if (lastObstacle && lastObstacle.isVisible() && (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) < this.dimensions.WIDTH) { this.addNewObstacle(currentSpeed); } } else {//若障碍物列表中没有障碍物则立即添加 this.addNewObstacle(currentSpeed); } }, //随机添加障碍 addNewObstacle:function (currentSpeed) { //随机选取一种类型的障碍 var obstacleTypeIndex = getRandomNum(0,Obstacle.types.length - 1); var obstacleType = Obstacle.types[obstacleTypeIndex]; //检查随机取到的障碍物类型是否与前两个重复 //或者检查其速度是否合法,这样可以保证游戏在低速时不出现翼龙 //如果检查不通过,则重新再选一次直到通过为止 if(this.duplicateObstacleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) { this.addNewObstacle(currentSpeed); } else { //检查通过后,获取其雪碧图中的坐标 var obstacleSpritePos = this.spritePos[obstacleType.type]; //生成新的障碍物并存入数组 Obstacle.obstacles.push(new Obstacle(c,obstacleType,obstacleSpritePos,this.dimensions, this.gapCoefficient,currentSpeed,obstacleType.width)); //同时将障碍物的类型存入history数组 Obstacle.obstacleHistory.unshift(obstacleType.type); } //若history数组的长度大于1,则清空最前面的两个 if (Obstacle.obstacleHistory.length > 1) { Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION); } }, //检查障碍物是否超过允许的最大重复数 duplicateObstacleCheck:function(nextObstacleType) { var duplicateCount = 0; //与history数组中的障碍物类型比较,最大只允许重复两次 for(var i = 0; i < Obstacle.obstacleHistory.length; i++) { duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0; } return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION; } }; //endregion window.onload = function () { var h = new HorizonLine(c,spriteDefinition.HORIZON); var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH); var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH); var obstacle = new Obstacle(c,Obstacle.types[0],spriteDefinition,{WIDTH:600},0.6,1); var startTime = 0; var deltaTime; var speed = 3; (function draw(time) { gameFrame++; if(speed < 13.5) { speed += 0.01; } ctx.clearRect(0,0,600,150); time = time || 0; deltaTime = time - startTime; h.update(deltaTime,speed); cloud.updateClouds(0.2); night.invert(deltaTime); obstacle.updateObstacles(deltaTime,speed); startTime = time; window.requestAnimationFrame(draw,c); })(); };// ]]></script>

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