标签:
在Unity3D官方资源中Standard Assets –>Vehicles–>Car是赛车的相关资源,包括赛车模型、赛车控制脚本等。虽然用起来很方便,但是由于对有些脚本理解不彻底,就用不好。尤其是主要控制脚本CarController,我此次就对这个脚本进行了全面解析
整个CarController脚本主要是通过共有函数Move对赛车进行控制
//汽车驱动类型
internal enum CarDriveType
{
//四驱
FrontWheelDrive,
//后驱
RearWheelDrive,
//前驱
FourWheelDrive
}
//速度类型
internal enum SpeedType
{
//英里每小时
MPH,
//千米每小时
KPH
}
[SerializeField] private CarDriveType m_CarDriveType = CarDriveType.FourWheelDrive;
[SerializeField] private WheelCollider[] m_WheelColliders = new WheelCollider[4];
[SerializeField] private GameObject[] m_WheelMeshes = new GameObject[4];
[SerializeField] private WheelEffects[] m_WheelEffects = new WheelEffects[4];
//重心位置
[SerializeField] private Vector3 m_CentreOfMassOffset;
//最大可转角度
[SerializeField] private float m_MaximumSteerAngle;
[Range(0, 1)] [SerializeField] private float m_SteerHelper; // 0 is raw physics , 1 the car will grip in the direction it is facing
[Range(0, 1)] [SerializeField] private float m_TractionControl; // 0 is no traction control, 1 is full interference
//所有轮胎的扭矩
[SerializeField] private float m_FullTorqueOverAllWheels;
//反向扭矩
[SerializeField] private float m_ReverseTorque;
//最大刹车扭矩
[SerializeField] private float m_MaxHandbrakeTorque;
//最大下压力
[SerializeField] private float m_Downforce = 100f;
//速度单位
[SerializeField] private SpeedType m_SpeedType;
//最高速度
[SerializeField] private float m_Topspeed = 200;
//档位总数
[SerializeField] private static int NoOfGears = 5;
//
[SerializeField] private float m_RevRangeBoundary = 1f;
//最大滑动距离
[SerializeField] private float m_SlipLimit;
//刹车扭矩
[SerializeField] private float m_BrakeTorque;
//外部调用的汽车移动控制函数
public void Move(float steering, float accel, float footbrake, float handbrake)
{
Debug.Log ("***************************: " + footbrake + " " + handbrake);
//保持当前的轮胎网格跟随WheelCollider转动
for (int i = 0; i < 4; i++)
{
Quaternion quat;
Vector3 position;
m_WheelColliders[i].GetWorldPose(out position, out quat);
m_WheelMeshes[i].transform.position = position;
m_WheelMeshes[i].transform.rotation = quat;
}
//clamp input values
//限定输入值范围
steering = Mathf.Clamp(steering, -1, 1);
AccelInput = accel = Mathf.Clamp(accel, 0, 1);
BrakeInput = footbrake = -1*Mathf.Clamp(footbrake, -1, 0);
handbrake = Mathf.Clamp(handbrake, 0, 1);
//Set the steer on the front wheels.
//设置前轮转角
//Assuming that wheels 0 and 1 are the front wheels.
//wheels下标为0、1的就是前轮
m_SteerAngle = steering*m_MaximumSteerAngle;
m_WheelColliders[0].steerAngle = m_SteerAngle;
m_WheelColliders[1].steerAngle = m_SteerAngle;
//调用角度辅助助手,
SteerHelper();
//设置加速/刹车信息到WheelCollider
ApplyDrive(accel, footbrake);
//检查速度范围
CapSpeed();
//Set the handbrake.
//设置手刹
//Assuming that wheels 2 and 3 are the rear wheels.
//Wheel下标是2、3就是后轮
if (handbrake > 0f)
{
//设置手刹值到后轮,达到减速目的
var hbTorque = handbrake*m_MaxHandbrakeTorque;
m_WheelColliders[2].brakeTorque = hbTorque;
m_WheelColliders[3].brakeTorque = hbTorque;
}
//计算转速,用来供外部调用转速属性Revs来播放引擎声音等
CalculateRevs();
//改变档位
GearChanging();
//施加下压力
AddDownForce();
//检查轮胎
CheckForWheelSpin();
//牵引力控制系统
TractionControl();
}
private void SteerHelper()
{
for (int i = 0; i < 4; i++)
{
WheelHit wheelhit;
m_WheelColliders[i].GetGroundHit(out wheelhit);
if (wheelhit.normal == Vector3.zero)
return; // wheels arent on the ground so dont realign the rigidbody velocity
//假如轮子离地,就不用调整汽车角度了
}
// this if is needed to avoid gimbal lock problems that will make the car suddenly shift direction
//这个是为了避免万向锁问题的,万向锁问题会导致汽车突然变换方向(我知道万向锁问题,但不理解下面是怎么避免问题的,我只知道四元数的使用就是为了避免万向锁问题)
//下面这个If函数的效果就是:假如上一次车体Y方向角度比这次小于十度,就根据相差的度数乘以系数m_SteerHelper,得出需要旋转的度数
//根据这个度数算出四元数,然后将刚体速度直接旋转这个偏移度数,
//根据代码开头m_SteerHelper的定义,这个做法相当于做了一个角度辅助,不完全凭借WheelCollider物理效果
//而直接操控速度方向,对车角度进行调整。
//现在来看,如果m_SteerHelper越小,则调整的角度越小,如果m_SteerHelper为0,则调整的角度为0,。
if (Mathf.Abs(m_OldRotation - transform.eulerAngles.y) < 10f)
{
var turnadjust = (transform.eulerAngles.y - m_OldRotation) * m_SteerHelper;
Quaternion velRotation = Quaternion.AngleAxis(turnadjust, Vector3.up);
m_Rigidbody.velocity = velRotation * m_Rigidbody.velocity;
}
m_OldRotation = transform.eulerAngles.y;
}
我通过实践,发现的效果就是如果m_SteerHelper为0,车转角度时就很死,如果为1,车转角度就特别灵活。所以这个属性得看个人的情况进行调整
// crude traction control that reduces the power to wheel if the car is wheel spinning too much
//如果汽车轮胎过度滑转,牵引力系统可以控制减少轮胎动力
private void TractionControl()
{
WheelHit wheelHit;
switch (m_CarDriveType)
{
//四驱
case CarDriveType.FourWheelDrive:
// loop through all wheels
for (int i = 0; i < 4; i++)
{
m_WheelColliders[i].GetGroundHit(out wheelHit);
AdjustTorque(wheelHit.forwardSlip);
}
break;
//后驱
case CarDriveType.RearWheelDrive:
m_WheelColliders[2].GetGroundHit(out wheelHit);
AdjustTorque(wheelHit.forwardSlip);
m_WheelColliders[3].GetGroundHit(out wheelHit);
AdjustTorque(wheelHit.forwardSlip);
break;
//前驱
case CarDriveType.FrontWheelDrive:
m_WheelColliders[0].GetGroundHit(out wheelHit);
AdjustTorque(wheelHit.forwardSlip);
m_WheelColliders[1].GetGroundHit(out wheelHit);
AdjustTorque(wheelHit.forwardSlip);
break;
}
}
private void AdjustTorque(float forwardSlip)
{
//当向前滑动距离超过阈值后,就说明轮胎过度滑转,则减少牵引力,以降低转速。
if (forwardSlip >= m_SlipLimit && m_CurrentTorque >= 0)
{
m_CurrentTorque -= 10 * m_TractionControl;
}
else
{
m_CurrentTorque += 10 * m_TractionControl;
if (m_CurrentTorque > m_FullTorqueOverAllWheels)
{
m_CurrentTorque = m_FullTorqueOverAllWheels;
}
}
}
从上面的函数可以看出来,属性m_TractionControl控制着牵引力每次增加或者减少的增量大小。
我们再来看另外一段Start中的代码
//设置当前扭矩,初始化的扭矩值跟m_TractionControl大小有关,m_TractionControl决定是否有牵引力,如果m_TractionControl
//值为0,则当前扭矩直接就是最大值,如果该值为1,则初始扭矩为0,然后汽车启动慢慢增加扭矩力。
m_CurrentTorque = m_FullTorqueOverAllWheels - (m_TractionControl*m_FullTorqueOverAllWheels);
也就是说,如果m_TractionControl为1,那么一开始的汽车扭矩力就是0了,然后通过上面函数TranctionControl中的代码,每次10个力地增加。
我们会发现,每次启动汽车都会非常缓慢,而且如果爬坡时一次没冲上去,停在了半坡,再想冲上去,非常困难,因为这个时候扭矩力很小,动力不足;
如果我们把m_TractionControl设置为0,那么汽车一开始的动力就是满的,也不会再增加减少,但是汽车启动可能会特别快。
如果我们把m_TractionControl设置为0.5,那么汽车一开始就拥有满动力一半的动力,然后另外一半动力会动态地变化。
所以只要把握住了上面几点,就根据自己需要设置该属性就好了
//计算转速
private void CalculateRevs()
{
// calculate engine revs (for display / sound)
//计算引擎转速(只用于显示和声音)
// (this is done in retrospect - revs are not used in force/power calculations)
//(我的个人理解:)这个计算是回溯的转速,不能用于力的计算。也就是说,这个是根据速度,反算出来的转速,只是为了效果显示
//计算在当前档位上的转速因子(决定在当前档位上的转速)
CalculateGearFactor();
//档位因子:当前档位/总档位数
var gearNumFactor = m_GearNum/(float) NoOfGears;
//计算在当前档位下的最小转速
var revsRangeMin = ULerp(0f, m_RevRangeBoundary, CurveFactor(gearNumFactor));
//计算在当前档位下的最大转速
var revsRangeMax = ULerp(m_RevRangeBoundary, 1f, gearNumFactor);
//根据当前的转速因子,计算当前的转速
Revs = ULerp(revsRangeMin, revsRangeMax, m_GearFactor);
}
其中有个比较奇怪的地方是计算RevsRangeMin,对档位因子使用了CurveFactor曲线函数,我的理解是,由于档位与转速的对应关系不是y = x这种简单的关系,所以不能直接使用gearNumFactor当做转速的比例系数,所以需要做一次转换,也就是说,档位或者速度与转速之间的对应关系是转速 = 1- (1-X)(1-X)。请看我根据这个函数做出来的曲线
至于为什么计算最大转速时,又没有使用这个函数,我就还没有搞清楚呢
//计算档位因子
private void CalculateGearFactor()
{
float f = (1/(float) NoOfGears);
// gear factor is a normalised representation of the current speed within the current gear‘s range of speeds.
// We smooth towards the ‘target‘ gear factor, so that revs don‘t instantly snap up or down when changing gear.
//我们要让值平滑地想着目标移动,以保证转速不会在变换档位时突然地上高或者降低
//反向差值,通过当前速度的比例值,找当前速度在当前档位的比例位置,得到的值将是一个0~1范围内的值。
var targetGearFactor = Mathf.InverseLerp(f*m_GearNum, f*(m_GearNum + 1), Mathf.Abs(CurrentSpeed/MaxSpeed));
//从当前档位因子向目标档位因子做平滑差值
m_GearFactor = Mathf.Lerp(m_GearFactor, targetGearFactor, Time.deltaTime*5f);
}
从上面我们可以看出,至少档位速度之间的对应关系,大致是直线性,不是曲线的。不然上面的那种反插值函数,可能就得使用曲线变换函数了。
其他的函数也有些有意思的函数,我就不在这里列举了。我上传了我注释版的CarController,希望能给有用的朋友一些启发。我的好多理解也可能不对,只是自圆其说,我日后再研究有结果会再更新的
我的上传注释版链接:
http://download.csdn.net/detail/narutojzm1/9516648
Unity3d 官方资源Car的主控脚本CarController翻译与详解
标签:
原文地址:http://blog.csdn.net/narutojzm1/article/details/51374198