Unity基础(4)-小赛车练习

Paste_Image.png

引言:今天突发奇想,想搞一个车的项目,话不多说,直接下载,但是之前对它一无所知。好在通过简单的测试,已经解决。运行效果还不错。上图是运行效果

1、导入游戏资源
1-1 导入标准资源包:

最后一个就是拥有车模型的资源包,而且导入Unity中会自动出现一个Standard Assets:


Paste_Image.png
Paste_Image.png

标准资源包里面就有车的模型:


Paste_Image.png

其他资源是我自己下载的:可以加群(Q群:134688909)获取相关资源

Paste_Image.png
1-2让车能够移动

因为系统自带的车模型,拥有移动功能,而且还不错。所以直接拿来当轮子使用

Paste_Image.png

再搞一些跟随车辆:显得有竞技效果,此处也是使用系统资源


Paste_Image.png
2、搭建简单地形:
2-1创建地形
Paste_Image.png
Paste_Image.png
2-2添加地形:
Paste_Image.png
Paste_Image.png
2-3最终效果图:
Paste_Image.png
3、添加游戏对象 - 车
Paste_Image.png
4、脚本一栏:
namespace UnityStandardAssets.Vehicles.Car
{
    //汽车驱动类型
    internal enum CarDriveType
    {
        //四驱
        FrontWheelDrive,
        //后驱
        RearWheelDrive,
        //前驱
        FourWheelDrive
    }
    //速度类型
    internal enum SpeedType
    {
        //英里每小时
        MPH,
        //千米每小时
        KPH
    }

    public class CarController : MonoBehaviour
    {
        [SerializeField] private CarDriveType m_CarDriveType = CarDriveType.FourWheelDrive;
        [SerializeField] private WheelCollider[] m_WheelColliders = new WheelCollider[4];
        [SerializeField] private GameObject[] m_WheelMeshes = new GameObject[4];
        [SerializeField] private WheelEffects[] m_WheelEffects = new WheelEffects[4];
        //重心位置
        [SerializeField] private Vector3 m_CentreOfMassOffset;
        //最大可转角度
        [SerializeField] private float m_MaximumSteerAngle;
        [Range(0, 1)] [SerializeField] private float m_SteerHelper; // 0 is raw physics , 1 the car will grip in the direction it is facing
        [Range(0, 1)] [SerializeField] private float m_TractionControl; // 0 is no traction control, 1 is full interference
        //所有轮胎的扭矩
        [SerializeField] private float m_FullTorqueOverAllWheels;
        //反向扭矩
        [SerializeField] private float m_ReverseTorque;
        //最大刹车扭矩
        [SerializeField] private float m_MaxHandbrakeTorque;
        //最大下压力
        [SerializeField] private float m_Downforce = 100f;
        //速度单位
        [SerializeField] private SpeedType m_SpeedType;
        //最高速度
        [SerializeField] private float m_Topspeed = 200;
        //档位总数
        [SerializeField] private static int NoOfGears = 5;
        //
        [SerializeField] private float m_RevRangeBoundary = 1f;
        //最大滑动距离
        [SerializeField] private float m_SlipLimit;
        //刹车扭矩
        [SerializeField] private float m_BrakeTorque;

        private Quaternion[] m_WheelMeshLocalRotations;
        private Vector3 m_Prevpos, m_Pos;
        private float m_SteerAngle;
        //当前档位
        private int m_GearNum;
        //档位因子
        private float m_GearFactor;
        //上一帧汽车方向
        private float m_OldRotation;
        //当前扭矩
        private float m_CurrentTorque;
        private Rigidbody m_Rigidbody;
        private const float k_ReversingThreshold = 0.01f;

        public bool Skidding { get; private set; }
        public float BrakeInput { get; private set; }
        public float CurrentSteerAngle{ get { return m_SteerAngle; }}
        public float CurrentSpeed{ get { if(m_Rigidbody == null) return 0;return m_Rigidbody.velocity.magnitude*2.23693629f; }}
        public float MaxSpeed{get { return m_Topspeed; }}
        public float Revs { get; private set; }
        public float AccelInput { get; private set; }
        public Rigidbody CarRigidbody { get{return m_Rigidbody;}}
        // Use this for initialization
        private void Start()
        {
            //四个轮胎的自转方向
            m_WheelMeshLocalRotations = new Quaternion[4];
            for (int i = 0; i < 4; i++)
            {
                m_WheelMeshLocalRotations[i] = m_WheelMeshes[i].transform.localRotation;
            }
            //设置重心
            m_WheelColliders[0].attachedRigidbody.centerOfMass = m_CentreOfMassOffset;
            //设置最大手刹扭矩
            m_MaxHandbrakeTorque = float.MaxValue;

            m_Rigidbody = GetComponent<Rigidbody>();
            //设置当前扭矩,初始化的扭矩值跟m_TractionControl大小有关,m_TractionControl决定是否有牵引力,如果m_TractionControl
            //值为0,则当前扭矩直接就是最大值,如果该值为1,则初始扭矩为0,然后汽车启动慢慢增加扭矩力。
            m_CurrentTorque = m_FullTorqueOverAllWheels - (m_TractionControl*m_FullTorqueOverAllWheels);
        }

        //档位更新
        private void GearChanging()
        {
            //这里其实相当于将所有速度进行了划分,f就是指的目前速度处于最大速度的几分之几
            //如果NoOfGears是5,那么就把速度分成5段
            //1档就是0/5 ~ 1/5,
            //2档就是1/5 ~ 2/5,
            //3档就是2/5 ~ 3/5,
            //4档就是3/5 ~ 4/5,
            //5档就是4/5 ~ 5/5,
            //每次先看当前档位是几档,然后看当前速度是否匹配当前档位,如果匹配当前档位没啥事,如果不匹配则看当前速度如果小于当前档位
            //最小速度则减档,如果大于当前档位最大速度则,加档;
            float f = Mathf.Abs(CurrentSpeed/MaxSpeed);
            //计算当前档位的速度上限
            float upgearlimit = (1/(float) NoOfGears)*(m_GearNum + 1);
            //计算当前档位的速度下限
            float downgearlimit = (1/(float) NoOfGears)*m_GearNum;
            //如果当前档位大于0, 则看当前速度是否小于当前档位,如果小于,则果断降档
            if (m_GearNum > 0 && f < downgearlimit)
            {
                m_GearNum--;
            }
            //如果当前档位小于最大档位-1档,且当前速度大于当前档位上限,则果断加档
            //这里我好奇的是,为什么条件不是"if(f>upgearlimit&&(m_GearNum < NoOfGears))",干嘛要减一?
            //我觉得原因可能是因为这个5档,只是理论速度,故意不达到。。。就像一般汽车为了汽车性能稳定,速度设定可以上200码一样,但是没有人上过。。
            if (f > upgearlimit && (m_GearNum < (NoOfGears - 1)))
            {
                m_GearNum++;
            }
        }


        // simple function to add a curved bias towards 1 for a value in the 0-1 range
        //
        private static float CurveFactor(float factor)
        {
            return 1 - (1 - factor)*(1 - factor);
        }


        // unclamped version of Lerp, to allow value to exceed the from-to range
        //非限制版本的插值函数,我刚开始没发现他有啥特别之处,“非限制”怎么体现?直到我发现他的value没有限制0~1的范围才瞬间明白。
        private static float ULerp(float from, float to, float value)
        {
            return (1.0f - value)*from + value*to;
        }

        //计算档位因子
        private void CalculateGearFactor()
        {
            float f = (1/(float) NoOfGears);
            // gear factor is a normalised representation of the current speed within the current gear's range of speeds.
            // We smooth towards the 'target' gear factor, so that revs don't instantly snap up or down when changing gear.
            //我们要让值平滑地想着目标移动,以保证转速不会在变换档位时突然地上高或者降低
            //反向差值,通过当前速度的比例值,找当前速度在当前档位的比例位置,得到的值将是一个0~1范围内的值。
            var targetGearFactor = Mathf.InverseLerp(f*m_GearNum, f*(m_GearNum + 1), Mathf.Abs(CurrentSpeed/MaxSpeed));
            //从当前档位因子向目标档位因子做平滑差值
            m_GearFactor = Mathf.Lerp(m_GearFactor, targetGearFactor, Time.deltaTime*5f);
        }

        //计算转速
        private void CalculateRevs()
        {
            // calculate engine revs (for display / sound)
            //计算引擎转速(只用于显示和声音)
            // (this is done in retrospect - revs are not used in force/power calculations)
            //(我的个人理解:)这个计算是回溯的转速,不能用于力的计算。
            //计算在当前档位上的转速因子(决定在当前档位上的转速)
            CalculateGearFactor();
            //档位因子
            var gearNumFactor = m_GearNum/(float) NoOfGears;
            //计算在当前档位下的最小速度
            var revsRangeMin = ULerp(0f, m_RevRangeBoundary, CurveFactor(gearNumFactor));
            //计算在当前档位下的最大速度
            var revsRangeMax = ULerp(m_RevRangeBoundary, 1f, gearNumFactor);
            //根据当前的转速因子,计算当前的转速
            Revs = ULerp(revsRangeMin, revsRangeMax, m_GearFactor);
        }

        //外部调用的汽车移动控制函数
        public void Move(float steering, float accel, float footbrake, float handbrake)
        {
            Debug.Log ("***************************: " + footbrake + " " + handbrake);
            //保持当前的轮胎网格跟随WheelCollider转动
            for (int i = 0; i < 4; i++)
            {
                Quaternion quat;
                Vector3 position;
                m_WheelColliders[i].GetWorldPose(out position, out quat);
                m_WheelMeshes[i].transform.position = position;
                m_WheelMeshes[i].transform.rotation = quat;
            }
            //clamp input values
            //限定输入值范围
            steering = Mathf.Clamp(steering, -1, 1);
            AccelInput = accel = Mathf.Clamp(accel, 0, 1);
            BrakeInput = footbrake = -1*Mathf.Clamp(footbrake, -1, 0);
            handbrake = Mathf.Clamp(handbrake, 0, 1);

            //Set the steer on the front wheels.
            //设置前轮转角
            //Assuming that wheels 0 and 1 are the front wheels.
            //wheels下标为0、1的就是前轮
            m_SteerAngle = steering*m_MaximumSteerAngle;
            m_WheelColliders[0].steerAngle = m_SteerAngle;
            m_WheelColliders[1].steerAngle = m_SteerAngle;
            //调用角度辅助助手,
            SteerHelper();
            //设置加速/刹车信息到WheelCollider
            ApplyDrive(accel, footbrake);
            //检查速度范围
            CapSpeed();

            //Set the handbrake.
            //设置手刹
            //Assuming that wheels 2 and 3 are the rear wheels.
            //Wheel下标是2、3就是后轮
            if (handbrake > 0f)
            {
                //设置手刹值到后轮,达到减速目的
                var hbTorque = handbrake*m_MaxHandbrakeTorque;
                m_WheelColliders[2].brakeTorque = hbTorque;
                m_WheelColliders[3].brakeTorque = hbTorque;
            }

            //计算转速
            CalculateRevs();
            //改变档位
            GearChanging();
            //施加下压力
            AddDownForce();
            //检查轮胎
            CheckForWheelSpin();
            //牵引力控制系统
            TractionControl();
        }

        //控制车速
        private void CapSpeed()
        {
            float speed = m_Rigidbody.velocity.magnitude;
            switch (m_SpeedType)
            {
                case SpeedType.MPH:
                    //将速度m/s转换为mile/h,以便比较
                    speed *= 2.23693629f;
                    if (speed > m_Topspeed)
                        m_Rigidbody.velocity = (m_Topspeed/2.23693629f) * m_Rigidbody.velocity.normalized;
                    break;

                case SpeedType.KPH:
                //将速度m/s转换为km/h,以便比较
                    speed *= 3.6f;
                    if (speed > m_Topspeed)
                        m_Rigidbody.velocity = (m_Topspeed/3.6f) * m_Rigidbody.velocity.normalized;
                    break;
            }
        }


        private void ApplyDrive(float accel, float footbrake)
        {

            float thrustTorque;
            switch (m_CarDriveType)
            {
                case CarDriveType.FourWheelDrive:
                    thrustTorque = accel * (m_CurrentTorque / 4f);
                    for (int i = 0; i < 4; i++)
                    {
                        m_WheelColliders[i].motorTorque = thrustTorque;
                    }
                    break;

                case CarDriveType.FrontWheelDrive:
                    thrustTorque = accel * (m_CurrentTorque / 2f);
                    m_WheelColliders[0].motorTorque = m_WheelColliders[1].motorTorque = thrustTorque;
                    break;

                case CarDriveType.RearWheelDrive:
                    thrustTorque = accel * (m_CurrentTorque / 2f);
                    m_WheelColliders[2].motorTorque = m_WheelColliders[3].motorTorque = thrustTorque;
                    break;

            }

            for (int i = 0; i < 4; i++)
            {
                if (CurrentSpeed > 5 && Vector3.Angle(transform.forward, m_Rigidbody.velocity) < 50f)
                {
                    m_WheelColliders[i].brakeTorque = m_BrakeTorque*footbrake;
                }
                else if (footbrake > 0)
                {
                    m_WheelColliders[i].brakeTorque = 0f;
                    m_WheelColliders[i].motorTorque = -m_ReverseTorque*footbrake;
                }
            }
        }


        private void SteerHelper()
        {
            for (int i = 0; i < 4; i++)
            {
                WheelHit wheelhit;
                m_WheelColliders[i].GetGroundHit(out wheelhit);
                if (wheelhit.normal == Vector3.zero)
                    return; // wheels arent on the ground so dont realign the rigidbody velocity
                //假如轮子离地,就不用调整汽车角度了
            }

            // this if is needed to avoid gimbal lock problems that will make the car suddenly shift direction
            //这个是为了避免万向锁问题的,万向锁问题会导致汽车突然变换方向(我直到万向锁问题,但不理解下面是怎么避免问题的,我只知道四元数的使用
            //就是为了避免万向锁问题)
            //下面这个If函数的效果就是,假如上一次车体Y方向角度比这次小于十度,就根据相差的度数乘以系数m_SteerHelper
            //计算出这个偏移角度的四元数,然后将刚体速度直接旋转这个偏倚度数,
            //根据代码开头m_SteerHelper的定义,这个做法相当于做了一个角度辅助,不完全凭借WheelCollider物理效果
            //而直接操控速度方向,对车角度进行调整。
            if (Mathf.Abs(m_OldRotation - transform.eulerAngles.y) < 10f)
            {
                var turnadjust = (transform.eulerAngles.y - m_OldRotation) * m_SteerHelper;
                Quaternion velRotation = Quaternion.AngleAxis(turnadjust, Vector3.up);
                m_Rigidbody.velocity = velRotation * m_Rigidbody.velocity;
            }
            m_OldRotation = transform.eulerAngles.y;
        }


        // this is used to add more grip in relation to speed
        private void AddDownForce()
        {
            m_WheelColliders[0].attachedRigidbody.AddForce(-transform.up*m_Downforce*
                                                         m_WheelColliders[0].attachedRigidbody.velocity.magnitude);
        }


        // checks if the wheels are spinning and is so does three things
        //检查轮胎是否旋转
        // 1) emits particles
        //检查是否发射尾气粒子
        // 2) plays tiure skidding sounds
        //播放滑行音
        // 3) leaves skidmarks on the ground
        //去掉刹车印
        // these effects are controlled through the WheelEffects class
        //这些特效都是通过WheelEffects类控制的
        private void CheckForWheelSpin()
        {
            // loop through all wheels
            for (int i = 0; i < 4; i++)
            {
                WheelHit wheelHit;
                m_WheelColliders[i].GetGroundHit(out wheelHit);

                // is the tire slipping above the given threshhold
                //轮胎滑行距离是否超出阈值
                if (Mathf.Abs(wheelHit.forwardSlip) >= m_SlipLimit || Mathf.Abs(wheelHit.sidewaysSlip) >= m_SlipLimit)
                {
                    //超出则发射烟雾粒子
                    m_WheelEffects[i].EmitTyreSmoke();

                    // avoiding all four tires screeching at the same time
                    //避免四个轮胎都同时播放滑行声音
                    // if they do it can lead to some strange audio artefacts
                    //如果那样的话会导致某些奇怪的音效
                    if (!AnySkidSoundPlaying())
                    {
                        m_WheelEffects[i].PlayAudio();
                    }
                    continue;
                }

                // if it wasnt slipping stop all the audio
                //假如没有超出阈值,还没有停止音效,则停止音效
                if (m_WheelEffects[i].PlayingAudio)
                {
                    m_WheelEffects[i].StopAudio();
                }
                // end the trail generation
                //停止烟雾生成
                m_WheelEffects[i].EndSkidTrail();
            }
        }

        // crude traction control that reduces the power to wheel if the car is wheel spinning too much
        //如果汽车轮胎过度滑转,牵引力系统可以控制减少轮胎动力
        private void TractionControl()
        {
            WheelHit wheelHit;
            switch (m_CarDriveType)
            {
                case CarDriveType.FourWheelDrive:
                    // loop through all wheels
                    for (int i = 0; i < 4; i++)
                    {
                        m_WheelColliders[i].GetGroundHit(out wheelHit);

                        AdjustTorque(wheelHit.forwardSlip);
                    }
                    break;

                case CarDriveType.RearWheelDrive:
                    m_WheelColliders[2].GetGroundHit(out wheelHit);
                    AdjustTorque(wheelHit.forwardSlip);

                    m_WheelColliders[3].GetGroundHit(out wheelHit);
                    AdjustTorque(wheelHit.forwardSlip);
                    break;

                case CarDriveType.FrontWheelDrive:
                    m_WheelColliders[0].GetGroundHit(out wheelHit);
                    AdjustTorque(wheelHit.forwardSlip);

                    m_WheelColliders[1].GetGroundHit(out wheelHit);
                    AdjustTorque(wheelHit.forwardSlip);
                    break;
            }
        }


        private void AdjustTorque(float forwardSlip)
        {
            //当向前滑动距离超过阈值后,就说明轮胎过度滑转,则减少牵引力,以降低转速。
            if (forwardSlip >= m_SlipLimit && m_CurrentTorque >= 0)
            {
                m_CurrentTorque -= 10 * m_TractionControl;
            }
            else
            {
                m_CurrentTorque += 10 * m_TractionControl;
                if (m_CurrentTorque > m_FullTorqueOverAllWheels)
                {
                    m_CurrentTorque = m_FullTorqueOverAllWheels;
                }
            }
        }


        private bool AnySkidSoundPlaying()
        {
            for (int i = 0; i < 4; i++)
            {
                if (m_WheelEffects[i].PlayingAudio)
                {
                    return true;
                }
            }
            return false;
        }

        void Update()
        {
            //这两段If分别用来控制汽车X轴/Z轴上汽车不会过度翻转,导致汽车游戏性差。
            if (transform.rotation.eulerAngles.x > 60 && transform.rotation.eulerAngles.x < 300) {
                Quaternion temp = transform.rotation;
                if (transform.rotation.eulerAngles.x < 180) {
                    temp.eulerAngles = new Vector3(60f, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);
                }
                else {
                    temp.eulerAngles = new Vector3(300f, transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.z);
                }
                transform.rotation = temp;
            }
            if (transform.rotation.eulerAngles.z > 25 && transform.rotation.eulerAngles.z < 335) {
                Quaternion temp = transform.rotation;
                if (transform.rotation.eulerAngles.z < 180) {
                    temp.eulerAngles = new Vector3(transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, 25);

                }
                else
                {
                    temp.eulerAngles = new Vector3(transform.rotation.eulerAngles.x, transform.rotation.eulerAngles.y, 335);

                }
                transform.rotation = temp;
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,980评论 3 119
  • 拖着熟悉的行李箱,挤在人山人海的火车站,踏上了回家的路途。不知不觉中,四年的时间匆匆而去;学生时代的最后一个假期就...
    彦珵阅读 734评论 0 0
  • 今天讲到格局,有三个简单易行的提升格局的方法:抓大放小、见人所长、以队为家。 提升格局,通常理解是把关注重点放在更...
    云棫阅读 163评论 4 2
  • 自从三年级的时候知道《追忆似水年华》以后,我再也没有忘记它。 无法否认的是,用两年的时间看完一本书确实是一件挺奇怪...
    闾橘子阅读 257评论 0 2
  • 今天朋友圈很多朋友都在发一段话,内容是这样的:开学第一课, 在教育孩子的时候,你选择了挣钱,不去管教孩子,等孩子...
    冰封鱼阅读 531评论 0 0