Unity制作赛车游戏相关技术

一:前言

一般制作一款赛车游戏,赛车的面板结构是由三部分组成:车身(车的碰撞器)、四个轮子、四个轮子的碰撞器(四个轮子与四个轮子的碰撞器需要分开)


二:WheelCollider组件


——Mass:车轮的质量
——Radius:车轮的半径
——Wheel Damping Rate:轮胎受到的阻力

注意:
——车身上必须挂载Rigibody组件WheelCollider才能够显示出来
——车抖动或者发生奇怪的现象:调整车身上的Rigibody组件的Mass属性或调整悬挂高度
——Wheel Collider是独立于Unity物理引擎计算的摩擦,是基于滑动的摩擦模型。这样就允许模拟更现实的行为,但它将会忽略物理材质的设置
——如果车很容易翻车,降低车身上Rigibody组件的center of mass


三:代码控制车的移动

实现了车的前进,转向,按下空格刹车,面板中可以设置四驱还是两驱

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;//是否有刹车动力
}

四:简易漂移效果

注意不能使用transform.Translate控制移动,要使用motorTorque控制车移动

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;//是否有刹车动力
}

五:绘制曲线图动态查看车的速度变化


编写以下代码,将脚本挂载到相机物体上,将带有Rigibody组件的车赋值给vehicleBody变量

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;//是否能够控制转向
}
发布了169 篇原创文章 · 获赞 370 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/LLLLL__/article/details/105298788