一:前言

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


二: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;elseindex = 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;//是否能够控制转向
}

Unity中实现赛车游戏相关推荐

  1. unity中2D飞机游戏手指触屏操作的问题

    unity中2D飞机游戏手指触屏操作的问题 菜鸡记录一下在项目中遇到的坑 下面就是我用的代码 if (UnityEngine.Input.touchCount > 0) //每一帧触摸到的屏幕的 ...

  2. unity 构建迷宫_教程:使用GameDraw在Unity中构建迷宫游戏关卡

    unity 构建迷宫 GameDraw is a 3D modeling extension for Unity developed by Mixed Dimensions that reduces ...

  3. unity中第三人称游戏摄像头遮挡的处理

    在游戏开发过程中难免会做第三人称游戏 如果地形中的设施比较多 或者说做一个房间内的游戏 很可能我们调整好的视角 在游戏过程中会被墙体挡住主角 这样游戏体验感就会大打折扣 就像这样(遮住半个身子还是可以 ...

  4. 在Unity中构建Pong克隆:UI和游戏玩法

    In Part 1 of this tutorial – the Retro Revolution: Building a Pong Clone in Unity – we created a Pon ...

  5. Unity对H5小游戏开发的支持——Project Tiny

      随着微信小游戏跳一跳的大火,游戏市场对于H5小游戏的开发逐渐火爆,作为一名Unity游戏开发从业者,大家对于Unity中开发小游戏的支持也很关注!近日Unity官方发布了Project Tiny ...

  6. 使用Unity创建塔防游戏(Part1)

    How to Create a Tower Defense Game in Unity - Part1 原文作者:Barbara Reichart 文章原译:http://www.cnblogs.co ...

  7. 使用Unity创建塔防游戏(Part2)

    How to Create a Tower Defense Game in Unity – Part 2 原文地址:https://www.raywenderlich.com/107529/unity ...

  8. 学习unity的第一个游戏-Playground

    Playground 项目 unity 论坛链接 Playground 官方教程链接 项目资源及安装 主要参考教程:[unity2021入门]13-官方入门教程系列-02-PlayGround-01​ ...

  9. Unity Android 2021:用C#打造3D ZigZag赛车游戏

    Unity Android 2021 : Build 3D ZigZag Racing Game with C# MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch ...

最新文章

  1. Codeforces ECR50 div2题解
  2. css 书写记录(兼容性)
  3. 实现一个通用的生产者消费者队列(c语言版本)
  4. python contextlib closing
  5. final、finally 和 finalize的区别
  6. Hadoop学习笔记—6.Hadoop Eclipse插件的使用
  7. python函数式编程-装饰器
  8. 最新微软Windows Server 2008之一 系统安装
  9. AForge处理视频和拍照(暂时没有音频)
  10. 记录一下java的常用单词
  11. namecheap关闭域名自动续费的方法步骤
  12. java 自定义的异常类_java自定义异常类实例讲解
  13. 列表中循环添加字典出现覆盖现象的问题
  14. UE5 学习2- 快捷键
  15. 近两日学的Linux系统基础命令总结
  16. 刘德华开抖音了,一键下载华仔所有无水印视频
  17. iphoneXR的tabbar底部图片的适配
  18. 六个免费网站统计平台工具对比推荐 你的网站统计用哪个
  19. php隐藏后门,php后门隐藏技巧
  20. 浩瀚抄码器冻品进口肉类抄重工具怎么用抄码器?抄码器如何用?便携式抄码器牛羊肉手持抄码器

热门文章

  1. KISSY基础篇乄KISSY之Anim(1)
  2. 基于QT的在线打字练习软件助手(C/S模型)
  3. 肥胖与高血压有什么关系?
  4. 怎么打开计算机的音乐模式,xp开机音乐怎么设置 如何设置开机音乐
  5. 学会使用QT的帮助文档
  6. Verilog实现一个8位带进位全加器
  7. element-ui中导航菜单默认激活子菜单的第一项
  8. 15K服务器硬盘对齐分数,服务器硬盘的IOPS计算方法
  9. php access 教程 pdf,Access 2007 以PDF 格式保存文件
  10. MySQL中的通用查询日志(General Query Log)