引言:今天突發(fā)奇想盖喷,想搞一個車的項目爆办,話不多說,直接下載课梳,但是之前對它一無所知距辆。好在通過簡單的測試,已經(jīng)解決暮刃。運行效果還不錯跨算。上圖是運行效果
1、導(dǎo)入游戲資源
1-1 導(dǎo)入標(biāo)準(zhǔn)資源包:
最后一個就是擁有車模型的資源包沾歪,而且導(dǎo)入Unity中會自動出現(xiàn)一個Standard Assets:
標(biāo)準(zhǔn)資源包里面就有車的模型:
其他資源是我自己下載的:可以加群(Q群:134688909)獲取相關(guān)資源
1-2讓車能夠移動
因為系統(tǒng)自帶的車模型漂彤,擁有移動功能,而且還不錯灾搏。所以直接拿來當(dāng)輪子使用
再搞一些跟隨車輛:顯得有競技效果挫望,此處也是使用系統(tǒng)資源
2、搭建簡單地形:
2-1創(chuàng)建地形
2-2添加地形:
2-3最終效果圖:
3狂窑、添加游戲?qū)ο?- 車
4媳板、腳本一欄:
namespace UnityStandardAssets.Vehicles.Car
{
//汽車驅(qū)動類型
internal enum CarDriveType
{
//四驅(qū)
FrontWheelDrive,
//后驅(qū)
RearWheelDrive,
//前驅(qū)
FourWheelDrive
}
//速度類型
internal enum SpeedType
{
//英里每小時
MPH,
//千米每小時
KPH
}
public class CarController : MonoBehaviour
{
[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;
//最大可轉(zhuǎn)角度
[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;
//檔位總數(shù)
[SerializeField] private static int NoOfGears = 5;
//
[SerializeField] private float m_RevRangeBoundary = 1f;
//最大滑動距離
[SerializeField] private float m_SlipLimit;
//剎車扭矩
[SerializeField] private float m_BrakeTorque;
private Quaternion[] m_WheelMeshLocalRotations;
private Vector3 m_Prevpos, m_Pos;
private float m_SteerAngle;
//當(dāng)前檔位
private int m_GearNum;
//檔位因子
private float m_GearFactor;
//上一幀汽車方向
private float m_OldRotation;
//當(dāng)前扭矩
private float m_CurrentTorque;
private Rigidbody m_Rigidbody;
private const float k_ReversingThreshold = 0.01f;
public bool Skidding { get; private set; }
public float BrakeInput { get; private set; }
public float CurrentSteerAngle{ get { return m_SteerAngle; }}
public float CurrentSpeed{ get { if(m_Rigidbody == null) return 0;return m_Rigidbody.velocity.magnitude*2.23693629f; }}
public float MaxSpeed{get { return m_Topspeed; }}
public float Revs { get; private set; }
public float AccelInput { get; private set; }
public Rigidbody CarRigidbody { get{return m_Rigidbody;}}
// Use this for initialization
private void Start()
{
//四個輪胎的自轉(zhuǎn)方向
m_WheelMeshLocalRotations = new Quaternion[4];
for (int i = 0; i < 4; i++)
{
m_WheelMeshLocalRotations[i] = m_WheelMeshes[i].transform.localRotation;
}
//設(shè)置重心
m_WheelColliders[0].attachedRigidbody.centerOfMass = m_CentreOfMassOffset;
//設(shè)置最大手剎扭矩
m_MaxHandbrakeTorque = float.MaxValue;
m_Rigidbody = GetComponent<Rigidbody>();
//設(shè)置當(dāng)前扭矩,初始化的扭矩值跟m_TractionControl大小有關(guān)泉哈,m_TractionControl決定是否有牽引力蛉幸,如果m_TractionControl
//值為0,則當(dāng)前扭矩直接就是最大值丛晦,如果該值為1奕纫,則初始扭矩為0,然后汽車啟動慢慢增加扭矩力烫沙。
m_CurrentTorque = m_FullTorqueOverAllWheels - (m_TractionControl*m_FullTorqueOverAllWheels);
}
//檔位更新
private void GearChanging()
{
//這里其實相當(dāng)于將所有速度進(jìn)行了劃分匹层,f就是指的目前速度處于最大速度的幾分之幾
//如果NoOfGears是5,那么就把速度分成5段
//1檔就是0/5 ~ 1/5,
//2檔就是1/5 ~ 2/5锌蓄,
//3檔就是2/5 ~ 3/5,
//4檔就是3/5 ~ 4/5升筏,
//5檔就是4/5 ~ 5/5,
//每次先看當(dāng)前檔位是幾檔撑柔,然后看當(dāng)前速度是否匹配當(dāng)前檔位,如果匹配當(dāng)前檔位沒啥事您访,如果不匹配則看當(dāng)前速度如果小于當(dāng)前檔位
//最小速度則減檔铅忿,如果大于當(dāng)前檔位最大速度則,加檔灵汪;
float f = Mathf.Abs(CurrentSpeed/MaxSpeed);
//計算當(dāng)前檔位的速度上限
float upgearlimit = (1/(float) NoOfGears)*(m_GearNum + 1);
//計算當(dāng)前檔位的速度下限
float downgearlimit = (1/(float) NoOfGears)*m_GearNum;
//如果當(dāng)前檔位大于0檀训, 則看當(dāng)前速度是否小于當(dāng)前檔位,如果小于享言,則果斷降檔
if (m_GearNum > 0 && f < downgearlimit)
{
m_GearNum--;
}
//如果當(dāng)前檔位小于最大檔位-1檔肢扯,且當(dāng)前速度大于當(dāng)前檔位上限,則果斷加檔
//這里我好奇的是担锤,為什么條件不是"if(f>upgearlimit&&(m_GearNum < NoOfGears))",干嘛要減一乍钻?
//我覺得原因可能是因為這個5檔肛循,只是理論速度,故意不達(dá)到银择。多糠。。就像一般汽車為了汽車性能穩(wěn)定浩考,速度設(shè)定可以上200碼一樣夹孔,但是沒有人上過。析孽。
if (f > upgearlimit && (m_GearNum < (NoOfGears - 1)))
{
m_GearNum++;
}
}
// simple function to add a curved bias towards 1 for a value in the 0-1 range
//
private static float CurveFactor(float factor)
{
return 1 - (1 - factor)*(1 - factor);
}
// unclamped version of Lerp, to allow value to exceed the from-to range
//非限制版本的插值函數(shù)搭伤,我剛開始沒發(fā)現(xiàn)他有啥特別之處,“非限制”怎么體現(xiàn)袜瞬?直到我發(fā)現(xiàn)他的value沒有限制0~1的范圍才瞬間明白怜俐。
private static float ULerp(float from, float to, float value)
{
return (1.0f - value)*from + value*to;
}
//計算檔位因子
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.
//我們要讓值平滑地想著目標(biāo)移動,以保證轉(zhuǎn)速不會在變換檔位時突然地上高或者降低
//反向差值邓尤,通過當(dāng)前速度的比例值拍鲤,找當(dāng)前速度在當(dāng)前檔位的比例位置,得到的值將是一個0~1范圍內(nèi)的值汞扎。
var targetGearFactor = Mathf.InverseLerp(f*m_GearNum, f*(m_GearNum + 1), Mathf.Abs(CurrentSpeed/MaxSpeed));
//從當(dāng)前檔位因子向目標(biāo)檔位因子做平滑差值
m_GearFactor = Mathf.Lerp(m_GearFactor, targetGearFactor, Time.deltaTime*5f);
}
//計算轉(zhuǎn)速
private void CalculateRevs()
{
// calculate engine revs (for display / sound)
//計算引擎轉(zhuǎn)速(只用于顯示和聲音)
// (this is done in retrospect - revs are not used in force/power calculations)
//(我的個人理解:)這個計算是回溯的轉(zhuǎn)速季稳,不能用于力的計算。
//計算在當(dāng)前檔位上的轉(zhuǎn)速因子(決定在當(dāng)前檔位上的轉(zhuǎn)速)
CalculateGearFactor();
//檔位因子
var gearNumFactor = m_GearNum/(float) NoOfGears;
//計算在當(dāng)前檔位下的最小速度
var revsRangeMin = ULerp(0f, m_RevRangeBoundary, CurveFactor(gearNumFactor));
//計算在當(dāng)前檔位下的最大速度
var revsRangeMax = ULerp(m_RevRangeBoundary, 1f, gearNumFactor);
//根據(jù)當(dāng)前的轉(zhuǎn)速因子澈魄,計算當(dāng)前的轉(zhuǎn)速
Revs = ULerp(revsRangeMin, revsRangeMax, m_GearFactor);
}
//外部調(diào)用的汽車移動控制函數(shù)
public void Move(float steering, float accel, float footbrake, float handbrake)
{
Debug.Log ("***************************: " + footbrake + " " + handbrake);
//保持當(dāng)前的輪胎網(wǎng)格跟隨WheelCollider轉(zhuǎn)動
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.
//設(shè)置前輪轉(zhuǎn)角
//Assuming that wheels 0 and 1 are the front wheels.
//wheels下標(biāo)為0景鼠、1的就是前輪
m_SteerAngle = steering*m_MaximumSteerAngle;
m_WheelColliders[0].steerAngle = m_SteerAngle;
m_WheelColliders[1].steerAngle = m_SteerAngle;
//調(diào)用角度輔助助手,
SteerHelper();
//設(shè)置加速/剎車信息到WheelCollider
ApplyDrive(accel, footbrake);
//檢查速度范圍
CapSpeed();
//Set the handbrake.
//設(shè)置手剎
//Assuming that wheels 2 and 3 are the rear wheels.
//Wheel下標(biāo)是2一忱、3就是后輪
if (handbrake > 0f)
{
//設(shè)置手剎值到后輪莲蜘,達(dá)到減速目的
var hbTorque = handbrake*m_MaxHandbrakeTorque;
m_WheelColliders[2].brakeTorque = hbTorque;
m_WheelColliders[3].brakeTorque = hbTorque;
}
//計算轉(zhuǎn)速
CalculateRevs();
//改變檔位
GearChanging();
//施加下壓力
AddDownForce();
//檢查輪胎
CheckForWheelSpin();
//牽引力控制系統(tǒng)
TractionControl();
}
//控制車速
private void CapSpeed()
{
float speed = m_Rigidbody.velocity.magnitude;
switch (m_SpeedType)
{
case SpeedType.MPH:
//將速度m/s轉(zhuǎn)換為mile/h谭确,以便比較
speed *= 2.23693629f;
if (speed > m_Topspeed)
m_Rigidbody.velocity = (m_Topspeed/2.23693629f) * m_Rigidbody.velocity.normalized;
break;
case SpeedType.KPH:
//將速度m/s轉(zhuǎn)換為km/h,以便比較
speed *= 3.6f;
if (speed > m_Topspeed)
m_Rigidbody.velocity = (m_Topspeed/3.6f) * m_Rigidbody.velocity.normalized;
break;
}
}
private void ApplyDrive(float accel, float footbrake)
{
float thrustTorque;
switch (m_CarDriveType)
{
case CarDriveType.FourWheelDrive:
thrustTorque = accel * (m_CurrentTorque / 4f);
for (int i = 0; i < 4; i++)
{
m_WheelColliders[i].motorTorque = thrustTorque;
}
break;
case CarDriveType.FrontWheelDrive:
thrustTorque = accel * (m_CurrentTorque / 2f);
m_WheelColliders[0].motorTorque = m_WheelColliders[1].motorTorque = thrustTorque;
break;
case CarDriveType.RearWheelDrive:
thrustTorque = accel * (m_CurrentTorque / 2f);
m_WheelColliders[2].motorTorque = m_WheelColliders[3].motorTorque = thrustTorque;
break;
}
for (int i = 0; i < 4; i++)
{
if (CurrentSpeed > 5 && Vector3.Angle(transform.forward, m_Rigidbody.velocity) < 50f)
{
m_WheelColliders[i].brakeTorque = m_BrakeTorque*footbrake;
}
else if (footbrake > 0)
{
m_WheelColliders[i].brakeTorque = 0f;
m_WheelColliders[i].motorTorque = -m_ReverseTorque*footbrake;
}
}
}
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
//假如輪子離地票渠,就不用調(diào)整汽車角度了
}
// this if is needed to avoid gimbal lock problems that will make the car suddenly shift direction
//這個是為了避免萬向鎖問題的逐哈,萬向鎖問題會導(dǎo)致汽車突然變換方向(我直到萬向鎖問題,但不理解下面是怎么避免問題的问顷,我只知道四元數(shù)的使用
//就是為了避免萬向鎖問題)
//下面這個If函數(shù)的效果就是昂秃,假如上一次車體Y方向角度比這次小于十度,就根據(jù)相差的度數(shù)乘以系數(shù)m_SteerHelper
//計算出這個偏移角度的四元數(shù)杜窄,然后將剛體速度直接旋轉(zhuǎn)這個偏倚度數(shù)肠骆,
//根據(jù)代碼開頭m_SteerHelper的定義,這個做法相當(dāng)于做了一個角度輔助塞耕,不完全憑借WheelCollider物理效果
//而直接操控速度方向蚀腿,對車角度進(jìn)行調(diào)整。
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;
}
// this is used to add more grip in relation to speed
private void AddDownForce()
{
m_WheelColliders[0].attachedRigidbody.AddForce(-transform.up*m_Downforce*
m_WheelColliders[0].attachedRigidbody.velocity.magnitude);
}
// checks if the wheels are spinning and is so does three things
//檢查輪胎是否旋轉(zhuǎn)
// 1) emits particles
//檢查是否發(fā)射尾氣粒子
// 2) plays tiure skidding sounds
//播放滑行音
// 3) leaves skidmarks on the ground
//去掉剎車印
// these effects are controlled through the WheelEffects class
//這些特效都是通過WheelEffects類控制的
private void CheckForWheelSpin()
{
// loop through all wheels
for (int i = 0; i < 4; i++)
{
WheelHit wheelHit;
m_WheelColliders[i].GetGroundHit(out wheelHit);
// is the tire slipping above the given threshhold
//輪胎滑行距離是否超出閾值
if (Mathf.Abs(wheelHit.forwardSlip) >= m_SlipLimit || Mathf.Abs(wheelHit.sidewaysSlip) >= m_SlipLimit)
{
//超出則發(fā)射煙霧粒子
m_WheelEffects[i].EmitTyreSmoke();
// avoiding all four tires screeching at the same time
//避免四個輪胎都同時播放滑行聲音
// if they do it can lead to some strange audio artefacts
//如果那樣的話會導(dǎo)致某些奇怪的音效
if (!AnySkidSoundPlaying())
{
m_WheelEffects[i].PlayAudio();
}
continue;
}
// if it wasnt slipping stop all the audio
//假如沒有超出閾值扫外,還沒有停止音效莉钙,則停止音效
if (m_WheelEffects[i].PlayingAudio)
{
m_WheelEffects[i].StopAudio();
}
// end the trail generation
//停止煙霧生成
m_WheelEffects[i].EndSkidTrail();
}
}
// crude traction control that reduces the power to wheel if the car is wheel spinning too much
//如果汽車輪胎過度滑轉(zhuǎn),牽引力系統(tǒng)可以控制減少輪胎動力
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)
{
//當(dāng)向前滑動距離超過閾值后筛谚,就說明輪胎過度滑轉(zhuǎn)磁玉,則減少牽引力,以降低轉(zhuǎn)速驾讲。
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;
}
}
}
private bool AnySkidSoundPlaying()
{
for (int i = 0; i < 4; i++)
{
if (m_WheelEffects[i].PlayingAudio)
{
return true;
}
}
return false;
}
void Update()
{
//這兩段If分別用來控制汽車X軸/Z軸上汽車不會過度翻轉(zhuǎn)蚊伞,導(dǎo)致汽車游戲性差。
if (transform.rotation.eulerAngles.x > 60 && transform.rotation.eulerAngles.x < 300) {
Quaternion temp = transform.rotation;
if (transform.rotation.eulerAngles.x < 180) {
temp.eulerAngles = new Vector3(60f, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);
}
else {
temp.eulerAngles = new Vector3(300f, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);
}
transform.rotation = temp;
}
if (transform.rotation.eulerAngles.z > 25 && transform.rotation.eulerAngles.z < 335) {
Quaternion temp = transform.rotation;
if (transform.rotation.eulerAngles.z < 180) {
temp.eulerAngles = new Vector3(transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, 25);
}
else
{
temp.eulerAngles = new Vector3(transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, 335);
}
transform.rotation = temp;
}
}
}
}