内容简介:文章首发于我的上一篇文章:《这个游戏采用的检测方法是盒子碰撞,这种检测方法最大的好处就是简单,但是缺点是不够精确。
文章首发于我的 博客
前言
上一篇文章:《 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 小恐龙游戏源码探究八 -- 奔跑的小恐龙
- 跨域不完全探究
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Don't Make Me Think
Steve Krug / New Riders Press / 18 August, 2005 / $35.00
Five years and more than 100,000 copies after it was first published, it's hard to imagine anyone working in Web design who hasn't read Steve Krug's "instant classic" on Web usability, but people are ......一起来看看 《Don't Make Me Think》 这本书的介绍吧!