内容简介:文章首发于我的上一篇文章:《这个游戏采用的检测方法是盒子碰撞,这种检测方法最大的好处就是简单,但是缺点是不够精确。
文章首发于我的 博客
前言
上一篇文章:《 Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙 》实现了小恐龙的绘制以及键盘对小恐龙的控制,这一篇文章中将实现游戏的碰撞检测。
碰撞检测原理
这个游戏采用的检测方法是盒子碰撞,这种检测方法最大的好处就是简单,但是缺点是不够精确。
首先,如果将小恐龙和障碍物分别看作两个大的盒子,那么进行碰撞检测的效果如下:
可以看出,两个盒子虽然有重叠部分,但是实际小恐龙并没有和障碍物相撞。
所以想要进行更精确的检测,需要把物体拆分成多个较小的盒子。例如:
但是拆分的时候也不能过于细致,否则运算的时候,很影响性能。
这个游戏中所进行的必要拆分如图所示:
这里值得一提的是,当小恐龙俯身时,只需要将其拆成一个大的盒子。因为当小恐龙俯身时,可以产生碰撞的部分只有前面,而在小恐龙前面碰撞一定会碰到它的头部。毕竟现在这个游戏中还没有那么矮小的障碍物,以至于刚好碰到小恐龙俯身时的下巴。
这就提示我们,如果想要对游戏进行扩展,添加新的障碍物,就要考虑到小恐龙当前的碰撞盒子是否需要进行调整,要确保当前的碰撞盒子可以正确检测出所有情况。
生成碰撞盒子
游戏中使用 CollisionBox
类来生成碰撞盒子:
/** * 用于生成碰撞盒子 * @param {Number} x X 坐标 * @param {Number} y Y坐标 * @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 坐标 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 坐标不固定 multipleSpeed: 999, minSpeed: 8.5, minGap: 150, numFrames: 2, // 两个动画帧 frameRate: 1000 / 6, // 帧率(一帧的时间) 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
类上添加属性:
function Obstacle(canvas, type, spriteImgPos, dimensions, gapCoefficient, speed, opt_xOffset) { //... + this.collisionBoxes = []; // 存储碰撞盒子 // ... }
添加方法,用于拷贝障碍物的碰撞盒子:
Obstacle.prototype = { // 复制碰撞盒子 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); } }, };
然后,调用这个方法来初始化障碍物的碰撞盒子:
Obstacle.prototype = { init: function () { + this.cloneCollisionBoxes(); // ... }, };
这里需要对仙人掌中间的碰撞盒子进行调整:
Obstacle.prototype = { init: function () { // ... // 调整中间的碰撞盒子的大小 // ____ ______ ________ // _| |-| _| |-| _| |-| // | |<->| | | |<--->| | | |<----->| | // | | 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; } // ... }, };
碰撞检测
首先,检测矩形四个边的相对位置,来判断两个矩形是否相交:
/** * 比较两个矩形是否相交 * @param {CollisionBox} tRexBox 小恐龙的碰撞盒子 * @param {CollisionBox} obstacleBox 障碍物的碰撞盒子 */ function boxCompare(tRexBox, obstacleBox) { var crashed = false; // 两个矩形相交 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; };
然后调用这个方法,判断小恐龙和障碍物是否碰撞的逻辑如下:
/** * 检测盒子是否碰撞 * @param {Object} obstacle 障碍物 * @param {Object} tRex 小恐龙 * @param {HTMLCanvasContext} opt_canvasCtx 画布上下文 */ function checkForCollision(obstacle, tRex, opt_canvasCtx) { // 调整碰撞盒子的边界,因为小恐龙和障碍物有 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); // 绘制调试边框 if (opt_canvasCtx) { drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); } // 检查最外层的盒子是否碰撞 if (boxCompare(tRexBox, obstacleBox)) { var collisionBoxes = obstacle.collisionBoxes; // 小恐龙有两种碰撞盒子,分别对应小恐龙站立状态和低头状态 var tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING; // 检测里面小的盒子是否碰撞 for (var t = 0; t < tRexCollisionBoxes.length; t++) { for (var i = 0; i < collisionBoxes.length; i++) { // 调整碰撞盒子的实际位置(除去小恐龙和障碍物上 1 像素的白边) var adjTrexBox = createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); var adjObstacleBox = createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); var crashed = boxCompare(adjTrexBox, adjObstacleBox); // 绘制调试边框 if (opt_canvasCtx) { drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox); } if (crashed) { return [adjTrexBox, adjObstacleBox]; } } } } return false; }; /** * 调整碰撞盒子 * @param {!CollisionBox} box 原始的盒子 * @param {!CollisionBox} adjustment 要调整成的盒子 * @return {CollisionBox} 被调整的盒子对象 */ 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
时用的,用于显示碰撞盒子的边框。
上面的代码中,对碰撞检测的计算进行了优化:首先判断小恐龙和障碍物最外层的盒子有没有碰撞,当它们最外层的盒子碰撞后,再计算里面的小盒子是否碰撞。这样和直接计算所有盒子是否碰撞比起来,性能要好很多。
然后,调用 checkForCollision
方法:
Runner.prototype = { update: function () { // ... if (this.playing) { // ... // 碰撞检测 + var collision = hasObstacles && + checkForCollision(this.horizon.obstacles[0], this.tRex, this.ctx); // ... } // ... }, };
效果如下:
可以看到碰撞检测是实现了,但是小恐龙遮住了显示出来的碰撞盒子,这是因为更新画布时,绘制小恐龙的方法在碰撞检测后面调用。所以为了演示,我们把碰撞检测的调用代码调整一下位置:
Runner.prototype = { update: function () { // ... // 游戏变为开始状态或小恐龙还没有眨三次眼 if (this.playing || (!this.activated && this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { this.tRex.update(deltaTime); // 碰撞检测 + var collision = hasObstacles && + checkForCollision(this.horizon.obstacles[0], this.tRex, this.ctx); // 进行下一次更新 this.scheduleNextUpdate(); } }, };
这样就可以看到显示出的碰撞盒子,效果如下:
到此就实现了碰撞检测。至于检测出碰撞后,结束游戏的相关逻辑,放到下一章来实现。
查看添加的代码, 戳这里
Demo 体验地址: https://liuyib.github.io/pages/demo/games/google-dino/collision-detection/
上一篇 | 下一篇 | Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙 | TODO |
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Chrome 小恐龙游戏源码探究六 -- 记录游戏分数
- Chrome 小恐龙游戏源码探究完 -- 游戏结束和其他要素
- Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍
- Chrome 小恐龙游戏源码探究二 -- 让地面动起来
- Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙
- 跨域不完全探究
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。