摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究八奔跑的小恐龍實(shí)現(xiàn)了小恐龍的繪制以及鍵盤(pán)對(duì)小恐龍的控制,這一篇文章中將實(shí)現(xiàn)游戲的碰撞檢測(cè)。
文章首發(fā)于我的 GitHub 博客前言
上一篇文章:《Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍》實(shí)現(xiàn)了小恐龍的繪制以及鍵盤(pán)對(duì)小恐龍的控制,這一篇文章中將實(shí)現(xiàn)游戲的碰撞檢測(cè)。
碰撞檢測(cè)原理這個(gè)游戲采用的檢測(cè)方法是盒子碰撞,這種檢測(cè)方法最大的好處就是簡(jiǎn)單,但是缺點(diǎn)是不夠精確。
首先,如果將小恐龍和障礙物分別看作兩個(gè)大的盒子,那么進(jìn)行碰撞檢測(cè)的效果如下:
可以看出,兩個(gè)盒子雖然有重疊部分,但是實(shí)際小恐龍并沒(méi)有和障礙物相撞。
所以想要進(jìn)行更精確的檢測(cè),需要把物體拆分成多個(gè)較小的盒子。例如:
但是拆分的時(shí)候也不能過(guò)于細(xì)致,否則運(yùn)算的時(shí)候,很影響性能。
這個(gè)游戲中所進(jìn)行的必要拆分如圖所示:
這里值得一提的是,當(dāng)小恐龍俯身時(shí),只需要將其拆成一個(gè)大的盒子。因?yàn)楫?dāng)小恐龍俯身時(shí),可以產(chǎn)生碰撞的部分只有前面,而在小恐龍前面碰撞一定會(huì)碰到它的頭部。畢竟現(xiàn)在這個(gè)游戲中還沒(méi)有那么矮小的障礙物,以至于剛好碰到小恐龍俯身時(shí)的下巴。
這就提示我們,如果想要對(duì)游戲進(jìn)行擴(kuò)展,添加新的障礙物,就要考慮到小恐龍當(dāng)前的碰撞盒子是否需要進(jìn)行調(diào)整,要確保當(dāng)前的碰撞盒子可以正確檢測(cè)出所有情況。
生成碰撞盒子游戲中使用 CollisionBox 類(lèi)來(lái)生成碰撞盒子:
/** * 用于生成碰撞盒子 * @param {Number} x X 坐標(biāo) * @param {Number} y Y坐標(biāo) * @param {Number} w 寬度 * @param {Number} h 高度 */ function CollisionBox(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; };
小恐龍的碰撞盒子如下:
// 小恐龍的碰撞盒子 Trex.collisionBoxes = { DUCKING: [ new CollisionBox(1, 18, 55, 25) ], RUNNING: [ new CollisionBox(22, 0, 17, 16), new CollisionBox(1, 18, 30, 9), new CollisionBox(10, 35, 14, 8), new CollisionBox(1, 24, 29, 5), new CollisionBox(5, 30, 21, 4), new CollisionBox(9, 34, 15, 4) ] };
障礙物的碰撞盒子如下:
Obstacle.types = [{ type: "CACTUS_SMALL", // 小仙人掌 width: 17, height: 35, yPos: 105, // 在 canvas 上的 y 坐標(biāo) multipleSpeed: 4, minGap: 120, // 最小間距 minSpeed: 0, // 最低速度 + collisionBoxes: [ // 碰撞盒子 + new CollisionBox(0, 7, 5, 27), + new CollisionBox(4, 0, 6, 34), + new CollisionBox(10, 4, 7, 14), + ], }, { type: "CACTUS_LARGE", // 大仙人掌 width: 25, height: 50, yPos: 90, multipleSpeed: 7, minGap: 120, minSpeed: 0, + collisionBoxes: [ // 碰撞盒子 + new CollisionBox(0, 12, 7, 38), + new CollisionBox(8, 0, 7, 49), + new CollisionBox(13, 10, 10, 38), + ], }, { 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)畫(huà)幀 frameRate: 1000 / 6, // 幀率(一幀的時(shí)間) speedOffset: 0.8, // 速度修正 + collisionBoxes: [ // 碰撞盒子 + new CollisionBox(15, 15, 16, 5), + new CollisionBox(18, 21, 24, 6), + new CollisionBox(2, 14, 4, 3), + new CollisionBox(6, 10, 4, 7), + new CollisionBox(10, 8, 6, 9), + ], }];添加碰撞盒子
在 Obstacle 類(lèi)上添加屬性:
function Obstacle(canvas, type, spriteImgPos, dimensions, gapCoefficient, speed, opt_xOffset) { //... + this.collisionBoxes = []; // 存儲(chǔ)碰撞盒子 // ... }
添加方法,用于拷貝障礙物的碰撞盒子:
Obstacle.prototype = { // 復(fù)制碰撞盒子 cloneCollisionBoxes: function() { var collisionBoxes = this.typeConfig.collisionBoxes; for (var i = collisionBoxes.length - 1; i >= 0; i--) { this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width, collisionBoxes[i].height); } }, };
然后,調(diào)用這個(gè)方法來(lái)初始化障礙物的碰撞盒子:
Obstacle.prototype = { init: function () { + this.cloneCollisionBoxes(); // ... }, };
這里需要對(duì)仙人掌中間的碰撞盒子進(jìn)行調(diào)整:
Obstacle.prototype = { init: function () { // ... + // 調(diào)整中間的碰撞盒子的大小 + // ____ ______ ________ + // _| |-| _| |-| _| |-| + // | |<->| | | |<--->| | | |<----->| | + // | | 1 | | | | 2 | | | | 3 | | + // |_|___|_| |_|_____|_| |_|_______|_| + // + if (this.size > 1) { + this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - + this.collisionBoxes[2].width; + this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; + } // ... }, };碰撞檢測(cè)
首先,檢測(cè)矩形四個(gè)邊的相對(duì)位置,來(lái)判斷兩個(gè)矩形是否相交:
/** * 比較兩個(gè)矩形是否相交 * @param {CollisionBox} tRexBox 小恐龍的碰撞盒子 * @param {CollisionBox} obstacleBox 障礙物的碰撞盒子 */ function boxCompare(tRexBox, obstacleBox) { var crashed = false; // 兩個(gè)矩形相交 if (tRexBox.x < obstacleBox.x + obstacleBox.width && tRexBox.x + tRexBox.width > obstacleBox.x && tRexBox.y < obstacleBox.y + obstacleBox.height && tRexBox.height + tRexBox.y > obstacleBox.y) { crashed = true; } return crashed; };
然后調(diào)用這個(gè)方法,判斷小恐龍和障礙物是否碰撞的邏輯如下:
/** * 檢測(cè)盒子是否碰撞 * @param {Object} obstacle 障礙物 * @param {Object} tRex 小恐龍 * @param {HTMLCanvasContext} opt_canvasCtx 畫(huà)布上下文 */ function checkForCollision(obstacle, tRex, opt_canvasCtx) { // 調(diào)整碰撞盒子的邊界,因?yàn)樾】铸埡驼系K物有 1 像素的白邊 var tRexBox = new CollisionBox( // 小恐龍最外層的碰撞盒子 tRex.xPos + 1, tRex.yPos + 1, tRex.config.WIDTH - 2, tRex.config.HEIGHT - 2); var obstacleBox = new CollisionBox( // 障礙物最外層的碰撞盒子 obstacle.xPos + 1, obstacle.yPos + 1, obstacle.typeConfig.width * obstacle.size - 2, obstacle.typeConfig.height - 2); // 繪制調(diào)試邊框 if (opt_canvasCtx) { drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); } // 檢查最外層的盒子是否碰撞 if (boxCompare(tRexBox, obstacleBox)) { var collisionBoxes = obstacle.collisionBoxes; // 小恐龍有兩種碰撞盒子,分別對(duì)應(yīng)小恐龍站立狀態(tài)和低頭狀態(tài) var tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING; // 檢測(cè)里面小的盒子是否碰撞 for (var t = 0; t < tRexCollisionBoxes.length; t++) { for (var i = 0; i < collisionBoxes.length; i++) { // 調(diào)整碰撞盒子的實(shí)際位置(除去小恐龍和障礙物上 1 像素的白邊) var adjTrexBox = createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); var adjObstacleBox = createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); var crashed = boxCompare(adjTrexBox, adjObstacleBox); // 繪制調(diào)試邊框 if (opt_canvasCtx) { drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox); } if (crashed) { return [adjTrexBox, adjObstacleBox]; } } } } return false; }; /** * 調(diào)整碰撞盒子 * @param {!CollisionBox} box 原始的盒子 * @param {!CollisionBox} adjustment 要調(diào)整成的盒子 * @return {CollisionBox} 被調(diào)整的盒子對(duì)象 */ function createAdjustedCollisionBox(box, adjustment) { return new CollisionBox( box.x + adjustment.x, box.y + adjustment.y, box.width, box.height); }; /** * 繪制碰撞盒子的邊框 * @param {HTMLCanvasContext} canvasCtx canvas 上下文 * @param {CollisionBox} tRexBox 小恐龍的碰撞盒子 * @param {CollisionBox} obstacleBox 障礙物的碰撞盒子 */ function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) { canvasCtx.save(); canvasCtx.strokeStyle = "#f00"; canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height); canvasCtx.strokeStyle = "#0f0"; canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y, obstacleBox.width, obstacleBox.height); canvasCtx.restore(); };
其中 drawCollisionBoxes 方法是 debug 時(shí)用的,用于顯示碰撞盒子的邊框。
上面的代碼中,對(duì)碰撞檢測(cè)的計(jì)算進(jìn)行了優(yōu)化:首先判斷小恐龍和障礙物最外層的盒子有沒(méi)有碰撞,當(dāng)它們最外層的盒子碰撞后,再計(jì)算里面的小盒子是否碰撞。這樣和直接計(jì)算所有盒子是否碰撞比起來(lái),性能要好很多。
然后,調(diào)用 checkForCollision 方法:
Runner.prototype = { update: function () { // ... if (this.playing) { // ... + // 碰撞檢測(cè) + var collision = hasObstacles && + checkForCollision(this.horizon.obstacles[0], this.tRex, this.ctx); // ... } // ... }, };
效果如下:
可以看到碰撞檢測(cè)是實(shí)現(xiàn)了,但是小恐龍遮住了顯示出來(lái)的碰撞盒子,這是因?yàn)楦庐?huà)布時(shí),繪制小恐龍的方法在碰撞檢測(cè)后面調(diào)用。所以為了演示,我們把碰撞檢測(cè)的調(diào)用代碼調(diào)整一下位置:
Runner.prototype = { update: function () { // ... // 游戲變?yōu)殚_(kāi)始狀態(tài)或小恐龍還沒(méi)有眨三次眼 if (this.playing || (!this.activated && this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { this.tRex.update(deltaTime); + // 碰撞檢測(cè) + var collision = hasObstacles && + checkForCollision(this.horizon.obstacles[0], this.tRex, this.ctx); // 進(jìn)行下一次更新 this.scheduleNextUpdate(); } }, };
這樣就可以看到顯示出的碰撞盒子,效果如下:
到此就實(shí)現(xiàn)了碰撞檢測(cè)。至于檢測(cè)出碰撞后,結(jié)束游戲的相關(guān)邏輯,放到下一章來(lái)實(shí)現(xiàn)。
查看添加或修改的代碼,戳這里
Demo 體驗(yàn)地址:https://liuyib.github.io/blog/demo/game/google-dino/collision-detection/
上一篇 | 下一篇 | Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍 | Chrome 小恐龍游戲源碼探究完 -- 游戲結(jié)束和其他要素 |
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/103911.html
摘要:首先是繪制靜態(tài)的地面。上一篇下一篇無(wú)小恐龍游戲源碼探究二讓地面動(dòng)起來(lái) 文章首發(fā)于我的 GitHub 博客 目錄 Chrome 小恐龍游戲源碼探究一 -- 繪制靜態(tài)地面 Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來(lái) Chrome 小恐龍游戲源碼探究三 -- 進(jìn)入街機(jī)模式 Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵 Chrome 小恐龍游戲源碼探究五 -- 隨機(jī)繪...
摘要:例如,將函數(shù)修改為小恐龍眨眼這樣小恐龍會(huì)不停的眨眼睛。小恐龍的開(kāi)場(chǎng)動(dòng)畫(huà)下面來(lái)實(shí)現(xiàn)小恐龍對(duì)鍵盤(pán)按鍵的響應(yīng)。接下來(lái)還需要更新動(dòng)畫(huà)幀才能實(shí)現(xiàn)小恐龍的奔跑動(dòng)畫(huà)。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替》實(shí)現(xiàn)了游戲晝夜模式的交替,這一篇文章中,將實(shí)現(xiàn):1、小恐龍的繪制 2、鍵盤(pán)對(duì)小恐龍的控制 3、頁(yè)面失焦后,重新聚焦會(huì)重置...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究四隨機(jī)繪制云朵實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn)仙人掌翼龍障礙物的繪制游戲速度的改變障礙物的類(lèi)型有兩種仙人掌和翼龍。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究四 -- 隨機(jī)繪制云朵》 實(shí)現(xiàn)了云朵的隨機(jī)繪制,這一篇文章中將實(shí)現(xiàn):1、仙人掌、翼龍障礙物的繪制 2、游戲速度的改變 障礙物...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究二讓地面動(dòng)起來(lái)實(shí)現(xiàn)了地面的移動(dòng)。街機(jī)模式的效果就是游戲開(kāi)始后,進(jìn)入全屏模式。例如可以看到,進(jìn)入街機(jī)模式之前,有一段開(kāi)場(chǎng)動(dòng)畫(huà)。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究二 -- 讓地面動(dòng)起來(lái)》 實(shí)現(xiàn)了地面的移動(dòng)。這一篇文章中,將實(shí)現(xiàn)效果:1、瀏覽器失焦時(shí)游戲暫停,聚焦游戲繼續(xù)。 2、開(kāi)場(chǎng)動(dòng)...
摘要:文章首發(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...
閱讀 2998·2021-11-24 09:39
閱讀 1746·2021-09-28 09:35
閱讀 1233·2021-09-06 15:02
閱讀 1480·2021-07-25 21:37
閱讀 2920·2019-08-30 15:53
閱讀 3731·2019-08-30 14:07
閱讀 786·2019-08-30 11:07
閱讀 3714·2019-08-29 18:36