Unity中的车辆控制
Unity3D的车辆控制
WheelCollider(车轮碰撞器)是一种特殊的地面车辆碰撞器,它具有内置的碰撞检测、车轮物理引擎和一个基于滑移的轮胎摩擦模型。WheelCollider是专门为有轮子的车辆所做的设计。此处把坦克作为车辆来实现坦克的车辆碰撞和车辆运动。
车轮碰撞器
在添加轮子时,给车辆(此处是坦克整体tank)添加碰撞器Rigidbody,且坦克较重,需要在Rigidbody中调整坦克的质量(Mass属性,这里调整为300),如下图:

在坦克模型下建立名为PhysicalBody的空物体,用于存放坦克的碰撞器组件。再在PhysicalBody里添加wheelL1、wheelL2、wheelR1、wheelR2这4个空物体代表坦克的四个轮子。在PhysicalBody里添加2个名为collider的空物体,用于给坦克添加碰撞器(坦克车身有上下两部分,故添加2个collider),此时坦克层次模型如下图:

给2个collider添加BoxCollider组件(Component –> Physics –> Box Collider),然后点击collider的属性面板的Edit Collider调整位置和大小。再给4个轮子分别添加WheelCollider(Component –> Physics –> WheelCollider),调整位置和大小,注意轮子的位置、大小、角度的细微差别会影响物理性能,所以要根据Transform的数值进行调整。L1表示左前轮,L2表示左后轮,R1表示右前轮,R2表示右后轮。最后调整结果见下图:

WheelCollider的属性
WheelCollider是用Unity3D制作汽车类型游戏的关键所在,它不仅可以模拟轮子的碰撞过程,还模拟了汽车的悬挂系统、引擎系统、轮胎摩擦等汽车的关键物理特性。WheelCollider的属性及说明见下表:
| 属性 | 说明 |
|---|---|
| mass | 车轮的质量 |
| radius | 轮子半径 |
| suspensionDistance | 车轮悬挂的最大延长距离 |
| center | 轮子的中心位置(相对于本地坐标系) |
| suspensionSpring | 车轮悬挂的参数,通过添加弹簧和阻尼力,悬挂试图达到的目标位置 |
| forwardFriction | 在车轮指向方向上的摩擦力的属性 |
| sidewaysFriction | 轮胎侧面方向上的摩擦力的属性 |
车轮是由motorTorque、brakeTorque和steerAngle属性控制的,它们的含义见下表:
| 属性或方法 | 说明 |
|---|---|
| motorTorque | 在轮轴上的电机力矩 |
| brakeTorque | 刹车的力矩 |
| steerAngle | 车轮转向角度 |
一般来说,重心、悬挂系统和轮胎(摩擦力属性)对汽车的性能有着很大的影响。
- 重心:由于车辆的重心通常不是车的中心位置,若要得到更真实的效果,可以通过代码设置Rigidbody.centerOfMass来调整重心位置。
- 悬挂系统:车的悬挂系统可以增强轮胎和路面的摩擦。下面是与悬挂系统有关的参数及说明:
| 属性或方法 | 说明 |
|---|---|
| spring | 悬挂弹簧。该值决定了 悬挂弹簧的刚性,把它设得很高可以使弹簧很软,所以车轮将有更大的震动范围,把它设得很小,将使悬挂很硬 |
| damper | 悬挂阻尼器,可以使弹簧震动变得平滑 |
| targetPosition | 目标位置。静止状态下悬挂的距离,0表示充分伸展弹簧,1表示充分压缩弹簧 |
- 轮胎摩擦力:Forward Friction是前后方向的摩擦力属性,影响motorTorque和brakeTorque的效果。Sideways Friction是左右方向的摩擦力属性,影响steerAngle的效果。下面是与摩擦力相关的参数及说明:
| 参数 | 说明 |
|---|---|
| extremumSlip | 滑动极值点(默认为1) |
| exyremumValue | 滑动极值的力(默认为20000) |
| asymptoteSlip | 渐近线滑动点(默认为2) |
| asymptoteValue | 渐近线滑动上的力(默认为10000) |
| stiffness | 用于extremumValue和asymptoteValue值的倍数(默认为1) |
控制车辆
汽车的前轮和后轮分别悬挂在两条轴上,每条轴上两个轮子的步调是一致的。新建名为AxleInfo的Script脚本,添加代表车轴信息的AxleInfo类,(此处的炮管可参见前面的博客)代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class AxleInfo { public WheelCollider leftWheel; public WheelCollider rightWheel; public bool motor; public bool steering; }
编写名为tank的Script脚本拖到坦克上,代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Tank : MonoBehaviour { public Transform turret; private float turretRotSpeed = 0.5f; public Transform gun; private float maxRoll = 10f; private float minRoll = -4f; private float turretRotTarget = 0; private float turretRollTarget = 0; public List<AxleInfo> axleInfos; private float motor = 0; public float maxMotorTorque; private float brakeTorque = 0; public float maxBrakeTorque = 100; private float steering = 0; public float maxSteeringAngle; void Start() { turret = transform.Find("turret"); gun = turret.Find("gun"); } void Update() { PlayerCtrl(); foreach(AxleInfo axleInfo in axleInfos) { if (axleInfo.steering) { axleInfo.leftWheel.steerAngle = steering; axleInfo.rightWheel.steerAngle = steering; } if (axleInfo.motor) { axleInfo.leftWheel.motorTorque = motor; axleInfo.rightWheel.motorTorque = motor; } if (true) { axleInfo.leftWheel.brakeTorque = brakeTorque; axleInfo.rightWheel.brakeTorque = brakeTorque; } } TurretRotation(); TurretRoll(); } public void TurretRotation() { if (Camera.main == null) return; if (turret == null) return; float angle = turret.eulerAngles.y - turretRotTarget; if (angle < 0) angle += 360; if (angle > turretRotSpeed && angle < 180) turret.Rotate(0f, -turretRotSpeed, 0f); else if (angle > 180 && angle < 360 - turretRotSpeed) turret.Rotate(0f, turretRotSpeed, 0f); } public void TurretRoll() { if (Camera.main == null) return; if (turret == null) return; Vector3 worldEuler = gun.eulerAngles; Vector3 localEuler = gun.localEulerAngles; worldEuler.x = turretRollTarget; gun.eulerAngles = worldEuler; Vector3 euler = gun.localEulerAngles; if (euler.x > 180) euler.x -= 360; if (euler.x > maxRoll) euler.x = maxRoll; if (euler.x < minRoll) euler.x = minRoll; gun.localEulerAngles = new Vector3(euler.x, localEuler.y, localEuler.z); } public void PlayerCtrl() { motor = maxMotorTorque * Input.GetAxis("Vertical"); steering = maxSteeringAngle * Input.GetAxis("Horizontal"); turretRotTarget = Camera.main.transform.eulerAngles.y; turretRollTarget = Camera.main.transform.eulerAngles.x; } }
最后设置tank模型的AxleInfos的size设置为2(前后两条车轴),并将4个WheelCollider拖入其中,设置为后驱,如下图:

选择坦克的4个WheelCollider,调整参数,可以得到更好的体验,见下图:

刹车
玩家按下后退键时,是刹车还是后退。可以使用车轮碰撞器的rpm(转速)属性判断。如果转速大于某个值(这里取5),可以视为坦克前进,小于某个值(这里取-5)为后退,转速为0则表示静止,而不单单根据后退键来看。修改PlayerCtrl,代码如下:
public void PlayerCtrl() { motor = maxMotorTorque * Input.GetAxis("Vertical"); steering = maxSteeringAngle * Input.GetAxis("Horizontal"); brakeTorque = 0; foreach(AxleInfo axlenInfo in axleInfos) { if (axlenInfo.leftWheel.rpm > 5 && motor < 0) brakeTorque = maxBrakeTorque; else if(axlenInfo.leftWheel.rpm <-5 && motor > 0) brakeTorque = maxBrakeTorque; continue; } turretRotTarget = Camera.main.transform.eulerAngles.y; turretRollTarget = Camera.main.transform.eulerAngles.x; }
运行游戏,可以体验到刹车效果。
