在坦克模型下建立名为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是用Unity3D制作汽车类型游戏的关键所在,它不仅可以模拟轮子的碰撞过程,还模拟了汽车的悬挂系统、引擎系统、轮胎摩擦等汽车的关键物理特性。WheelCollider的属性及说明见下表:
属性 | 说明 |
---|---|
mass | 车轮的质量 |
radius | 轮子半径 |
suspensionDistance | 车轮悬挂的最大延长距离 |
center | 轮子的中心位置(相对于本地坐标系) |
suspensionSpring | 车轮悬挂的参数,通过添加弹簧和阻尼力,悬挂试图达到的目标位置 |
forwardFriction | 在车轮指向方向上的摩擦力的属性 |
sidewaysFriction | 轮胎侧面方向上的摩擦力的属性 |
车轮是由motorTorque、brakeTorque和steerAngle属性控制的,它们的含义见下表:
属性或方法 | 说明 |
---|---|
motorTorque | 在轮轴上的电机力矩 |
brakeTorque | 刹车的力矩 |
steerAngle | 车轮转向角度 |
一般来说,重心、悬挂系统和轮胎(摩擦力属性)对汽车的性能有着很大的影响。
属性或方法 | 说明 |
---|---|
spring | 悬挂弹簧。该值决定了 悬挂弹簧的刚性,把它设得很高可以使弹簧很软,所以车轮将有更大的震动范围,把它设得很小,将使悬挂很硬 |
damper | 悬挂阻尼器,可以使弹簧震动变得平滑 |
targetPosition | 目标位置。静止状态下悬挂的距离,0表示充分伸展弹簧,1表示充分压缩弹簧 |
参数 | 说明 |
---|---|
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;
}
运行游戏,可以体验到刹车效果。