车辆碰撞检测
借助于前面的代码,我们已经知道如何检测汽车与护栏的碰撞。只需创建两个碰撞平面,每个护栏一个,然后检查四个轮子与这两个平面的碰撞。因为赛车不仅是一个球体,因此无法像小行星那样做简单的处理,四个面都必须检测,但只检测四轮的位置(或至少是赛车的最外层)仍能实现基本碰撞检测(见图13-15)。
碰撞检测的代码在CarPhysics类的ApplyGravityAndCheckForCollisions方法中。此代码遍历了赛车的四个角并检测是否与平面碰撞(见图13-15中的道路旁边的两条线)。在实际车长和宽的基础上可计算赛车的四个角:
// Check all 4 corner points of our car. Vector3 carRight = Vector3.Cross(carDir, carUp); Vector3 carLeft = -carRight; // Car dimensions are 2.6m (width) x 5.6m (length) x 1.8m (height) // Note: This could be improved by using more points or using // the actual car geometry. // Note: We ignore the height, this way the collision is simpler. // We then check the height above the road to see if we are flying // above the guard rails out into the landscape. Vector3[] carCorners = new Vector3[] { // Top left pos + carDir * 5.6f/2.0f - carRight * 2.6f/2.0f, // Top right pos + carDir * 5.6f/2.0f + carRight * 2.6f/2.0f, // Bottom right pos - carDir * 5.6f/2.0f + carRight * 2.6f/2.0f, // Bottom left pos - carDir * 5.6f/2.0f - carRight * 2.6f/2.0f, };
借助于在此方法前计算过的辅助值,碰撞检测现在相对简单了:
// Check for each corner if we collide with the guard rail for (int num = 0; num < carCorners.Length; num++) { // Hit any guardrail? float leftDist = Vector3Helper.DistanceToLine( carCorners[num], guardrailLeft, nextGuardrailLeft); float rightDist = Vector3Helper.DistanceToLine( carCorners[num], guardrailRight, nextGuardrailRight); // If we are closer than 0.1f, thats a collision! if (leftDist < 0.1f || // Also include the case where we are farther away from rightDist // than the road is wide. rightDist > roadWidth) { // Handle collision with left guard rail here } // if (leftDist < 0.1f) if (rightDist < 0.1f || // Also include the case where we are farther away from leftDist // than the road is wide. leftDist > roadWidth) { // Handle collision with right guard rail here } // if (rightDist < 0.1f) } // for (num)
碰撞处理的最后部分是碰撞事件的反应。但在此之前,你需要在单元测试中设置一些字段来显示碰撞检测代码是否正常工作。或只在“if”代码块中设置断点,看看是否已进入条件。
现在,所有要做的就是播放碰撞音效并根据车的哪个角碰撞选择车辆。汽车将减速,相机摇晃告知玩家他刚刚碰撞了。如果直行时碰撞(0~45度对墙),车将完全停住并播放完全碰撞的音效:
// Force car back on the road, for that calculate impulse and // collision direction (same stuff as in Rocket Commander). Vector3 collisionDir = Vector3.Reflect(carDir, guardrailRightNormal); float collisionAngle = Vector3Helper.GetAngleBetweenVectors( carLeft, guardrailRightNormal); // Flip at 180 degrees (if driving in wrong direction) if (collisionAngle > MathHelper.Pi / 2) collisionAngle -= MathHelper.Pi; // Just correct rotation if collison happened at 0-45 degrees (slowly) if (Math.Abs(collisionAngle) < MathHelper.Pi / 4.0f) { // Play crash sound Sound.PlayCrashSound(false); // For front wheels to full collision rotation, for back half! if (num < 2) { rotateCarAfterCollision = +collisionAngle / 1.5f; speed *= 0.935f;//0.85f; if (viewDistance > 0.75f) viewDistance -= 0.1f;//0.15f; } // if (num) else { rotateCarAfterCollision = +collisionAngle / 2.5f; //slowdownCarAfterCollision = 0.8f; speed *= 0.96f;//0.9f; if (viewDistance > 0.75f) viewDistance -= 0.05f;//0.1f; } // else // Shake camera ChaseCamera.SetCameraWobbel(0.00075f * speed); } // if (collisionAngle) // If 90-45 degrees (in either direction), make frontal crash // + stop car + wobble camera else if (Math.Abs(collisionAngle) < MathHelper.Pi * 3.0f / 4.0f) { // Also rotate car if less than 60 degrees if (Math.Abs(collisionAngle) < MathHelper.Pi / 3.0f) rotateCarAfterCollision = +collisionAngle / 3.0f; // Play crash sound Sound.PlayCrashSound(true); // Shake camera ChaseCamera.SetCameraWobbel(0.005f * speed); // Just stop car! speed = 0; } // if (collisionAngle) // For all collisions, kill the current car force carForce = Vector3.Zero; // Always make sure we are OUTSIDE of the collision range for // the next frame. But first find out how much we have to move. float speedDistanceToGuardrails = speed * Math.Abs(Vector3.Dot(carDir, guardrailLeftNormal)); if (rightDist > 0) { float correctCarPosValue = (rightDist + 0.01f +//0.11f + 0.1f * speedDistanceToGuardrails * moveFactor); carPos += correctCarPosValue * guardrailRightNormal; } // if (rightDist)
有了这些代码,现在你可以进行TestCarPhysicsOnPlaneWithGuardRails单元测试并调整与护栏的碰撞。该代码有点复杂,但规则相同,所有的碰撞代码和物理计算与单元测试中使用的的完全一样。