摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究四隨機(jī)繪制云朵實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn)仙人掌翼龍障礙物的繪制游戲速度的改變障礙物的類型有兩種仙人掌和翼龍。
文章首發(fā)于我的 GitHub 博客前言
上一篇文章:《Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵》 實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn):1、仙人掌、翼龍障礙物的繪制 2、游戲速度的改變
障礙物的類型有兩種:仙人掌和翼龍。翼龍每次只能有一只,高度隨機(jī),仙人掌一次可以繪制多個(gè),一次繪制的數(shù)目隨機(jī)。對于繪制障礙物的關(guān)鍵是:保證合適的大小和間隔。例如:不能在游戲剛開始速度很慢的時(shí)候就繪制一個(gè)很寬的障礙物,否則是跳不過去的。也不能在游戲速度較快的情況下,兩個(gè)障礙物間隔生成的很窄,否則當(dāng)跳過第一個(gè)障礙物后,一定會(huì)撞到下一個(gè)障礙物。
有關(guān)障礙物的碰撞檢測部分這里先不實(shí)現(xiàn),會(huì)放在后面的多帶帶一章來講。障礙物類 Obstacle
定義障礙物類 Obstacle:
/** * 障礙物類 * @param {HTMLCanvasElement} canvas 畫布 * @param {String} type 障礙物類型 * @param {Object} spriteImgPos 在雪碧圖中的位置 * @param {Object} dimensions 畫布尺寸 * @param {Number} gapCoefficient 間隙系數(shù) * @param {Number} speed 速度 * @param {Number} opt_xOffset x 坐標(biāo)修正 */ function Obstacle(canvas, type, spriteImgPos, dimensions, gapCoefficient, speed, opt_xOffset) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.typeConfig = type; // 障礙物類型 this.spritePos = spriteImgPos; // 在雪碧圖中的位置 this.gapCoefficient = gapCoefficient; // 間隔系數(shù) this.dimensions = dimensions; // 每組障礙物的數(shù)量(隨機(jī) 1~3 個(gè)) this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); this.xPos = dimensions.WIDTH + (opt_xOffset || 0); this.yPos = 0; this.remove = false; // 是否可以被刪除 this.gap = 0; // 間隙 this.speedOffset = 0; // 速度修正 // 非靜態(tài)障礙物的屬性 this.currentFrame = 0; // 當(dāng)前動(dòng)畫幀 this.timer = 0; // 動(dòng)畫幀切換計(jì)時(shí)器 this.init(speed); }
相關(guān)的配置參數(shù):
Obstacle.MAX_GAP_COEFFICIENT = 1.5; // 最大間隙系數(shù) Obstacle.MAX_OBSTACLE_LENGTH = 3; // 每組障礙物的最大數(shù)量 Obstacle.types = [{ type: "CACTUS_SMALL", // 小仙人掌 width: 17, height: 35, yPos: 105, // 在 canvas 上的 y 坐標(biāo) 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 ], // y 坐標(biāo)不固定 multipleSpeed: 999, minSpeed: 8.5, minGap: 150, numFrames: 2, // 兩個(gè)動(dòng)畫幀 frameRate: 1000 / 6, // 幀率(一幀的時(shí)間) speedOffset: 0.8, // 速度修正 }];
補(bǔ)充本篇文章中會(huì)用到的一些數(shù)據(jù):
function Runner(containerSelector, opt_config) { // ... + this.runningTime = 0; // 游戲運(yùn)行的時(shí)間 } Runner.config = { // ... + GAP_COEFFICIENT: 0.6, // 障礙物間隙系數(shù) + MAX_OBSTACLE_DUPLICATION: 2, // 障礙物相鄰的最大重復(fù) + CLEAR_TIME: 3000, // 游戲開始后,等待三秒再繪制障礙物 + MAX_SPEED: 13, // 游戲的最大速度 + ACCELERATION: 0.001, // 加速度 }; Runner.spriteDefinition = { LDPI: { // ... + CACTUS_SMALL: {x: 228, y: 2}, // 小仙人掌 + CACTUS_LARGE: {x: 332, y: 2}, // 大仙人掌 + PTERODACTYL: {x: 134, y: 2}, // 翼龍 }, };
在 Obstacle 原型鏈上添加方法:
Obstacle.prototype = { // 初始化障礙物 init: function (speed) { // 這里是為了確保剛開始游戲速度慢時(shí),不會(huì)生成較大的障礙物和翼龍 // 否則速度慢時(shí),生成較大的障礙物或翼龍是跳不過去的 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; // 隨機(jī)高度 this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; } else { this.yPos = this.typeConfig.yPos; } this.draw(); // 對于速度與地面不同的障礙物(翼龍)進(jìn)行速度修正 // 使得有的速度看起來快一些,有的看起來慢一些 if (this.typeConfig.speedOffset) { this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : -this.typeConfig.speedOffset; } // 障礙物的間隙隨游戲速度變化而改變 this.gap = this.getGap(this.gapCoefficient, speed); }, /** * 獲取障礙物的間隙 * @param {Number} gapCoefficient 間隙系數(shù) * @param {Number} speed 速度 */ 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); }, // 繪制障礙物 draw: function () { var sourceWidth = this.typeConfig.width; var sourceHeight = this.typeConfig.height; // 根據(jù)每組障礙物的數(shù)量計(jì)算障礙物在雪碧圖上的坐標(biāo) var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x; // 如果存在動(dòng)畫幀,則計(jì)算當(dāng)前動(dòng)畫幀在雪碧圖中的坐標(biāo) if (this.currentFrame > 0) { sourceX += sourceWidth * this.currentFrame; } this.ctx.drawImage( Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size, sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size, this.typeConfig.height ); }, // 更新障礙物 update: function (deltaTime, speed) { if (!this.remove) { // 修正速度 if (this.typeConfig.speedOffset) { speed += this.speedOffset; } this.xPos -= Math.floor((speed * FPS / 1000) * Math.round(deltaTime)); // 如果有動(dòng)畫幀,則更新 if (this.typeConfig.numFrames) { this.timer += deltaTime; if (this.timer >= this.typeConfig.frameRate) { // 第一幀 currentFrame 為 0,第二幀 currentFrame 為 1 this.currentFrame = this.currentFrame == this.typeConfig.numFrames - 1 ? 0 : this.currentFrame + 1; this.timer = 0; } } this.draw(); // 標(biāo)記移出畫布的障礙物 if (!this.isVisible()) { this.remove = true; } } }, // 障礙物是否還在畫布中 isVisible: function () { return this.xPos + this.width > 0; }, };
定義好 Obstacle 類之后,需要通過 Horizon 類來調(diào)用。首先需要定義兩個(gè)變量來存儲障礙物和障礙物的類型:
- function Horizon(canvas, spritePos, dimensions) { + function Horizon(canvas, spritePos, dimensions, gapCoefficient) { this.canvas = canvas; this.ctx = this.canvas.getContext("2d"); this.spritePos = spritePos; this.dimensions = dimensions; + this.gapCoefficient = gapCoefficient; + this.obstacles = []; // 存儲障礙物 + this.obstacleHistory = []; // 記錄存儲的障礙物的類型 // 云的頻率 this.cloudFrequency = Cloud.config.CLOUD_FREQUENCY; // ... }
修改初始化 Horizon 類時(shí)傳的參數(shù):
Runner.prototype = { init: function () { // ... + // 加載背景類 Horizon - this.horizon = new Horizon(this.canvas, this.spriteDef, - this.dimensions); + this.horizon = new Horizon(this.canvas, this.spriteDef, + this.dimensions, this.config.GAP_COEFFICIENT); }, };
定義添加障礙物的方法:
Horizon.prototype = { addNewObstacle: function(currentSpeed) { // 隨機(jī)障礙物 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); var obstacleType = Obstacle.types[obstacleTypeIndex]; // 檢查當(dāng)前添加的障礙物與前面障礙物的重復(fù)次數(shù)是否符合要求 // 如果當(dāng)前的速度小于障礙物的速度,證明障礙物是翼龍(其他障礙物速度都是 0) // 添加的障礙物是翼龍,并且當(dāng)前速度小于翼龍的速度,則重新添加(保證低速不出現(xiàn)翼龍) if (this.duplicateObstacleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) { this.addNewObstacle(currentSpeed); } else { // 通過檢查后,存儲新添加的障礙物 var obstacleSpritePos = this.spritePos[obstacleType.type]; // 存儲障礙物 this.obstacles.push(new Obstacle(this.canvas, obstacleType, obstacleSpritePos, this.dimensions, this.gapCoefficient, currentSpeed, obstacleType.width)); // 存儲障礙物類型 this.obstacleHistory.unshift(obstacleType.type); // 若 history 數(shù)組長度大于 1, 清空最前面兩個(gè)數(shù)據(jù) if (this.obstacleHistory.length > 1) { this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); } } }, /** * 檢查當(dāng)前障礙物前面的障礙物的重復(fù)次數(shù)是否大于等于最大重復(fù)次數(shù) * @param {String} nextObstacleType 障礙物類型 */ duplicateObstacleCheck: function(nextObstacleType) { var duplicateCount = 0; // 重復(fù)次數(shù) // 根據(jù)存儲的障礙物類型來判斷障礙物的重復(fù)次數(shù) for (var i = 0; i < this.obstacleHistory.length; i++) { duplicateCount = this.obstacleHistory[i] == nextObstacleType ? duplicateCount + 1 : 0; } return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; }, };
然后定義更新障礙物的方法:
Horizon.prototype = { updateObstacles: function (deltaTime, currentSpeed) { // 復(fù)制存儲的障礙物 var updatedObstacles = this.obstacles.slice(0); for (var i = 0; i < this.obstacles.length; i++) { var obstacle = this.obstacles[i]; obstacle.update(deltaTime, currentSpeed); // 刪除被標(biāo)記的障礙物 if (obstacle.remove) { updatedObstacles.shift(); } } // 更新存儲的障礙物 this.obstacles = updatedObstacles; if (this.obstacles.length > 0) { var lastObstacle = this.obstacles[this.obstacles.length - 1]; // 滿足添加障礙物的條件 if (lastObstacle && !lastObstacle.followingObstacleCreated && lastObstacle.isVisible() && (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) < this.dimensions.WIDTH) { this.addNewObstacle(currentSpeed); lastObstacle.followingObstacleCreated = true; } } else { // 沒有存儲障礙物,直接添加 this.addNewObstacle(currentSpeed); } }, };
調(diào)用 updateObstacles 方法:
Horizon.prototype = { - update: function (deltaTime, currentSpeed) { + update: function (deltaTime, currentSpeed, updateObstacles) { this.horizonLine.update(deltaTime, currentSpeed); this.updateCloud(deltaTime, currentSpeed); + if (updateObstacles) { + this.updateObstacles(deltaTime, currentSpeed); + } }, };
最后通過 Runner 上的 update 方法來調(diào)用 Horizon 的 update 方法:
Runner.prototype = { update: function () { // ... if (this.playing) { this.clearCanvas(); + this.runningTime += deltaTime; + var hasObstacles = this.runningTime > this.config.CLEAR_TIME; // 剛開始 this.playingIntro 未定義 !this.playingIntro 為真 if (!this.playingIntro) { this.playIntro(); // 執(zhí)行開場動(dòng)畫 } // 直到開場動(dòng)畫結(jié)束再移動(dòng)地面 if (this.playingIntro) { - this.horizon.update(0, this.currentSpeed); + this.horizon.update(0, this.currentSpeed, hasObstacles); } else { deltaTime = !this.activated ? 0 : deltaTime; - this.horizon.update(deltaTime, this.currentSpeed); + this.horizon.update(deltaTime, this.currentSpeed, hasObstacles); } } // ... }, };
到這里,就實(shí)現(xiàn)了障礙物的基本繪制。不過由于速度一直恒定并且較小,所以不會(huì)繪制較大的障礙物。下面我們給游戲加上加速度來實(shí)現(xiàn)速度的不斷加快(有最大值)。
修改 Runner 的 update 方法:
Runner.prototype = { update: function () { // ... if (this.playing) { // ... + if (this.currentSpeed < this.config.MAX_SPEED) { + this.currentSpeed += this.config.ACCELERATION; // 速度增加一個(gè)加速度的值 + } } // ... }, };
這樣就完整實(shí)現(xiàn)了障礙物的繪制和移動(dòng)。效果如下:
查看添加或修改的代碼,戳這里
Demo 體驗(yàn)地址:https://liuyib.github.io/blog/demo/game/google-dino/add-obstacle/
上一篇 | 下一篇 | Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵 | Chrome 小恐龍游戲源碼探究六 -- 記錄游戲分?jǐn)?shù) |
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103881.html
摘要:首先是繪制靜態(tài)的地面。上一篇下一篇無小恐龍游戲源碼探究二讓地面動(dòng)起來 文章首發(fā)于我的 GitHub 博客 目錄 Chrome 小恐龍游戲源碼探究一 -- 繪制靜態(tài)地面 Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來 Chrome 小恐龍游戲源碼探究三 -- 進(jìn)入街機(jī)模式 Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵 Chrome 小恐龍游戲源碼探究五 -- 隨機(jī)繪...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究五隨機(jī)繪制障礙實(shí)現(xiàn)了障礙物仙人掌和翼龍的繪制。在游戲中,小恐龍移動(dòng)的距離就是游戲的分?jǐn)?shù)。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究五 -- 隨機(jī)繪制障礙》 實(shí)現(xiàn)了障礙物仙人掌和翼龍的繪制。這一篇將實(shí)現(xiàn)當(dāng)前分?jǐn)?shù)、最高分?jǐn)?shù)的記錄和繪制。 在游戲中,小恐龍移動(dòng)的距離就是游戲的分?jǐn)?shù)。分?jǐn)?shù)每達(dá) 1...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究三進(jìn)入街機(jī)模式實(shí)現(xiàn)了開場動(dòng)畫和街機(jī)模式。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究三 -- 進(jìn)入街機(jī)模式》 實(shí)現(xiàn)了開場動(dòng)畫和街機(jī)模式。這一篇文章中,將實(shí)現(xiàn)云朵的隨機(jī)繪制。 云朵類 Cloud 定義云朵類 Cloud: /** * 云朵類 * @param {HTMLCanvasEle...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究八奔跑的小恐龍實(shí)現(xiàn)了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實(shí)現(xiàn)游戲的碰撞檢測。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍》實(shí)現(xiàn)了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實(shí)現(xiàn)游戲的碰撞檢測。 碰撞檢測原理 這個(gè)游戲采用的檢測方法是盒子碰撞,這種檢...
摘要:例如,將函數(shù)修改為小恐龍眨眼這樣小恐龍會(huì)不停的眨眼睛。小恐龍的開場動(dòng)畫下面來實(shí)現(xiàn)小恐龍對鍵盤按鍵的響應(yīng)。接下來還需要更新動(dòng)畫幀才能實(shí)現(xiàn)小恐龍的奔跑動(dòng)畫。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替》實(shí)現(xiàn)了游戲晝夜模式的交替,這一篇文章中,將實(shí)現(xiàn):1、小恐龍的繪制 2、鍵盤對小恐龍的控制 3、頁面失焦后,重新聚焦會(huì)重置...
閱讀 1424·2021-11-22 09:34
閱讀 1386·2021-09-22 14:57
閱讀 3418·2021-09-10 10:50
閱讀 1408·2019-08-30 15:54
閱讀 3700·2019-08-29 17:02
閱讀 3483·2019-08-29 12:54
閱讀 2623·2019-08-27 10:57
閱讀 3328·2019-08-26 12:24