Chrome自带恐龙小游戏的源码研究(七)
在上一篇《Chrome自带恐龙小游戏的源码研究(六)》中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测。
碰撞盒子
游戏中采用的是矩形(非旋转矩形)碰撞。这类碰撞优点是计算比较简单,缺点是对不规则物体的检测不够精确。如果不做更为精细的处理,结果会像下图:
如图所示,两个盒子虽然有重叠部分,但实际情况是恐龙和仙人掌之间并未发生碰撞。为了解决这个问题,需要建立多个碰撞盒子:
不过这样还是有问题,观察图片,恐龙和仙人掌都有四个碰撞盒子,如果每次Game Loop里都对这些盒子进行碰撞检测,那么结果是每次需要进行4X4=16次计算,如果物体或者盒子很多,就会导致运算量大大增加,造成严重的性能问题。为改进这一点,只需要先检测两个大盒子之间是否碰撞,如果没有,则略去里面小盒子的碰撞检测。反之则对里面的小盒子做碰撞检测。游戏中使用CollisionBox构造函数创建碰撞盒子:
/** * 碰撞盒子 * @param x {number} 盒子x坐标 * @param y {number} 盒子y坐标 * @param w {number} 盒子宽度 * @param h {number} 盒子高度 */ function CollisionBox(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; }
使用boxCompare方法检测两个盒子是否发生碰撞:
1 /** 2 * 碰撞检测 3 * @param tRexBox {Object} 霸王龙的碰撞盒子 4 * @param obstacleBox {Object} 障碍物的碰撞盒子 5 */ 6 function boxCompare(tRexBox, obstacleBox) { 7 var tRexBoxX = tRexBox.x, 8 tRexBoxY = tRexBox.y, 9 obstacleBoxX = obstacleBox.x, 10 obstacleBoxY = obstacleBox.y; 11 12 return tRexBoxX < obstacleBoxX + obstacleBox.width && tRexBoxX + tRexBox.width > obstacleBoxX && tRexBoxY < obstacleBoxY + obstacleBox.height && tRexBox.height + tRexBoxY > obstacleBoxY; 13 }
建立碰撞盒子
接下来要为恐龙和障碍物建立碰撞盒子。游戏中为恐龙建立了6个碰撞盒子,分布在头、躯干和脚,同时它还有闪避状态:
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中:
1 Obstacle.types = [{ 2 type: 'CACTUS_SMALL', 3 width: 17, 4 height: 35, 5 yPos: 105, 6 multipleSpeed: 4, 7 minGap: 120, 8 minSpeed: 0, 9 collisionBoxes: [new CollisionBox(0, 7, 5, 27), new CollisionBox(4, 0, 6, 34), new CollisionBox(10, 4, 7, 14)] 10 }, 11 { 12 type: 'CACTUS_LARGE', 13 width: 25, 14 height: 50, 15 yPos: 90, 16 multipleSpeed: 7, 17 minGap: 120, 18 minSpeed: 0, 19 collisionBoxes: [new CollisionBox(0, 12, 7, 38), new CollisionBox(8, 0, 7, 49), new CollisionBox(13, 10, 10, 38)] 20 }, 21 { 22 type: 'PTERODACTYL', 23 width: 46, 24 height: 40, 25 yPos: [100, 75, 50], 26 // Variable height mobile. 27 multipleSpeed: 999, 28 minSpeed: 8.5, 29 minGap: 150, 30 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)], 31 numFrames: 2, 32 frameRate: 1000 / 6, 33 speedOffset: .8 34 }];
不过这只是定义了障碍物数量为1的情况,复数的障碍物需要在创建时修正碰撞盒子:
1 if (this.size > 1) {//只针对仙人掌 2 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - this.collisionBoxes[2].width; 3 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; 4 }
下图分别为单数和复数的盒子。
最后执行碰撞检测:
1 function checkForCollision(obstacle, tRex) { 2 //创建最外层的大盒子 3 var tRexBox = new CollisionBox(tRex.xPos + 1, tRex.yPos + 1, tRex.config.WIDTH - 2, tRex.config.HEIGHT - 2); 4 var obstacleBox = new CollisionBox(obstacle.xPos + 1, obstacle.yPos + 1, obstacle.typeConfig.width * obstacle.size - 2, obstacle.typeConfig.height - 2); 5 6 } 7 if (boxCompare(tRexBox, obstacleBox)) { 8 var collisionBoxes = obstacle.collisionBoxes; 9 var tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING: Trex.collisionBoxes.RUNNING; 10 11 for (var t = 0; t < tRexCollisionBoxes.length; t++) { 12 for (var i = 0; i < collisionBoxes.length; i++) { 13 //修正盒子 14 var adjTrexBox = createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); 15 var adjObstacleBox = createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); 16 var crashed = boxCompare(adjTrexBox, adjObstacleBox); 17 18 if (crashed) { 19 return [adjTrexBox, adjObstacleBox]; 20 } 21 } 22 } 23 } 24 return false; 25 }
//修正盒子,将相对坐标转为画布坐标 function createAdjustedCollisionBox(box, adjustment) { return new CollisionBox(box.x + adjustment.x, box.y + adjustment.y, box.width, box.height); }
以下是最终运行效果,打开控制台就能看到碰撞输出:
后记
通过建立碰撞盒子进行碰撞检测在应用上非常广泛,著名的街机游戏《街霸》和《拳皇》就是采用了这种方式:
可以看到游戏中对人物建立了多个碰撞盒子,红色代表攻击区域,蓝色代表可以被攻击的区域,绿色区域之间不能重叠,用来推挤对手。