天天看點

Unity中實作賽車遊戲

一:前言

一般制作一款賽車遊戲,賽車的面闆結構是由三部分組成:車身(車的碰撞器)、四個輪子、四個輪子的碰撞器(四個輪子與四個輪子的碰撞器需要分開)

Unity中實作賽車遊戲

二:WheelCollider元件

Unity中實作賽車遊戲

——Mass:車輪的品質

——Radius:車輪的半徑

——Wheel Damping Rate:輪胎受到的阻力

注意:

——車身上必須挂載Rigibody元件WheelCollider才能夠顯示出來

——車抖動或者發生奇怪的現象:調整車身上的Rigibody元件的Mass屬性或調整懸挂高度

——Wheel Collider是獨立于Unity實體引擎計算的摩擦,是基于滑動的摩擦模型。這樣就允許模拟更現實的行為,但它将會忽略實體材質的設定

——如果車很容易翻車,降低車身上Rigibody元件的center of mass

三:代碼控制車的移動

Unity中實作賽車遊戲

實作了車的前進,轉向,按下空格刹車,面闆中可以設定四驅還是兩驅

using UnityEngine;
using System;

public class Car : MonoBehaviour
{
    public AxleInfo[] axleInfos;//車輪資訊

    [Header("重心")]
    public Vector3 massOfCenter;//重心

    [Header("車的一些參數")]
    public float maxSteer = 30;//轉彎的力
    public float maxMotor = 300;//動力(前進的力)
    public float maxBrake = 500000000;//刹車的力

    private void Awake()
    {
        GetComponent<Rigidbody>().centerOfMass = massOfCenter;
    }

    private void Update()
    {
        //刹車
        Brake();

        //更新輪子的位置
        UpdateWheelTrans();
    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移動
    /// </summary>
    private void Move()
    {
        float motor = maxMotor * Input.GetAxis("Vertical");
        float steer = maxSteer * Input.GetAxis("Horizontal");
        foreach (AxleInfo info in axleInfos)
        {
            //控制前進和轉向
            if (info.motor)
            {
                info.rightWheel_wc.motorTorque = motor;
                info.leftWheel_wc.motorTorque = motor;
            }
            if (info.steer)
            {
                info.rightWheel_wc.steerAngle = steer;
                info.leftWheel_wc.steerAngle = steer;
            }
        }
    }

    /// <summary>
    /// 刹車
    /// </summary>
    private void Brake()
    {
        float brake = Input.GetKey(KeyCode.Space) ? maxBrake : 0;

        foreach (AxleInfo info in axleInfos)
        {
            if (info.brake)
            {
                info.rightWheel_wc.brakeTorque = brake;
                info.leftWheel_wc.brakeTorque = brake;
            }
        }
    }

    /// <summary>
    /// 更新輪子的位置
    /// </summary>
    private void UpdateWheelTrans()
    {
        foreach (AxleInfo info in axleInfos)
        {
            Quaternion r_qua;
            Quaternion l_qua;
            Vector3 r_pos;
            Vector3 l_pos;
            info.rightWheel_wc.GetWorldPose(out r_pos, out r_qua);
            info.leftWheel_wc.GetWorldPose(out l_pos, out l_qua);

            info.rightWheel_trans.transform.position = r_pos;
            info.leftWheel_trans.transform.position = l_pos;
            info.rightWheel_trans.rotation = r_qua;
            info.leftWheel_trans.rotation = Quaternion.Euler(l_qua.eulerAngles.x, l_qua.eulerAngles.y + 180, l_qua.eulerAngles.z);
        }
    }
}

/// <summary>
/// 車輪資訊類
/// </summary>
[Serializable]
public class AxleInfo
{
    public WheelCollider rightWheel_wc;//右輪子的WheelCollider碰撞器
    public WheelCollider leftWheel_wc;//左輪子的WheelCollider碰撞器
    public Transform rightWheel_trans;//右輪子的Transform元件
    public Transform leftWheel_trans;//左輪子的Transform元件
    [Header("是否有動力")]
    public bool motor;//是否有動力(四驅or兩驅)
    [Header("是否能夠控制轉向")]
    public bool steer;//是否能夠控制轉向
    [Header("是否有刹車動力")]
    public bool brake;//是否有刹車動力
}           

四:簡易漂移效果

using System;
using UnityEngine;

public class Car : MonoBehaviour
{
    public AxleInfo[] axleInfos;//車輪資訊

    [Header("重心")]
    public Vector3 massOfCenter;//重心

    [Header("車的一些參數")]
    public float maxSteer = 30;//轉彎的力
    public float maxMotor = 300;//動力(前進的力)
    public float maxBrake = 500000000;//刹車的力
    public float driftForce = 150;//漂移的力

    private float currentSteer;//目前的轉向

    private void Awake()
    {
        GetComponent<Rigidbody>().centerOfMass = massOfCenter;

        //更新輪子的位置
        UpdateWheelTrans();
    }

    private void Update()
    {
        //刹車
        Brake();
    }

    private void FixedUpdate()
    {
        Move();
        Drift();
    }

    /// <summary>
    /// 移動
    /// </summary>
    private void Move()
    {
        float motor = maxMotor * Input.GetAxis("Vertical");
        foreach (AxleInfo info in axleInfos)
        {
            //控制前進
            if (info.motor)
            {
                info.rightWheel_wc.motorTorque = motor;
                info.leftWheel_wc.motorTorque = motor;
            }
        }
    }

    /// <summary>
    /// 漂移
    /// </summary>
    private void Drift()
    {
        float h = Input.GetAxis("Horizontal");
        currentSteer += h * driftForce * Time.fixedDeltaTime;
        transform.rotation = Quaternion.Euler(0, currentSteer, 0);
    }

    /// <summary>
    /// 刹車
    /// </summary>
    private void Brake()
    {
        float brake = Input.GetKey(KeyCode.Space) ? maxBrake : 0;

        foreach (AxleInfo info in axleInfos)
        {
            if (info.brake)
            {
                info.rightWheel_wc.brakeTorque = brake;
                info.leftWheel_wc.brakeTorque = brake;
            }
        }
    }

    /// <summary>
    /// 更新輪子的位置
    /// </summary>
    private void UpdateWheelTrans()
    {
        foreach (AxleInfo info in axleInfos)
        {
            Quaternion r_qua;
            Quaternion l_qua;
            Vector3 r_pos;
            Vector3 l_pos;
            info.rightWheel_wc.GetWorldPose(out r_pos, out r_qua);
            info.leftWheel_wc.GetWorldPose(out l_pos, out l_qua);

            info.rightWheel_trans.transform.position = r_pos;
            info.leftWheel_trans.transform.position = l_pos;
            info.rightWheel_trans.rotation = r_qua;
            info.leftWheel_trans.rotation = Quaternion.Euler(l_qua.eulerAngles.x, l_qua.eulerAngles.y + 180, l_qua.eulerAngles.z);
        }
    }
}

/// <summary>
/// 車輪資訊類
/// </summary>
[Serializable]
public class AxleInfo
{
    public WheelCollider rightWheel_wc;//右輪子的WheelCollider碰撞器
    public WheelCollider leftWheel_wc;//左輪子的WheelCollider碰撞器
    public Transform rightWheel_trans;//右輪子的Transform元件
    public Transform leftWheel_trans;//左輪子的Transform元件
    [Header("是否有動力")]
    public bool motor;//是否有動力(四驅or兩驅)
    [Header("是否有刹車動力")]
    public bool brake;//是否有刹車動力
}           

五:繪制曲線圖動态檢視車的速度變化

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System;

/// <summary>
/// This is a really simple graphing solution for the WheelCollider's friction slips.
/// </summary> 
public class GraphOverlay : MonoBehaviour
{
    [Serializable]
    public class WheelConfig
    {
        public WheelCollider collider;
        public bool visible;

        public List<float> longData = new List<float>();
        public List<float> latData = new List<float>();
    }

    public int thickness = 1;
    public float width = 0.35f;
    public float height = 0.34f;
    public float widthSeconds = 2f;
    public float heightMeters = 3f;
    public Color32 bgColor = new Color32(50, 50, 50, 150);
    public Color32 forwardColor = Color.red;
    public Color32 sidewaysColor = Color.green;
    public Color32 guidesColor = Color.blue;
    public Color32 zeroColor = Color.black;
    public Rigidbody vehicleBody;
    [Range(0f, 10f)]
    public float timeTravel;
    public List<WheelConfig> wheelConfigs = new List<WheelConfig>();

    Color32[] m_PixelsBg;
    Color32[] m_Pixels;
    Text m_SpeedText;
    Texture2D m_Texture;
    int m_WidthPixels;
    int m_HeightPixels;

    const string k_EventSystemName = "EventSystem";
    const string k_GraphCanvasName = "GraphCanvas";
    const string k_GraphImageName = "RawImage";
    const string k_InfoTextName = "InfoText";
    const float k_GUIScreenEdgeOffset = 10f;
    const int k_InfoFontSize = 16;
    const float k_MaxRecordTimeTravel = 0.01f;

    void Start()
    {
        // Add GUI infrastructure.
        var eventSystem = new GameObject(k_EventSystemName);
        eventSystem.AddComponent<EventSystem>();
        eventSystem.AddComponent<StandaloneInputModule>();

        var canvas = new GameObject(k_GraphCanvasName);
        var canvasScript = canvas.AddComponent<Canvas>();
        canvas.AddComponent<CanvasScaler>();
        canvas.AddComponent<GraphicRaycaster>();

        canvasScript.renderMode = RenderMode.ScreenSpaceOverlay;

        // Add a raw image object.
        var rawImageGO = new GameObject(k_GraphImageName);
        rawImageGO.transform.parent = canvas.transform;
        var imageComponent = rawImageGO.AddComponent<RawImage>();
        var imageXform = rawImageGO.GetComponent<RectTransform>();

        imageXform.anchorMin = Vector2.up;
        imageXform.anchorMax = Vector2.up;
        imageXform.pivot = Vector2.up;
        imageXform.anchoredPosition = new Vector2(k_GUIScreenEdgeOffset, -k_GUIScreenEdgeOffset);

        // Set up our texture.
        m_WidthPixels = (int)(Screen.width * width);
        m_HeightPixels = (int)(Screen.height * height);
        m_Texture = new Texture2D(m_WidthPixels, m_HeightPixels);

        imageComponent.texture = m_Texture;
        imageComponent.SetNativeSize();

        m_Pixels = new Color32[m_WidthPixels * m_HeightPixels];
        m_PixelsBg = new Color32[m_WidthPixels * m_HeightPixels];

        for (int i = 0; i < m_Pixels.Length; ++i)
        {
            m_PixelsBg[i] = bgColor;
        }

        SetupWheelConfigs();

        // Add speed textbox.
        var infoGo = new GameObject(k_InfoTextName);
        infoGo.transform.parent = canvas.transform;
        m_SpeedText = infoGo.AddComponent<Text>();
        var textXform = infoGo.GetComponent<RectTransform>();

        m_SpeedText.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;
        m_SpeedText.fontSize = k_InfoFontSize;

        textXform.anchorMin = Vector2.up;
        textXform.anchorMax = Vector2.up;
        textXform.pivot = Vector2.up;
        textXform.anchoredPosition = new Vector2(k_GUIScreenEdgeOffset, -m_HeightPixels - k_GUIScreenEdgeOffset);
        var rect = textXform.sizeDelta;
        rect.x = m_WidthPixels;
        textXform.sizeDelta = rect;
    }

    void Reset()
    {
        SetupWheelConfigs();
    }


    public void SetupWheelConfigs()
    {
        wheelConfigs.Clear();

        // Locate all the wheels.
        if (vehicleBody)
        {
            foreach (var wheel in vehicleBody.GetComponentsInChildren<WheelCollider>())
            {
                var wheelConfig = new WheelConfig();
                wheelConfig.visible = true;
                wheelConfig.collider = wheel;

                wheelConfigs.Add(wheelConfig);
            }
        }
    }

    void FixedUpdate()
    {
        foreach (var wheelConfig in wheelConfigs)
        {
            WheelHit hit;
            if (!wheelConfig.collider.GetGroundHit(out hit))
            {
                // No hit!
                continue;
            }

            if (Mathf.Abs(timeTravel) < k_MaxRecordTimeTravel)// TODO: solve this mystery
            {
                wheelConfig.longData.Add(hit.forwardSlip);
                wheelConfig.latData.Add(hit.sidewaysSlip);
            }
        }
    }

    void Update()
    {
        // Clear.
        Array.Copy(m_PixelsBg, m_Pixels, m_Pixels.Length);

        // Draw guides.
        DrawLine(new Vector2(0f, m_HeightPixels * 0.5f), new Vector2(m_WidthPixels, m_HeightPixels * 0.5f), zeroColor);

        float guide = 1f / heightMeters * m_HeightPixels;
        float upperGuide = m_HeightPixels * 0.5f - guide;
        float lowerGuide = m_HeightPixels * 0.5f + guide;
        DrawLine(new Vector2(0f, upperGuide), new Vector2(m_WidthPixels, upperGuide), guidesColor);
        DrawLine(new Vector2(0f, lowerGuide), new Vector2(m_WidthPixels, lowerGuide), guidesColor);

        // Draw graphs.
        int samplesOnScreen = (int)(widthSeconds / Time.fixedDeltaTime);
        int stepsBack = (int)(timeTravel / Time.fixedDeltaTime);

        foreach (var wheelConfig in wheelConfigs)
        {
            if (!wheelConfig.visible)
                continue;

            int cursor = Mathf.Max(wheelConfig.longData.Count - samplesOnScreen - stepsBack, 0);

            // Forward slip.
            for (int i = cursor; i < wheelConfig.longData.Count - 1 - stepsBack; ++i)
            {
                DrawLine(PlotSpace(cursor, i, wheelConfig.longData[i]), PlotSpace(cursor, i + 1, wheelConfig.longData[i + 1]), forwardColor);
            }

            // Sideways slip.
            for (int i = cursor; i < wheelConfig.latData.Count - 1 - stepsBack; ++i)
            {
                DrawLine(PlotSpace(cursor, i, wheelConfig.latData[i]), PlotSpace(cursor, i + 1, wheelConfig.latData[i + 1]), sidewaysColor);
            }
        }

        m_Texture.SetPixels32(m_Pixels);
        m_Texture.Apply();

        if (vehicleBody)
            m_SpeedText.text = string.Format("Speed: {0:0.00} m/s", vehicleBody.velocity.magnitude);
    }

    // Convert time-value to the pixel plot space.
    Vector2 PlotSpace(int cursor, int sample, float value)
    {
        float x = (sample - cursor) * Time.fixedDeltaTime / widthSeconds * m_WidthPixels;

        float v = value + heightMeters / 2;
        float y = v / heightMeters * m_HeightPixels;

        if (y < 0)
            y = 0;

        if (y >= m_HeightPixels)
            y = m_HeightPixels - 1;

        return new Vector2(x, y);
    }

    void DrawLine(Vector2 from, Vector2 to, Color32 color)
    {
        int i;
        int j;

        if (Mathf.Abs(to.x - from.x) > Mathf.Abs(to.y - from.y))
        {
            // Horizontal line.
            i = 0;
            j = 1;
        }
        else
        {
            // Vertical line.
            i = 1;
            j = 0;
        }

        int x = (int)from[i];
        int delta = (int)Mathf.Sign(to[i] - from[i]);
        while (x != (int)to[i])
        {
            int y = (int)Mathf.Round(from[j] + (x - from[i]) * (to[j] - from[j]) / (to[i] - from[i]));

            int index;
            if (i == 0)
                index = y * m_WidthPixels + x;
            else
                index = x * m_WidthPixels + y;

            index = Mathf.Clamp(index, 0, m_Pixels.Length - 1);
            m_Pixels[index] = color;

            x += delta;
        }
    }
}           

六:模仿《全民漂移》的效果

using UnityEngine;
using DG.Tweening;
using System;

//運動方向
public enum MoveDir
{
    Forward,
    Right,
}

public class Car3 : MonoBehaviour
{
    private Rigidbody rigi;//剛體元件

    public AxleInfo[] axleInfos;//車輪資訊

    [Header("重心")]
    public Vector3 massOfCenter;//重心

    [Header("車的一些參數")]
    public float maxMotor = 1500;//動力(前進的力)
    public float driftTime;//漂移的時間

    private bool completeDrift = true;//是否完成漂移

    private MoveDir dir;//運動方向

    private void Awake()
    {
        rigi = GetComponent<Rigidbody>();

        rigi.centerOfMass = massOfCenter;
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Drift(MoveDir.Forward);
        }
        else if (Input.GetMouseButtonUp(0))
        {
            Drift(MoveDir.Right);
        }

        //對使用WheelCollider産生的問題進行處理
        if (completeDrift)
        {
            ControlAngle();
            ControlCentrifugalForce();
        }

        Debug.Log("目前速度——————————>" + rigi.velocity);
    }

    private void FixedUpdate()
    {
        Move();
    }

    /// <summary>
    /// 移動
    /// </summary>
    private void Move()
    {
        float motor = maxMotor;
        foreach (AxleInfo info in axleInfos)
        {
            //控制前進和轉向
            if (info.motor)
            {
                info.rightWheel_wc.motorTorque = motor;
                info.leftWheel_wc.motorTorque = motor;
            }
        }
    }

    /// <summary>
    /// 漂移
    /// </summary>
    /// <param name="currentDir">目前的方向</param>
    private void Drift(MoveDir currentDir)
    {
        //判斷目标角度和目标方向
        Vector3 targetAngle = Vector3.zero;
        MoveDir targetDir = MoveDir.Right;
        if (currentDir == MoveDir.Right)
        {
            targetDir = MoveDir.Forward;
            targetAngle = Vector3.zero;

        }
        else if (currentDir == MoveDir.Forward)
        {
            targetDir = MoveDir.Right;
            targetAngle = new Vector3(0, 90, 0);
        }

        //設定目标角度和目标方向
        this.dir = targetDir;
        completeDrift = false;
        transform.DOLocalRotate(targetAngle, driftTime).OnComplete(
      () =>
      {
          completeDrift = true;
      }
      );
    }

    #region 對使用WheelCollider産生的問題進行處理

    /// <summary>
    /// 控制離心力
    /// </summary>
    private void ControlCentrifugalForce()
    {
        Vector3 v = rigi.velocity;
        switch (dir)
        {
            case MoveDir.Right:
                if (v.z > 0)
                {
                    v.z -= Time.deltaTime * 2;
                }
                break;
            case MoveDir.Forward:
                if (v.x < 0)
                {
                    v.x += Time.deltaTime * 2;
                }
                break;
        }
        rigi.velocity = v;
    }

    /// <summary>
    /// 控制角度
    /// </summary>
    private void ControlAngle()
    {
        Vector3 angle = transform.rotation.eulerAngles;
        switch (dir)
        {
            case MoveDir.Right:
                angle.y = 90;
                break;
            case MoveDir.Forward:
                angle.y = 0;
                break;
        }
        transform.rotation = Quaternion.Euler(angle);
    }

    #endregion
}

/// <summary>
/// 車輪資訊類
/// </summary>
[Serializable]
public class AxleInfo
{
    public WheelCollider rightWheel_wc;//右輪子的WheelCollider碰撞器
    public WheelCollider leftWheel_wc;//左輪子的WheelCollider碰撞器
    public Transform rightWheel_trans;//右輪子的Transform元件
    public Transform leftWheel_trans;//左輪子的Transform元件
    [Header("是否有動力")]
    public bool motor;//是否有動力(四驅or兩驅)
    [Header("是否能夠控制轉向")]
    public bool steer;//是否能夠控制轉向
}