Unity3d 官方资源Car的主控脚本CarController翻译与详解

一.综述

在Unity3D官方资源中Standard Assets –>Vehicles–>Car是赛车的相关资源,包括赛车模型、赛车控制脚本等。虽然用起来很方便,但是由于对有些脚本理解不彻底,就用不好。尤其是主要控制脚本CarController,我此次就对这个脚本进行了全面解析

二.CarController

1.主要函数流程图

整个CarController脚本主要是通过共有函数Move对赛车进行控制


2.代码类型定义部分

    //汽车驱动类型
    internal enum CarDriveType
    {
        //四驱
        FrontWheelDrive,
        //后驱
        RearWheelDrive,
        //前驱
        FourWheelDrive
    }
    //速度类型
    internal enum SpeedType
    {
        //英里每小时
        MPH,
        //千米每小时
        KPH
    }

3.代码定义部分

        [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;

4.函数Move

//外部调用的汽车移动控制函数
        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;
            }

            //计算转速,用来供外部调用转速属性Revs来播放引擎声音等
            CalculateRevs();
            //改变档位
            GearChanging();
            //施加下压力
            AddDownForce();
            //检查轮胎
            CheckForWheelSpin();
            //牵引力控制系统
            TractionControl();
        }

5.函数SteerHelper,重点属性:m_SteerHelper

        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物理效果
            //而直接操控速度方向,对车角度进行调整。
            //现在来看,如果m_SteerHelper越小,则调整的角度越小,如果m_SteerHelper为0,则调整的角度为0,。
            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;
        }

我通过实践,发现的效果就是如果m_SteerHelper为0,车转角度时就很死,如果为1,车转角度就特别灵活。所以这个属性得看个人的情况进行调整

6.函数TranctionControl,重点属性m_TractionControl

// 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;
                }
            }
        }

从上面的函数可以看出来,属性m_TractionControl控制着牵引力每次增加或者减少的增量大小。

我们再来看另外一段Start中的代码

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

也就是说,如果m_TractionControl为1,那么一开始的汽车扭矩力就是0了,然后通过上面函数TranctionControl中的代码,每次10个力地增加。

我们会发现,每次启动汽车都会非常缓慢,而且如果爬坡时一次没冲上去,停在了半坡,再想冲上去,非常困难,因为这个时候扭矩力很小,动力不足;

如果我们把m_TractionControl设置为0,那么汽车一开始的动力就是满的,也不会再增加减少,但是汽车启动可能会特别快。

如果我们把m_TractionControl设置为0.5,那么汽车一开始就拥有满动力一半的动力,然后另外一半动力会动态地变化。

所以只要把握住了上面几点,就根据自己需要设置该属性就好了

7.函数CalculateRevs(),重点属性Revs

        //计算转速
        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);
        }

其中有个比较奇怪的地方是计算RevsRangeMin,对档位因子使用了CurveFactor曲线函数,我的理解是,由于档位与转速的对应关系不是y = x这种简单的关系,所以不能直接使用gearNumFactor当做转速的比例系数,所以需要做一次转换,也就是说,档位或者速度与转速之间的对应关系是转速 = 1- (1-X)(1-X)。请看我根据这个函数做出来的曲线

至于为什么计算最大转速时,又没有使用这个函数,我就还没有搞清楚呢

8.函数CalculateGearFactor

        //计算档位因子
        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);
        }

从上面我们可以看出,至少档位速度之间的对应关系,大致是直线性,不是曲线的。不然上面的那种反插值函数,可能就得使用曲线变换函数了。

三.其他

其他的函数也有些有意思的函数,我就不在这里列举了。我上传了我注释版的CarController,希望能给有用的朋友一些启发。我的好多理解也可能不对,只是自圆其说,我日后再研究有结果会再更新的

我的上传注释版链接:

http://download.csdn.net/detail/narutojzm1/9516648

时间: 2024-08-04 03:39:15

Unity3d 官方资源Car的主控脚本CarController翻译与详解的相关文章

[Unity3D]Script 脚本所有编译器属性详解

Script属性是基于IDE的一系列编译器属性JS中用@script 属性方法()访问,c#中用[属性方法()]访问.一共就只有9种属性访问方式: AddComponentMenu    在Component菜单中添加新的菜单项 ContextMenu      在当前脚本的组件中添加右键菜单内容 ExecuteInEditMode    让当前脚本可以在运行模式中实时更新修改 HideInInspector        是变量在检测时不被显示,但是会被实例化? NonSerialized  

bash脚本的变量使用详解

变量的类型包括整数.字符串和数组,但在bash脚本编程中不需要指定类型就可以直接赋值,默认均为字符型,其参与运算会自动进行隐式类型转换. 变量的赋值方式为:name='value',其中"="两边一定不能有空格,如果变量值中包含有空格则需要使用引号引起来,需要注意的是强引用和弱引用的区分.此外变量的赋值也可以引用变量和命令,如果引用变量赋值需要使用双引号,如果引用命令赋值需要使用反向单引号.变量的引用需要使用"$"符号,如引用变量PATH:$PATH 或 ${PAT

WinSocket脚本编写实例与详解

我们知道Winsocket脚本与Web(HTML)脚本不一样,WEB(HTML)脚本主要采用HTML协议进行模拟,根据开发人员提供的接口编写脚本,而Winsocket协议主要根据服务器与客户端采用的内部通讯协议(内部通讯协议,我们在这里讲的是自定义的通讯协议)编写脚本,所以我们需要用到的工具有网络抓包工具Wireshark以及了解内部通讯协议的内容与作用,我们还可以通过服务器与客户端通讯的日志查看(linux下我们可以通过tail -f /var/log/tomcat...查看日志).好了,我们

Shell脚本学习之sed详解

在编写shell脚本的过程中,我们经常需要使用sed流编辑器和awk对文本文件进行处理. 一.什么是sed? sed 是一种在线编辑器,它一次处理一行内容.sed是非交互式的编辑器.它不会修改文件,除非使用shell重定向来保存结果.默认情况下,所有的输出行都被打印到屏幕上. 二.sed的处理过程 sed编辑器逐行处理文件(或输入),并将结果发送到屏幕.具体过程如下:首先sed把当前正在处理的行保存在一个临时缓存区中(也称为模式空间),然后处理临时缓冲区中的行,完成后把该行发送到屏幕上.sed每

Shell脚本应用之正则表达式详解

通过Shell脚本应用(一).Shell脚本应用(二).Shell脚本应用(三)这几篇博文,我们已经掌握了Shell脚本的编写规则和各种语句的具体应用,但是实际生产环境中,Shell脚本通常与正则表达式.文本处理工具结合使用.我们就来认识一下"正则表达式(RE)". 正则表达式概述 1.正则表达式的定义 正则表达式又称正规表达式.常规表达式.在代码中常简写为regex.regexp或RE.正则表达式是使用单个字符串来描述,匹配一系列符合某个句法规则的字符串.简单的说,正则表达式是一种匹

Shell脚本的执行方式详解

当Shell脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(加载顺序通常是/etc/profile.~/.bash_profile.~/.bashrc./etc/bashrc等),在加载了上述环境变量文件后,Shell就开始执行Shell脚本中的内容. Shell脚本是从上至下.从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在Shell脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句. 通常

小程序1月9号将上线,官方详解七大能力

今天,张小龙在2017微信公开课PRO版上宣布微信小程序将于2017年1月9日正式在客户端上线. 在下午的分论坛上,微信官方首次对小程序的能力进行了全面解读,并在现场对小程序作了案例展示. 微信官方带来小程序能力解读 微信团队现场详解了微信小程序的能力,包括线下扫码.对话分享.消息通知.小程序切换.历史列表.公众号关联和搜索查找等7大功能.   线下扫码:用户可以在小程序中使用扫一扫. 对话分享:用户可以分享小程序或其中的任何一个页面给好友或群聊. 消息通知:商户可以发送模板消息给接受过服务的用

Unity3D技术之资源数据库 (AssetDatabase)详解

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,我们致力于打造业内unity3d培训.学习第一品牌. 资源数据库 (AssetDatabase)资源数据库 (AssetDatabase) 是允许您访问工程中的资源的 API.此外,其提供方法供您查找和加载资源,还可创建.删除和修改资源.Unity 编辑器 (Editor) 在内部使用资源数据库 (AssetDatabase) 追踪资源文件,并维护

Unity3D游戏开发之使用disunity提取Unity3D游戏资源

各位朋友,大家好,我是秦元培.今天博主想和分享的是使用disunity提取Unity3D游戏素材.这个工具呢,博主在Unity3D游戏开发之反编译AssetBundle提取游戏资源这篇文章中其实已经提到过了,不过因为有些朋友对如何使用这个工具依然存在问题,所以博主决定特地写一篇文章来讲解如何使用disunity来提取Unity3D游戏中的素材. 准备工作 disunity:负责对Unity3D的数据文件进行解包 Unity3D:负责将导出的数据文件显示出来 Bleander或者3DsMax:负责