Unity – Sample Car Damage & Open car doors on impact

前言

在我們先前有介紹過外掛的Unity 3D – Mesh Deformer來製作出Mesh碰撞的變形,但是在現實的世界裡面汽車的板件以及零件是相當多的,今天主要的是要練習利用C#語法創建Car Damage的效果以及車門以及前蓋可以隨著被沖擊所打開的效果,要接近真實汽車破狀效果的路上有許多問題要解決,譬如: 零件掉落、車門脫落、冒煙、金屬碰撞產生的火花等等都是一個一個需要學習的階段,今天雖然是Sample化的Car Damage以及車門可以依照物理產生卡開等等的基礎設定。

Car Damage

  1. 猶如我們要練習之前,我們需要車子跟可以測試的場景,並需要可以控制車子的Car Controller ,如果需要先行設定 Car Controller 的可以參考Unity Car Controller – Wheel Collider Physics這篇

前置作業做好以後我們就可以開始製作Car Damage的C#語法提供汽車使用了。

如果不太了解語法的用途可以看以下的語法詳細註解以利學習

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarDamage : MonoBehaviour
{
    public float maxMoveDelta = 1.0f; // 移動最大距離 (in meters)
    public float maxCollisionStrength = 50.0f;
    public float YforceDamp = 0.1f; // 0.0 - 1.0
    public float demolutionRange = 0.5f;
    public float impactDirManipulator = 0.0f;
    public MeshFilter[] MeshList;

    private MeshFilter[] meshfilters;
    private float sqrDemRange;

    //保存Vertex數據
    private struct permaVertsColl
    {
        public Vector3[] permaVerts;
    }
    private permaVertsColl[] originalMeshData;
    int i;

    public void Start()
    {

        if (MeshList.Length > 0)
            meshfilters = MeshList;
        else
            meshfilters = GetComponentsInChildren<MeshFilter>();

        sqrDemRange = demolutionRange * demolutionRange;

        LoadOriginalMeshData();

    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.R)) Repair();
    }

    void LoadOriginalMeshData()
    {
        originalMeshData = new permaVertsColl[meshfilters.Length];
        for (i = 0; i < meshfilters.Length; i++)
        {
            originalMeshData[i].permaVerts = meshfilters[i].mesh.vertices;
        }
    }

    void Repair()
    {
        for (int i = 0; i < meshfilters.Length; i++)
        {
            meshfilters[i].mesh.vertices = originalMeshData[i].permaVerts;
            meshfilters[i].mesh.RecalculateNormals();
            meshfilters[i].mesh.RecalculateBounds();
        }
    }

    public void OnCollisionEnter(Collision collision)
    {
        Vector3 colRelVel = collision.relativeVelocity;
        colRelVel.y *= YforceDamp;

        Vector3 colPointToMe = transform.position - collision.contacts[0].point;

        // Dot = 與碰撞點的角度,frontal = 最高傷害,strip = 最低傷害
        float colStrength = colRelVel.magnitude * Vector3.Dot(collision.contacts[0].normal, colPointToMe.normalized);

        OnMeshForce(collision.contacts[0].point, Mathf.Clamp01(colStrength / maxCollisionStrength));

    }

    // 如果由SendMessage()調用,則只有1個參數
    public void OnMeshForce(Vector4 originPosAndForce)
    {
        OnMeshForce((Vector3)originPosAndForce, originPosAndForce.w);
    }

    public void OnMeshForce(Vector3 originPos, float force)
    {
        // Force應在0.0到1.0之間
        force = Mathf.Clamp01(force);

        for (int j = 0; j < meshfilters.Length; ++j)
        {
            Vector3[] verts = meshfilters[j].mesh1.0.vertices;

            for (int i = 0; i < verts.Length; ++i)
            {
                Vector3 scaledVert = Vector3.Scale(verts[i], transform.localScale);
                Vector3 vertWorldPos = meshfilters[j].transform.position + (meshfilters[j].transform.rotation * scaledVert);
                Vector3 originToMeDir = vertWorldPos - originPos;
                Vector3 flatVertToCenterDir = transform.position - vertWorldPos;
                flatVertToCenterDir.y = 0.0f;

                // 0.5 - 1 => 45° to 0°  / 當前頂點比邊界中心更接近exploPos
                if (originToMeDir.sqrMagnitude < sqrDemRange) //dot > 0.8f )
                {
                    float dist = Mathf.Clamp01(originToMeDir.sqrMagnitude / sqrDemRange);
                    float moveDelta = force * (1.0f - dist) * maxMoveDelta;

                    Vector3 moveDir = Vector3.Slerp(originToMeDir, flatVertToCenterDir, impactDirManipulator).normalized * moveDelta;

                    verts[i] += Quaternion.Inverse(transform.rotation) * moveDir;
                }

            }

            meshfilters[j].mesh.vertices = verts;
            meshfilters[j].mesh.RecalculateBounds();
        }
    }
}

2. Car Damage 的語法建立好以後我們在車機的最外層直接使用,並將所有需要u有Damage的物件都拖曳到 Car Damage 的Mesh List裡面,這邊要注意在製作這些之前要記得將車子的RigiBody、MeshCollider設定好才會有效果。

Max Move Delta – 最大移動增量

Max Collision Strength – 最大碰撞強度

Y Force Damp – Y力道阻力

Demolution Range – 降級範圍

Impact Dir Manipulator – 方向盤操縱器

以下為我實際測試覺得效果還可以的數據

設定好以後我們就可以按下Play直接測試這次的Car Damage的效果如何,看起來其實目前是堪用的,但如果要接近真實世界的話應該還要有破碎的車燈殼以及板金刮傷的感覺,目前看起來無法達到這麼真實的效果。

Car Doors Open On Impact

在學習Car Damage的過程中常常在想要怎麼讓車上的零件可以隨著物理衝擊變成可動的狀態,如果有玩過賽車遊戲的朋友一定都知道汽車受到一定的衝擊以後都會出現車門或是引擎蓋噴走的效果,這一篇文章就是跟著大家一起走入讓車門動起來的入門語法。

  1. 首先我們要有個概念,要製作可動的物件的時候一定都要有Rigiboy、Collider、HinghJoint在每一個需要可動的物件上面。下圖是我將今天要讓他物理可動的物件抓出來給大家看一下,對! 這三個物件等等都會依照物理現象動起來。

接著我們將這三個物件加入Sphere Collider,讓這些物件就算打開了也是帶有自己的碰撞偵測,如果有低模的Collider也是可以直接使用MeshCollider。

如果大家跟我一樣都是使用Sphere Collider的話記得要縮小Radius的數值,如果太大的話反而會影響到到時候碰撞偵測的效果。

2. 接著我們就可以開始設定我們的Hinge Joint,這邊可以將Hinge Joint想做是物體物件的Joint(骨幹),只是沒有人體骨幹這麼的直覺可以看得到。

這邊很重要的是我們要將我們所要可動的物件拖曳到Hinge Joint裡面的Connected Body,這樣在這些物件裡面就會出現Rigibody可以設定了。

這邊的Rigibody我們只要設定Mass(質量)即可,因為最主要的RigiBody設定是在整個汽車的最外層做設定。

接著我們就可以開始針對 Imapact Door 做C#語法

using UnityEngine;

public class ImpactDoorSystem : MonoBehaviour
{

    public float MAX_OPEN = 50f; //Max limit of HingeJoint
    public float MIN_OPEN = 0f;  //Min limit of HingeJoint

    HingeJoint m_hinge;
    JointLimits m_limits;

    private void Awake()
    {
        m_hinge = GetComponent<HingeJoint>();
        m_limits = m_hinge.limits;
    }

    void Update()
    {
        //按下R可以將重置
        if (Input.GetKeyDown(KeyCode.R))
        {
            m_limits.min = 0;
            m_limits.max = 0;

            m_hinge.limits = m_limits;
        }
    }

    //設置發生碰撞時的HingeJoint限制

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.relativeVelocity.magnitude > 10) //Impact speed
        {
            m_limits.min = MIN_OPEN;
            m_limits.max = MAX_OPEN;

            m_hinge.limits = m_limits;
        }
    }

}

寫好語法以後我們就可以剛剛剛的語法加入到所要可動的物件裡,這邊記得我們只需要在要動的物件上增加語法。

接著我們需要將車門打開的位置以及角度在Edit Angular Limits裡面的Anchor跟Axis做調整,簡單來說就是圓的中心點要依照真實世界上的車門六角鎖位置。

因為車門的開啟角度蠻需要一點時間調整,我們要按下Edit Angular Limits並將Use Limits 打勾就可以看到一個Bar可以拉一下就知道自己目前的車門開啟角度是否正確。

大家可以參考下圖的開啟方向就會知道自己所設定的Anchor跟Axis是否有誤了。

引擎蓋的部分也是利用 Edit Angular Limits 的方式調整數值就可以知道自己的數值是否正確

全部設定好以後我們就可以按下Game play來看看今天的成果,因為語法裡面有寫入重置的效果,所以只要按下”R”就不會看到車門一直在那邊飄了,不過看到車門隨著開車的方向而產生物理現象擺動真的很感動啊。

測試一下引擎蓋的部分也是有依照衝擊的現象而翻動了起來。

小記

這次的練習對於未來碰撞以及車禍效果來說雖然只是個開頭,但是看到原本只是個單純的模型變成了可以有毀損以及車門等可動的物理現象,真的是個很不錯的練習結果,當然如果要接近真實世界的效果還有許多問題需要解決,如果大家有不錯的作法或是感想也可以互相分享。

參考資料:https://www.youtube.com/watch?v=EA28lqMERuI

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *