人人都是秋名山車神——Unity實現(xiàn)簡化版卡丁車漂移

作者:Truly
大家好。
前一陣子,Unity出了個名為Karting Microgame的教程項目,里邊有一個賽車小游戲示例場景,行駛、轉彎、漂移等主要功能都幾乎都是通過數(shù)學計算來完成的,同時還提供了場景資源供使用者自定義修改完善游戲。這讓我想起在YouTube上mix and jam大神出過一個制作馬里奧賽車漂移的視頻,于是也想嘗試自己搗鼓一個簡化版的漂移(用剛體加力什么的),實現(xiàn)效果如下:

一、前期準備
1 導入資源(使用其他資源的可跳過)
在Asset Store里搜索并下載Karting Microgame >?導入,寫文章時該資源暫時還不支持Unity 2019.x版本。

2 組合模型(使用其他資源的可跳過)
導入資源后新建場景,這次主要用到的是包里提供卡丁車以及賽道的模型,腳本另外重新寫。
(1) 組合卡丁車
①Hierarchy窗口中新建空的GameObject命名為KartPlayer。
②打開文件夾?Assets > Karting >Models。
③找到Kart并拖到KartPlayer下實例化。
④繼續(xù)在Models文件夾中找到WartWheels和Player,并把他們拖到Kart?GameObject下實例化,成為Kart的子物體,層次結構如下:

(2)分配Animator Controller(播放Idle動畫,可以不做)
①打開Assets > Karting > Animations >?Controllers文件夾,里邊的Animator Controller已經(jīng)配好Idle動畫。
②把KartControllrt分配給Kart GameObject的Animator組件(拖到Kart上)。
③把PlayerControllrt分配給Player GameObject的Animator組件(拖到Player?上)。

3 添加并且配置Collider與RigidBody
①添加Capsule Collider,設置參數(shù)(根據(jù)使用模型自行調(diào)節(jié)),使Capsule Collider大小與Kart相匹配,如下圖:
Center:0.6
Height:2
Direction:Z-Axis

②添加Rigidbody,配置如下:
Drag:2,增加阻力,用于模擬摩擦力。
Freeze Rotation:勾選X軸和Z軸,防止加力的時候翻車。

現(xiàn)在Ctrl+P試執(zhí)行,車和人物都在播放動畫,Player可能需要微調(diào)一下位置,調(diào)整完成后效果如下:

終于把前置準備工作完成了,接下來開始整理一下實現(xiàn)思路。
二、需求分析
1 這次目標只實現(xiàn)最基本的幾個功能:
(1)方向鍵控制移動。
(2)按住空格漂移。
(3)漂移結束時獲得加速。
(4)漂移粒子特效隨著漂移時間變色。
2 實現(xiàn)思路
(1)根據(jù)以上的功能,在Update()實現(xiàn)控制操作:
①首先獲取豎直和水平方向的輸入。
②當按住空格并且有水平輸入時,在落地的瞬間 && 不在漂移狀態(tài) &&有一定速度時開始漂移;
③松開空格鍵時,如果在漂移,則根據(jù)漂移等級給予加速,并且停止漂移。
代碼如下:
void Update()
??? {
??????? //輸入相關
??????? v_Input = Input.GetAxisRaw("Vertical");???? //豎直輸入
??????? h_Input = Input.GetAxisRaw("Horizontal");?? //水平輸入
?
??????? //按下空格起跳
??????? if (Input.GetKeyDown(KeyCode.Space))
??????? {
??????????? if (isGround)?? //如果在地上
??????????? {
??????????????? Jump();
??????????? }
??????? }
?
??????? //按住空格,并且有水平輸入:開始漂移
??????? if (Input.GetKey(KeyCode.Space) && h_Input != 0)
??????? {
??????????? //落地瞬間、不在漂移并且速度大于一定值時開始漂移
??????????? if (isGround && !isGroundLastFrame && !isDrifting && kartRigidbody.velocity.sqrMagnitude > 10)
??????????? {
??????????????? StartDrift();?? //開始漂移
??????????? }
??????? }
?
??????? //放開空格:漂移結束
??????? if (Input.GetKeyUp(KeyCode.Space))
??????? {
??????????? if (isDrifting)
??????????? {
??????????????? Boost(boostForce);//加速
??????????????? StopDrift();//停止漂移
??????????? }
??????? }
??? }
?
(2)在FixedUpdate()中進行邏輯運算:
①控制車的轉向。
車要與地面大致平行。
根據(jù)水平輸入,計算車將會繞自己的Y軸如何旋轉。
②計算添加到車上的力的大小以及方向。
③如果在漂移,計算漂移等級同時根據(jù)漂移等級改變粒子特效顏色。
④為了足夠的簡單粗暴,功能最終是通過旋轉剛體以及給剛體加力來完成的。
代碼如下:
private void FixedUpdate()
??? {
??????? //車轉向
??????? CheckGroundNormal();??????? //檢測是否在地面上,并且使車與地面保持水平
??????? Turn();???????????????????? //輸入控制左右轉向
?
??????? //起步時 力遞增
??????? IncreaseForce();
??????? //漂移加速后/松開加油鍵 力遞減
??????? ReduceForce();
?
?
??????? //如果在漂移
??????? if (isDrifting)
??????? {
??????????? CalculateDriftingLevel();?? //計算漂移等級
??????????? ChangeDriftColor();???????? //根據(jù)漂移等級改變顏色
??????? }
?
??????? //根據(jù)上述情況,進行最終的旋轉和加力
??????? kartRigidbody.MoveRotation(rotationStream);
??????? //計算力的方向
??????? CalculateForceDir();
??????? //移動
??????? AddForceToMove();
??? }
?
如果對不依賴物理系統(tǒng)來完成功能有興趣的小伙伴可以參考Karting Microgame的教程項目。
三 功能實現(xiàn)
有了大概的思路之后,開始著手實現(xiàn)功能。
1 漂移
本次想實現(xiàn)的是類似與馬里奧賽車那中非擬真的漂移,因此下邊只為了簡單實現(xiàn)表現(xiàn)效果而給剛體加的力,并非擬真的受力分析。
(1)根據(jù)觸地時的水平輸入,決定漂移時的車頭朝向。
(2)由于漂移時合速度的方向與車頭的朝向是不一致的,所以需要為最終的合力方向添加一個偏移量m_DriftOffset。以左漂移為例,合速度方向為車頭朝向的右前方,如圖所示(綠線為偏移后加力方向):

(3)開始漂移時播放粒子特效。
代碼如下:
? ? //開始漂移并且決定漂移朝向
??? public void StartDrift()
??? {
??????? isDrifting = true;
???????
??????? //根據(jù)水平輸入決定漂移時車的朝向,因為合速度方向與車身方向不一致,所以為加力方向添加偏移
??????? if (h_Input < 0)
??????? {
??????????? driftDirection = DriftDirection.Left;
??????????? //左漂移時,合速度方向為車頭朝向的右前方,偏移具體數(shù)值需結合實際自己調(diào)試
??????????? m_DriftOffset = Quaternion.Euler(0f, 30, 0f);
??????? }
??????? else if (h_Input > 0)
??????? {
??????????? driftDirection = DriftDirection.Right;
??????????? m_DriftOffset = Quaternion.Euler(0f, -30, 0f);
??????? }
?
??????? //播放漂移粒子特效
??????? PlayDriftParticle();
??? }
?
(3)停止漂移:把恢復漂移朝向,并且調(diào)整偏移。
? public void StopDrift()
??? {
??????? isDrifting = false;
??????? driftDirection = DriftDirection.None;
??????? driftPower = 0;
??????? m_DriftOffset = Quaternion.identity;
??????? StopDriftParticle();
??? }
?
2 轉向
(1)通過射線檢測地面法線,使車與地面大致水平
在靠近車頭中心和車尾中心兩個位置分別添加一個空的GameObject,從這兩個點往車底方向打射線,射線長度比發(fā)射點到車底的距離稍長。
如果其中一條射線打中地面,則判斷車在地面上,獲取擊中的地面的法線。
車的Up方向為兩地面的法線相加的方向(主要用在上下坡的時候)。

代碼如下:
? ? public void CheckGroundNormal()
??? {
??????? //從車頭中心附近往下打射線,長度比發(fā)射點到車底的距離長一點
??????? RaycastHit frontHit;
??????? bool hasFrontHit = Physics.Raycast(frontHitTrans.position, -transform.up, out frontHit, groundDistance, LayerMask.GetMask("Ground"));
?
??????? //從車尾中心附近往下打射線
??????? RaycastHit rearHit;
??????? bool hasRearHit = Physics.Raycast(rearHitTrans.position, -transform.up, out rearHit, groundDistance, LayerMask.GetMask("Ground"));
?
??????? isGroundLastFrame = isGround; //儲存上一幀是否在地面
??????? if (hasFrontHit || hasRearHit)//更新判斷現(xiàn)在是否在地面
??????? {
??????????? isGround = true;
??????? }
??????? else
??????? {
??????????? isGround = false;
??????? }
?
??????? //使車與地面水平
??????? Vector3 tempNormal = (frontHit.normal + rearHit.normal).normalized;
??????? Quaternion quaternion = Quaternion.FromToRotation(transform.up, tempNormal);
??????? rotationStream = quaternion * rotationStream;
??? }
?
(2)通過水平輸入控制轉向
馬里奧賽車在漂移的時候只需按著漂移鍵和加油鍵就能按照原先朝向一直繞著某個點作圓周的運動,并且可以通過左右方向鍵進行微調(diào)。因此推斷如下:
①漂移時需要自帶一個方向的旋轉。
②此時通過輸入可控的旋轉角速度要小于漂移自帶旋轉的角速度,否則會漂移到一定程度回反向飄。
③當車后退時,旋轉方向與前進時相反。
大概路線如圖(以左漂移為例):

根據(jù)流程對RotationStream變量進行處理,最終用kartRigidbody.MoveRotation(rotationStream)使車轉向。
RotationStream處理流程:

代碼如下(由于時間關系,角度直接寫死了,有興趣的同學可以自己細調(diào)):
public void Turn()
??? {
??????? //只能在移動時轉彎
??????? if (kartRigidbody.velocity.sqrMagnitude <= 0.1)
??????? {
??????????? return;
??????? }
?
??????? //漂移時自帶轉向
??????? if (driftDirection == DriftDirection.Left)
??????? {
??????????? rotationStream = rotationStream * Quaternion.Euler(0, -40 * Time.fixedDeltaTime, 0);
??????? }
??????? else if (driftDirection == DriftDirection.Right)
??????? {
??????????? rotationStream = rotationStream * Quaternion.Euler(0, 40 * Time.fixedDeltaTime, 0);
??????? }
?
??????? //后退時左右顛倒
??????? float modifiedSteering = Vector3.Dot(kartRigidbody.velocity, transform.forward) >= 0 ? h_Input : -h_Input;
?
??????? //輸入可控轉向:如果在漂移,可控角速度為30,否則平常狀態(tài)為60.
??????? turnSpeed = driftDirection != DriftDirection.None ? 30 : 60;
??????? float turnAngle = modifiedSteering * turnSpeed * Time.fixedDeltaTime;
??????? Quaternion deltaRotation = Quaternion.Euler(0, turnAngle, 0);
?
??????? rotationStream = rotationStream * deltaRotation;//局部坐標下旋轉,這里有空換一個簡單的寫法
??? }
?
3 移動
處理完轉向問題,剩下只需要計算力的方向并且加力,車就能動起來了。
(1)在水平方向(車的XZ平面)上,從車的前方進行一個漂移造成的偏移m_DriftOffset(在第一步漂移時已經(jīng)計算好)。
(2)當豎直輸入 < 0時,往反方向加力。
//計算加力方向
??? public void CalculateForceDir()
??? {
??????? //往前加力
??????? if (v_Input > 0)
??????? {
??????????? verticalModified = 1;
??????? }
??????? else if (v_Input < 0)//往后加力
??????? {
??????????? verticalModified = -1;
??????? }
?
??????? forceDir_Horizontal = m_DriftOffset * transform.forward;???????
??? }
??
??? //加力移動
??? public void AddForceToMove()
??? {
??????? //計算合力
??????? Vector3 tempForce = verticalModified * currentForce * forceDir_Horizontal;
?
??????? if (!isGround)? //如不在地上,則加重力
??????? {
??????????? tempForce = tempForce + gravity * Vector3.down;
??????? }
?
??????? kartRigidbody.AddForce(tempForce, ForceMode.Force);
??? }?
?
4 加速
放開空格鍵時,如果是漂移狀態(tài),則改變當前的力,并且激活拖尾,然后力再慢慢遞減。
//加速
public void Boost(float boostForce)
??? {
??????? //按照漂移等級加速:1 / 1.1 / 1.2
??????? currentForce = (1 + (int)driftLevel / 10) * boostForce;
??????? EnableTrail();
??? }
?
//力遞減
??? public void ReduceForce()
??? {
??????? float targetForce = currentForce;
??????? if (isGround && v_Input == 0)
??????? {
??????????? targetForce = 0;
??????? }
??????? else if (currentForce > normalForce)??? //用于加速后回到普通狀態(tài)
??????? {
??????????? targetForce = normalForce;
??????? }
?
??????? if (currentForce <= normalForce)
??????? {
??????????? DisableTrail();
??????? }
???????
??????? //每秒60遞減,可調(diào)
??????? currentForce = Mathf.MoveTowards(currentForce, targetForce, 60 * Time.fixedDeltaTime);
??? }
?
5 漂移特效變色
馬里奧賽車中,漂移火花的顏色會隨著時間或者搓方向鍵改變,這次只實現(xiàn)隨著時間變化。
(1)腳本聲明Color[]數(shù)組變量driftColors,并在Inspector面板中指定顏色。
(2)聲明一個枚舉變量作為漂移等級,當漂移時,在FixedUpdate中計算漂移等級。
? ? public void CalculateDriftingLevel()
??? {
??????? driftPower += Time.fixedDeltaTime;
??????? //0.7秒提升一個漂移等級
??????? if (driftPower < 0.7)??
??????? {
??????????? driftLevel = DriftLevel.One;
??????? }
??????? else if (driftPower < 1.4)
??????? {
??????????? driftLevel = DriftLevel.Two;
??????? }
??????? else
??????? {
??????????? driftLevel = DriftLevel.Three;
??????? }
??? }
?
(3)在場景中把粒子拖為KartPlayer的子對象,并移到車輪底下,在Start()里獲取所有特效。

? ? void Start()
??? {
??????? //...
??????? //漂移時車輪下粒子特效
??????? wheelsParticeles = wheelsParticeleTrans.GetComponentsInChildren<ParticleSystem>();
??? }
?
(4)根據(jù)漂移等級(作為顏色數(shù)組下標),改變粒子特效的顏色,這次用到的粒子特效是由多層疊加而成,所以需要遍歷賦值。
? ? public void ChangeDriftColor()
??? {
??????? foreach (var tempParticle in wheelsParticeles)
??????? {
??????????? var t = tempParticle.main;
??????????? t.startColor = driftColors[(int)driftLevel];
??????? }
??? }
?
此外,如果需要比較好的攝像機跟隨與特效效果可以使用Cinemachine進行調(diào)節(jié),有興趣的小伙伴可以參考工程里的設置。
結語:本篇中,通過旋轉剛體并給剛體加力的方式,實現(xiàn)了卡丁車的漂移、轉向、移動以及加速等功能,影響手感的參數(shù)強烈建議自己再調(diào)整,希望大家玩得開心,漂移時粒子特效的制作以后有機會再介紹~

參考資料:
Karting Microgame:Asset Store里可以下載,對不依賴物理系統(tǒng)來實現(xiàn)漂移感興趣的小伙伴推薦參考這個工程里的腳本~
mix and jam:https://www.youtube.com/watch?v=Ki-tWT50cEQ
鏈接:https://pan.baidu.com/share/init?surl=3RIAzicgicL3xlnz2PZfEg
提取碼:cghc

咱們的游戲開發(fā)交流群也歡迎強勢插入:869551769
希望參與線下游戲開發(fā)學習的,歡~~~~~~迎訪問:http://www.levelpp.com/