摘要:文章首發于我的博客前言上一篇文章小恐龍游戲源碼探究四隨機繪制云朵實現了云朵的隨機繪制,這一篇文章中將實現仙人掌翼龍障礙物的繪制游戲速度的改變障礙物的類型有兩種仙人掌和翼龍。
文章首發于我的 GitHub 博客前言
上一篇文章:《Chrome 小恐龍游戲源碼探究四 -- 隨機繪制云朵》 實現了云朵的隨機繪制,這一篇文章中將實現:1、仙人掌、翼龍障礙物的繪制 2、游戲速度的改變
障礙物的類型有兩種:仙人掌和翼龍。翼龍每次只能有一只,高度隨機,仙人掌一次可以繪制多個,一次繪制的數目隨機。對于繪制障礙物的關鍵是:保證合適的大小和間隔。例如:不能在游戲剛開始速度很慢的時候就繪制一個很寬的障礙物,否則是跳不過去的。也不能在游戲速度較快的情況下,兩個障礙物間隔生成的很窄,否則當跳過第一個障礙物后,一定會撞到下一個障礙物。
有關障礙物的碰撞檢測部分這里先不實現,會放在后面的多帶帶一章來講。障礙物類 Obstacle
定義障礙物類 Obstacle:
/** * 障礙物類 * @param {HTMLCanvasElement} canvas 畫布 * @param {String} type 障礙物類型 * @param {Object} spriteImgPos 在雪碧圖中的位置 * @param {Object} dimensions 畫布尺寸 * @param {Number} gapCoefficient 間隙系數 * @param {Number} speed 速度 * @param {Number} opt_xOffset x 坐標修正 */ 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; // 間隔系數 this.dimensions = dimensions; // 每組障礙物的數量(隨機 1~3 個) 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; // 速度修正 // 非靜態障礙物的屬性 this.currentFrame = 0; // 當前動畫幀 this.timer = 0; // 動畫幀切換計時器 this.init(speed); }
相關的配置參數:
Obstacle.MAX_GAP_COEFFICIENT = 1.5; // 最大間隙系數 Obstacle.MAX_OBSTACLE_LENGTH = 3; // 每組障礙物的最大數量 Obstacle.types = [{ type: "CACTUS_SMALL", // 小仙人掌 width: 17, height: 35, yPos: 105, // 在 canvas 上的 y 坐標 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 坐標不固定 multipleSpeed: 999, minSpeed: 8.5, minGap: 150, numFrames: 2, // 兩個動畫幀 frameRate: 1000 / 6, // 幀率(一幀的時間) speedOffset: 0.8, // 速度修正 }];
補充本篇文章中會用到的一些數據:
function Runner(containerSelector, opt_config) { // ... + this.runningTime = 0; // 游戲運行的時間 } Runner.config = { // ... + GAP_COEFFICIENT: 0.6, // 障礙物間隙系數 + MAX_OBSTACLE_DUPLICATION: 2, // 障礙物相鄰的最大重復 + 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) { // 這里是為了確保剛開始游戲速度慢時,不會生成較大的障礙物和翼龍 // 否則速度慢時,生成較大的障礙物或翼龍是跳不過去的 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); }, /** * 獲取障礙物的間隙 * @param {Number} gapCoefficient 間隙系數 * @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; // 根據每組障礙物的數量計算障礙物在雪碧圖上的坐標 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x; // 如果存在動畫幀,則計算當前動畫幀在雪碧圖中的坐標 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)); // 如果有動畫幀,則更新 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(); // 標記移出畫布的障礙物 if (!this.isVisible()) { this.remove = true; } } }, // 障礙物是否還在畫布中 isVisible: function () { return this.xPos + this.width > 0; }, };
定義好 Obstacle 類之后,需要通過 Horizon 類來調用。首先需要定義兩個變量來存儲障礙物和障礙物的類型:
- 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 類時傳的參數:
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) { // 隨機障礙物 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1); var obstacleType = Obstacle.types[obstacleTypeIndex]; // 檢查當前添加的障礙物與前面障礙物的重復次數是否符合要求 // 如果當前的速度小于障礙物的速度,證明障礙物是翼龍(其他障礙物速度都是 0) // 添加的障礙物是翼龍,并且當前速度小于翼龍的速度,則重新添加(保證低速不出現翼龍) 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 數組長度大于 1, 清空最前面兩個數據 if (this.obstacleHistory.length > 1) { this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); } } }, /** * 檢查當前障礙物前面的障礙物的重復次數是否大于等于最大重復次數 * @param {String} nextObstacleType 障礙物類型 */ duplicateObstacleCheck: function(nextObstacleType) { var duplicateCount = 0; // 重復次數 // 根據存儲的障礙物類型來判斷障礙物的重復次數 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) { // 復制存儲的障礙物 var updatedObstacles = this.obstacles.slice(0); for (var i = 0; i < this.obstacles.length; i++) { var obstacle = this.obstacles[i]; obstacle.update(deltaTime, currentSpeed); // 刪除被標記的障礙物 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); } }, };
調用 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 方法來調用 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(); // 執行開場動畫 } // 直到開場動畫結束再移動地面 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); } } // ... }, };
到這里,就實現了障礙物的基本繪制。不過由于速度一直恒定并且較小,所以不會繪制較大的障礙物。下面我們給游戲加上加速度來實現速度的不斷加快(有最大值)。
修改 Runner 的 update 方法:
Runner.prototype = { update: function () { // ... if (this.playing) { // ... + if (this.currentSpeed < this.config.MAX_SPEED) { + this.currentSpeed += this.config.ACCELERATION; // 速度增加一個加速度的值 + } } // ... }, };
這樣就完整實現了障礙物的繪制和移動。效果如下:
查看添加或修改的代碼,戳這里
Demo 體驗地址:https://liuyib.github.io/blog/demo/game/google-dino/add-obstacle/
上一篇 | 下一篇 | Chrome 小恐龍游戲源碼探究四 -- 隨機繪制云朵 | Chrome 小恐龍游戲源碼探究六 -- 記錄游戲分數 |
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103881.html
摘要:首先是繪制靜態的地面。上一篇下一篇無小恐龍游戲源碼探究二讓地面動起來 文章首發于我的 GitHub 博客 目錄 Chrome 小恐龍游戲源碼探究一 -- 繪制靜態地面 Chrome 小恐龍游戲源碼探究二 -- 讓地面動起來 Chrome 小恐龍游戲源碼探究三 -- 進入街機模式 Chrome 小恐龍游戲源碼探究四 -- 隨機繪制云朵 Chrome 小恐龍游戲源碼探究五 -- 隨機繪...
摘要:文章首發于我的博客前言上一篇文章小恐龍游戲源碼探究五隨機繪制障礙實現了障礙物仙人掌和翼龍的繪制。在游戲中,小恐龍移動的距離就是游戲的分數。 文章首發于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究五 -- 隨機繪制障礙》 實現了障礙物仙人掌和翼龍的繪制。這一篇將實現當前分數、最高分數的記錄和繪制。 在游戲中,小恐龍移動的距離就是游戲的分數。分數每達 1...
摘要:文章首發于我的博客前言上一篇文章小恐龍游戲源碼探究三進入街機模式實現了開場動畫和街機模式。 文章首發于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究三 -- 進入街機模式》 實現了開場動畫和街機模式。這一篇文章中,將實現云朵的隨機繪制。 云朵類 Cloud 定義云朵類 Cloud: /** * 云朵類 * @param {HTMLCanvasEle...
摘要:文章首發于我的博客前言上一篇文章小恐龍游戲源碼探究八奔跑的小恐龍實現了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實現游戲的碰撞檢測。 文章首發于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍》實現了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實現游戲的碰撞檢測。 碰撞檢測原理 這個游戲采用的檢測方法是盒子碰撞,這種檢...
摘要:例如,將函數修改為小恐龍眨眼這樣小恐龍會不停的眨眼睛。小恐龍的開場動畫下面來實現小恐龍對鍵盤按鍵的響應。接下來還需要更新動畫幀才能實現小恐龍的奔跑動畫。 文章首發于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替》實現了游戲晝夜模式的交替,這一篇文章中,將實現:1、小恐龍的繪制 2、鍵盤對小恐龍的控制 3、頁面失焦后,重新聚焦會重置...
閱讀 1410·2021-11-22 09:34
閱讀 1381·2021-09-22 14:57
閱讀 3410·2021-09-10 10:50
閱讀 1389·2019-08-30 15:54
閱讀 3693·2019-08-29 17:02
閱讀 3476·2019-08-29 12:54
閱讀 2618·2019-08-27 10:57
閱讀 3322·2019-08-26 12:24