cannon.js
cannon.js是一个Web端的轻量级物理引擎,其官方网站提供了大量的例子供我们学习。使用cannon.js搭配three.js或Babylon.js,可以开发出具有物理效果的3D Web应用程序。
RaycastVehicle
RaycastVehicle是cannon.js提供了一个车辆对象。并且官方给我们提供了demo。
何为Raycast
为何称为RaycastVehicle呢?这与该对象的物理模拟原理有关系。该对象使用刚体(CANNON.Body)作为车身,从刚体的四个角处向下发射固定长度的射线,射线与地面的交叉点作为车辆与地面的接触点,在该点为车身刚体施加纵向的悬挂弹力与横向的牵引摩擦力。
其核心计算在updateVehicle方法中。
使用方法
使用方法可以参考官方示例。
1、首先构造一个RaycastVehicle对象:
vehicle = new CANNON.RaycastVehicle({
chassisBody: chassisBody,
indexRightAxis: 0,
indexForwardAxis: 2,
indexUpAxis: 1,
});
vehicle.addToWorld(world);
其中,chassisBody是代表车身的刚体,indexRightAxis、indexForwardAxis、indexUpAxis官方示例中并没有用到,他们分别代表车的右、前、上轴,0、1、2分别代表x、y、z轴。
2、添加逻辑车轮:
vehicle.addWheel(options);
options对象为车轮及悬挂的参数:
chassisConnectionPointLocal: new Vec3(),// 车轮连接点,相对于chassisBody(也是发射射线的起点)
directionLocal: new Vec3(),// 车轮的下方方向(垂直车身向下)
axleLocal: new Vec3(),// 车轴方向
suspensionRestLength: 1,// 悬挂长度(未受任何力)
suspensionMaxLength: 2,// 悬挂最大长度,限制计算出的suspensionLength
suspensionStiffness: 100,// 悬挂刚度
dampingCompression: 10,// 悬挂压缩阻尼
dampingRelaxation: 10,// 悬挂复原阻尼
maxSuspensionForce: Number.MAX_VALUE, // 限制计算出的suspensionForce
maxSuspensionTravel: 1,// 悬挂可伸长或压缩的最大距离
radius: 1,// 车轮半径
frictionSlip: 10000,// 滑动摩檫系数(用于计算车轮所能提供的最大摩檫力)
rollInfluence: 0.01,// 施加侧向力时的位置系数,越小越接近车身,防止侧翻
3、添加车轮碰撞。添加好逻辑车轮后,还要在world中添加刚体以产生碰撞:
for (var i = 0; i < vehicle.wheelInfos.length; i++) {
var wheel = vehicle.wheelInfos[i];
var cylinderShape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
var wheelBody = new CANNON.Body({
mass: 0
});
wheelBody.type = CANNON.Body.KINEMATIC;
wheelBody.collisionFilterGroup = 0; // turn off collisions
var q = new CANNON.Quaternion();
q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);// 把竖着的圆柱体放倒作为车轮
wheelBody.addShape(cylinderShape, new CANNON.Vec3(), q);
wheelBodies.push(wheelBody);
world.addBody(wheelBody);
}
对于车轮碰撞,还要实时更新其Transom:
world.addEventListener('postStep', function () {
for (var i = 0; i < vehicle.wheelInfos.length; i++) {
var t = vehicle.wheelInfos[i].worldTransform;
var wheelBody = wheelBodies[i];
wheelBody.position.copy(t.position);
wheelBody.quaternion.copy(t.quaternion);
}
});
4、车辆控制。可以使用RaycastVehicle的如下函数控制车辆:
applyEngineForce = function(value, wheelIndex) // 施加牵引力
setSteeringValue = function(value, wheelIndex) // 设置转向角(弧度)
setBrake = function(brake, wheelIndex) // 刹车
5、实现动画。在每帧渲染之前,把车身、车轮的Transom拷贝给three.js或Bayalon.js图形,就实现了动画。
实现漂移
在赛车游戏中,漂移能够很大程度上增加游戏的娱乐性。要理解漂移,首先要了解汽车的转向。
后轮无滑移转向时,前轮与后轮的瞬心即为转向中心:
无滑移状态下,转向中心静止不变,车辆将沿转向中心做圆周运动。当漂移时,转向中心也做圆周运动,车辆运动的圆周比无滑移转向时的半径小:
(漂移轨迹有待验证)
实现漂移,可以在用户按下漂移键后,修改后轮的摩擦系数:
vehicle.wheelInfos[2].frictionSlip= up ? 3.5: 1.5;
vehicle.wheelInfos[3].frictionSlip= up ? 3.5: 1.5;
但是,此时很容易出现漂移过度,车子打一个圈:
前文中介绍了转向半径,这里再利用一下。设转向半径为r,轴距为l,轮距为w:
根据阿克曼条件,两轮的转向角为:
δ
0
=
arctan
(
l
r
+
w
/
2
)
δ_0=\arctan(\frac{l}{r+w/2})
δ0=arctan(r+w/2l)
δ
1
=
arctan
(
l
r
−
w
/
2
)
δ_1=\arctan(\frac{l}{r-w/2})
δ1=arctan(r−w/2l)
车子打圈时,就可以通过调整两前轮的转向角度,避免漂移过程中进入一个极小的转向半径。
r = 6 + Math.abs(vehicle.currentVehicleSpeedKmHour) / 10
switch (event.keyCode) {
// 。。。
case 39: // right
vehicle.setSteeringValue(up ? 0 : -Math.atan(2 / (r + 1 / 2)), 0);
vehicle.setSteeringValue(up ? 0 : -Math.atan(2 / (r - 1 / 2)), 1);
break;
case 37: // left
vehicle.setSteeringValue(up ? 0 : Math.atan(2 / (r - 1 / 2)), 0);
vehicle.setSteeringValue(up ? 0 : Math.atan(2 / (r + 1 / 2)), 1);
break;
case 67:
vehicle.wheelInfos[2].frictionSlip = up ? 3.5 : 1.4;
vehicle.wheelInfos[3].frictionSlip = up ? 3.5 : 1.4;
此外,在卡丁车类游戏中,也可以使用参考文献[1]中,给车身施加侧向力的方法让车辆产生更顺滑的漂移。
参考
[1] 人人都是秋名山车神——Unity实现简化版卡丁车漂移
[2] How to make a DRIFT Controler in UNITY(Free Download Script)