第三部分:Underthe Hood(内部脚本)

我们已经介绍了如何从一个三维模型,脚本和内置组件来组装一辆运行的车。我们也了解了可见的变量,以及它们可以怎样来调整汽车的行为。现在是时候我们来更深入的了解汽车引擎内部的精密机械 ——汽车的脚本。

  • 双击Car.js脚本,你的代码编译器会打开它。

咋一看这个脚本超过500行的代码和注释以及非常多的变量和函数有点吓人。当然不必沮丧,我们已经给每个函数都一个能说明它功能的名字了,这使得脚本的思路很清晰。因此,这部分代码没有罗列一大堆注释来重复解释这一切了——代码已经介绍了它自己。

我们建议的方式是找到最佳的切入点,然后顺着它的思路走。在这个例子里,Start(),Update() 和FixedUpdate()这些函数是切入点。每个主函数都会调用其它函数,所以当我们从Start()入手的时候,我们可以看到最先被调用的函数叫SetupWheelColliders()。在代码中找到这个函数,并研究它所做的事。然后再回到Start(),进入下一个函数,SetupCenterOfMass()。按照这样跟踪代码能粗略地得出在代码中到底发生了什么,使得汽车可以按照它展现在我们眼前的方式运行。

接下去我们会来看一下所以的函数。当然我们不会去解释每一行代码,但是我们会提供一个切入点,并且会浏览对于每一帧所发生的事的设置至关重要的东西。

哪些是最重要的?

多亏了像编译器、拖放工作流程以及所有的内置组件这样的工具,在使用Unity时,很多事情都变得很方便了。对于汽车的设置已经完成了一半——Unity负责导入模型,我们只需要通过点击鼠标添加组件来完成碰撞、渲染和物理系统的设置。

在我们的脚本里我们主要是来实现对这些组件的操作。当然,你会被很多用于设定汽车所发生的行为的计算所困扰,这是制作模拟游戏的时候不可避免的部分,你不得不为此去设计一些逻辑,比如当你想要做比基本的更多的时候所编写的脚本。不过这些计算只是为了得到正确的值去调整我们的组件。

如果你觉得这个代码还是太可怕,并且不知道从哪里开始入手,那么一个可行的方法是关注下面这些因素,然后考虑下大部分其他可能会影响或者促成这些因素的事情:

  • 刚体;
  • 车轮碰撞器(WheelCollider);
  • 我们所做的计算以及他们的顺序。

像这样去思考一下:

  • 通过给我们的汽车模型添加刚体属性我们有了可以符合物理规律的控制它的方法。我们通过计算推动它向前的力和使它减速的力来实现这一点。
  • 通过添加车轮碰撞器我们可以控制车轮与路面接触的部分。

Start()——设置

这是我们实现汽车所需的初始化事件的地方,Start()函数只执行一次,发生在脚本的最开始,Update ()函数之前。因此Start()函数通常是用来设置代码中的初始条件的。我们要访问的第一个函数是:

SetupWheelColliders()

我们有四个轮子附加到汽车上,并且我们在Inspector面板里将他们放到FrontWheels和RearWheels两个数组里。在这个函数里我们创建了实际的碰撞器,使得车轮可以与汽车下方的表面产生相互作用。我们在这个函数的开始部分访问了SetupWheelFrictionCurve()。

SetupWheelFrictionCurve()

在SetupWheelFrictionCurve()里我们只需要创建一个新的WheelFrictionCurve(车轮摩擦曲线)并且给它指定一个我们觉得适合值。WheelFrictionCurve(车轮摩擦曲线)是车轮碰撞器用来代表车轮的摩擦性能的。如果你想在使用Unity自带的车轮碰撞器时获得很好的效果,可以去参考下文档。

SetupWheel()

设置完曲线后,我们又回到了SetupWheelColliders(),准备创建实际的碰撞器和车轮对象,这是通过每个车轮调用SetupWheel()函数来实现的。如果你看一下脚本,你会发现它有两个参数:一个Transform和一个boolean,并且返回一个车轮对象。我们需要做的是将每一个车轮的transform组件指定给这个函数的transform参数,并且声明这个车轮是否是一个前轮。然后,该函数会创建并且返回一个车轮对象,放入我们的车轮数组,为脚本的其他部分提供我们所有的车轮。

for (var t : Transform infrontWheels) {

wheels[wheelCount]= SetupWheel(t, true);

wheelCount++;

}

在SetupWheel()里,我们所做的事是非常简单的:我们创建一个GameObject,并且赋予它与我们当前处理的车轮相同的位置和角度,然后我们再给这个GameObject添加一个车轮碰撞器组件。

我们根据在调整汽车的时候讨论的suspension(悬挂)变量(suspension的范围,弹簧和阻尼器)设置车轮碰撞器。

接着,我们要创建一个新的车轮对象,并且赋予它必要的属性:我们创建的碰撞器、WheelFrictionCurve(车轮摩擦曲线)、车轮图像(我们在设置汽车的时候拖到inspector面板上的DiscBrake对象)以及轮胎(这里应该是指轮胎纹理)图像(DiscBrake的子级)。

我们根据轮胎图像的尺寸来自适应设置车轮半径:

wheel.collider.radius=wheel.tireGraphic.renderer.bounds.size.y/ 2;

最后我们需要根据我们传到SetupWheel()里的true或者false值来确定刚刚创建的车轮是前轮还是后轮。如果是后轮,我们将它的driveWheel(驱动轮)值设为true,而如果是前轮,相反的,我们就将它的steerWheel(转向轮)设为true。后面的代码中,我们需要确保车至少有一个驱动车轮接触地面车才能开动,同样,必须至少有一个转向轮在地面上车才能转向。

此外,我们还要对前轮使些小伎俩,在车身与车轮之间单独创建一个gameobject,这就是Steer Column(转向轴),后面我们在转向的时候会用到来做旋转。然后,我们把创建的这个车轮作为返回值,在返回到SetupWheelColliders()时被放入车轮数组中,当我们处理完所有车轮后,我们会退出这个函数,并且返回Start()。

SetupCenterOfMass()

这是下一个我们要访问的函数,这是个非常简短的函数,将刚体的质心设置到CenterOfMass,之前我们创建的一个GameObject。如果质心没有设置,刚体会使用Unity计算得到的默认值。

然后我们使用一个小型的实用函数转化我们在Inspector面板输入的最高时速:

topSpeed =Convert_Miles_Per_Hour_To_Meters_Per_Second(topSpeed);

这个函数只是给最高时速乘了0.44704,将它转化为米每秒为单位。这样的设置可以允许我们在Inspector面板中以英里每小时为单位输入我们想要的速度,而在作物理计算的时候我们使用米每秒为单位。我们还有一个简短的函数来做逆运算,这在某些时候也是非常有用的,比如说当你要以英里每小时为单位输出速度值时。

SetupGears()

在这个函数里档位是通过分配给每档的最高时速来自动设置并计算出使汽车加速到每一档的最高时速需要的牵引力。这个力是通过friction(摩擦力)和拖拽得到的全局变量得到的,也就是说,这个计算是基于一个在update函数里的线性Z轴版本的摩擦力计算。这个力被乘以了一个因子来确保汽车将加速到一个高于分配给每档的阈值的速度。

SetUpSkidmarks()

这个函数用于寻找场景中名为Skidmark的gameobject,并且将它和它用于制作烟雾的ParticleEmitter(例子发射器)分别存储到变量中。Skidmark绑定的脚本在这个教程中我们没有涉及,但是你完全可以打开它并自己去琢磨。在Start()函数的末尾,我们将dragMultiplier(定义的一个Vector3类型的变量)的x值赋予了一个变量:

initialDragMultiplierX =dragMultiplier.x;

存储这个变量是因为当我们使用刹车的时候需要去修改dragMultiplier的x值,而当刹车使用结束的时候需要将它重置回初始值。这些就是Start()函数设置的所以东西,现在我们将要进入Update函数,这个脚本的主循环,每一帧都会执行一次。

Update()

GetInput()

在每一帧中我们所要做的第一件事是通过GetInput()函数来读取用户的输入(就是用户的按键事件)。头两行读取的是垂直和水平轴,并储存在我们的油门(throttle )和转向(steer)变量中:

throttle =Input.GetAxis(“Vertical”);

steer = Input.GetAxis(“Horizontal”);

垂直和水平轴可以在可以通过Unity中的Input Manager(输入管理器,Edit -> Project Settings->Input)来设置。默认情况下,垂直轴设置的是“W”键和“向上”方向键作为正方向,“S”键和“向下”方向键作为负方向,这里我们读取它的值是为了用于后面的油门的力。水平轴将“A”键和“向左”方向键作为一个方向,“D”和“向右”方向键作为另一个方向,用于转向。

CheckHandbrake()

读取完控制汽车的用户输入后,我们将调用CheckHandbrake()函数,这是用来检测空格键是否被按下,并且实现相应的一些逻辑的一个特殊的函数:

当我们第一次按下空格键的时候,handbrake变量的值设为true,启动一个(handbrake timer)刹车计时器,并且改变dragMultiplier.x的值(使汽车出现摇摆,类似手制动)。只要我们一直按着空格键,就不会再有什么变化了,因为handbrake变量已经变为true了。

当空格键的按下状态没有被注册的时候,else模型的代码就会被执行,但这也需要在handbrake变量为true的情况下。这再一次表明了,这个代码只有在用户第一次按下空格键的时候执行,协程StopHandbraking()就会被执行:

StartCoroutine(StopHandbraking(Mathf.Min(5,Time.time - handbrakeTime)));

StopHandbraking()

StopHandbraking()需要一个输入变量来指定dragMultiplier.x回它的初始状态所需要的秒数。这个值是5与在我们开始handbraking时启动的刹车计时器的值之间的较小者。然后,在指定的时间里将dragMultiplier.x的值从当前值变回到我们在Start()函数末尾保存的初始值,使汽车正常行驶。

Check_If_Car_Is_Flipped()

再次回到Update我们现在要调用的是Check_If_Car_Is_Flipped()来执行这个函数内部用来检查汽车旋转角度的“Turtle-check”。这在汽车翻车或者旋转到临界角度的时候非常有用,比如说,冲出跑道了或者做了一些疯狂的特技的时候,我们肯定想要排除汽车停在一个位置无法再行驶的可能性。因此,我们需要检测旋转角度是否到达了一个导致汽车无法继续行驶的角度,如果是,我们就给resetTimer变量加上一个上一帧到现在的时间值。如果这个值最终超过了我们已经设置的resetTime(默认是5秒),我们就去调用FlipCar()。而如果汽车的旋转角度不是一个糟糕的值,相反的,我们就需要将计时器(resetTimer)清零。

FlipCar()

在FlipCar()中,我们让汽车回到正常位置,并将它的速度设置为0,这样我们就可以从这个断点又一次开始启动汽车。

UpdateWheelGraphics()

这是Update()调用的最长最复杂的函数,幸运的是,中间有一大部分都是处理如何放置skidmarks的,而这一部分我们将不去涉及。对于车轮来说最重要的部分就是通过这个函数来更新它们的位置和旋转角度。

对于每个车轮,我们都是从检测它是否在地面上开始的,如果是的话,我们就将车轮图像的位置设置到当前与地面接触的位置前方相当于车轮半径的距离的位置上。这会使车轮的中心相对汽车底盘移动到正确的位置。

w.wheelGraphic.localPosition=wheel.transform.up*(wheelRadius+wheel.transform.InverseTransformPoint(wh.point).y);

车轮定位完后,我们从地面碰撞点得到刚体的速度,把它转化到车轮的自身坐标中,并存储在车轮对象中:

w.wheelVelo =rigidbody.GetPointVelocity(wh.point);

w.groundSpeed =w.wheelGraphic.InverseTransformDirection(w.wheelVelo);

如果当前正在处理的车轮没有与地面接触,我们就根据它的车轮父对象的transform和悬挂范围去设置它的位置。

接着,我们给车轮加上旋转。如果是转向轮,我们就从设置可见转向的旋转开始,这是通过旋转我们之前创建的Steer Column对象实现的。我们通过一个因子来旋转它,该因子由我们转动车轮的大小(基于用户输入得到的转向值)和我们已经设置好的最大旋转角度的乘积得到。因为Steer Column是车轮图像的一个父对象,所以车轮对象会随着Steer Column的转动而发生转向。对于所有的车轮我们都让它按可见速度进行旋转,通过根据速度和车轮半径绕着车轮自身的向前轴旋转的方式实现。

w.tireGraphic.Rotate( Vector3.right* (w.groundSpeed.z / wheelRadius) *Time.deltaTime*Mathf.Rad2Deg);

UpdateGear()

我们从Update()里最后调用的是UpdateGear(),这是一个简单的函数,通过比较当前速度与我们在Start()调用的SetupGears()中设定的每一档的值来计算汽车现在正运行在哪一档。

这就是实际上在Update()中每一帧都发生的一切——并没有那么复杂,对吧?我们最后需要来看的这部分是剩下的主循环部分,也就是物理计算,发生在FixedUpdate()中。

FixedUpdate() – 你所有的物理学计算都由我完成

处理物理学计算时,严格控制运算和操作是非常关键的,以确保结果是流畅的、顺利的。FixedUpdate()就是为了这个目的而产生的,它可以确保在固定的时间间隔执行,描述Update程序的文档会告诉你这样一段关于FixedUpdate()的话:“如果帧频较低的时候,它可以在每帧都被多次调用;而如果帧频较高的时候,它也不会在帧与帧之间的间隔被调用。所以的物理计算和更新都瞬间发生在FixedUpdate()之前。”

我们有很多在FixedUpdate()内执行的函数,它们都是用于计算和应用力到汽车的。

UpdateDrag()

第一个我们访问的是UpdateDrag(),阻力是汽车行驶的时候影响它的空气阻力,也就是说这是一个与汽车行驶方向相反的影响汽车上的力,会使汽车减速。

我们是在汽车速度平方的基础上设置阻力的:

Vector3( -relativeVelocity.x * Mathf.Abs(relativeVelocity.x),

-relativeVelocity.y * Mathf.Abs(relativeVelocity.y),

-relativeVelocity.z * Mathf.Abs(relativeVelocity.z) );

这意味着随着汽车速度的增大阻力会增大更快,计算阻力的时候要让速度平方是根据物理学上计算实际阻力的公式得来的。

接着,relativeDrag(上面设置的那个阻力的变量名)与我们之前看到的dragMultiplier一起缩放(两个Vector3类型的值,分别将它们的x与x,y与y,z与z相乘得到一个新的Vector3),这是考虑到了汽车的轮廓从正面、侧面和顶部看差别是很大的。

如果我们正在使用刹车,那么我们会额外在正面和侧面的阻力值基础上再加上一些力,根据汽车行驶的速度和由它的速度方向决定的车身朝向得到的。注意看,我们是在速度和汽车的向前方向之间使用点乘符号来计算汽车正面的额外阻力的。这个等式我们可以得出,汽车在向正前方(z方向)行驶的(刹车)时候的阻力更大,而偏向侧面的(侧滑)时候小一些。对于x方向的阻力也是一样的:汽车侧滑的程度越大,x方向的阻力就越大,这样就能让汽车减速,而不是一直让它滑动。

如果不是刹车状态,我们只需要更新它的x值就可以了:

drag.x *=topSpeed / relativeVelocity.magnitude;

这里又一次做了一个简单的事情让汽车行驶更完美——增大侧面的阻力就可以减缓转向时汽车在路面的侧滑。

在这个函数的末尾,我们将力添加到刚体上:

rigidbody.AddForce(transform.TransformDirection(drag)* rigidbody.mass * Time.deltaTime);

因为阻力是与速度方向相反的,所以我们就将力转换到刚体的transform方向(即转换到刚体的自身坐标)并且实现让汽车减速的效果。

UpdateFriction()

这个函数负责处理车轮和它所在地面之间的摩擦力。我们只要使用最初设定好的WheelFrictionCurve(车轮摩擦曲线),这方面就会非常容易实现了。车轮摩擦曲线会提供一个力,根据我们输入了的一个处理轮胎打滑的值得到的。这个力被拆分成两个方向:正前方的摩擦力(负责加速和刹车)和侧面的摩擦力(保持车身方向)。当我们给车轮摩擦曲线赋值的时候它会负责更新车轮与它所在地面之间的摩擦力:

w.collider.sidewaysFriction = wfc;

w.collider.forwardFriction = wfc;

在此之前我们还有一件事要做,就是根据汽车的侧向速度去改变侧向摩擦力,这样做是为了避免汽车在弯道转向的时候开始在地面上发生侧滑。

CalculateEnginePower()

在后面我们会用到引擎的牵引力,它的计算是相对简单的,但是需要注意一些问题:

  • 如果我们松开油门(停止制动,松开向上键),引擎的牵引力就会慢慢减小,汽车会减速。
  • 如果我们正在与汽车的当前行驶方向(我们会在HaveSameSign()函数中检测)上制动(就是踩油门啦),我们就计算出一个值加给引擎牵引力。接下去的事情有些不可思议:我们要计算出一个力的常量(normPower),用当前引擎牵引力的值去除以引擎牵引力的最大值(产生一个0到1直接的结果)然后再乘以2.这个结果会在0(当我们正在以越来越小的速度行驶的时候)到2(当我们以最大牵引力行驶的时候)之间。
  • 然后,我们调用一个非常有用的函数EvaluateNormPower(),这个函数通过传递进去的值返回一个值,在normPower是0到1的时候会返回0到10直接的值,而在normPower是1到2的时候会返回0到1直接的值。困惑吗?这个值会在给引擎添加力的函数里用到:currentEnginePower += Time.deltaTime * 200 * EvaluateNormPower(normPower);这样的结果是,在我们按着加速键(向上键)的时候,需要更大的力但是汽车的加速确实越来越慢的,最终,当汽车达到它的最大速度的时候,就不会在有更大的力加到引擎上了,这会使得汽车保持在一个稳定的速度。
  • 相反的,如果我们在反方向上施加制动,也就相当于刹车。在这种情况下,我们也会慢慢减小引擎牵引力,但会比松开油门的情况下减得稍微快一点。

最后,这个计算出来的引擎牵引力值需要被限制在当前档的牵引力值与前一档的牵引力值之间,以避免计算出一个太大或者太小的突变值的可能性。

CalculateState()

我们现在要调用这个简单函数,因为后面的函数中我们都需要知道汽车的驱动轮和转向轮是否在地面上。它所要做的事非常简单:

  • 我们将canDrive和canSteer的值设为默认的false;
  • 然后我们检查车轮数组中的每个车轮,看它们是否都与地面接触:

if(w.collider.isGrounded)

如果是在地面上,我们就判断它是哪一类车轮。如果是驱动轮,就将canDrive置为true;如果是转向轮,就将canSteer置为true。

这个函数完成它的工作后,我们要添加的是如果至少有一个驱动轮(就是我们的后轮)与地面接触,我们就能行驶。如果至少有一个转向轮(就是我们的前轮)接触地面,我们就能转向。

下面,我们要来看最后两个函数,它们是实际将我们的计算结果运用到汽车刚体上的函数。这里我们会讲到更多的细节,来让你更好地了解让汽车最终行驶和转向的逻辑和计算。第一个函数是:

ApplyThrottle()

这个函数只在当CalculateState()函数将canDrive置为true时候(也就是至少有一个驱动轮在地面上的时候)会执行程序。

      如果可以行驶(就是canDrive为true时),就从比较我们读取的用户输入的throttle值(也就是向上键为正,向下键为负)与relativeVelocity.z的值,也就是汽车正前方向上的速度。

如果他们有同样的符号——由HaveSameSign()这个功能函数确定——这意味着我们正在汽车行驶方向上施加制动,在这种情况下我们需要添加一个油门力(向前的制动力)到刚体上:

throttleForce= Mathf.Sign(throttle) * currentEnginePower * rigidbody.mass;

如果throttle是反向的(也就是用户按下了向后键),符号就是-1,这样我们就会计算出一个负的油门力添加到有一个向后速度的汽车上。因此会更快向后制动(也就是向后加速)。相反的情形是用户按下了加速键(向上键),那么我们就会给正在向前行驶的汽车施加一个向前的油门力,让它向前加速。

而另一方面,如果relativeVelocity.z和throttle的符号不同,那么必定是我们在汽车当前行驶方向的反方向上施加了一个throttle,换句话说,我们正在刹车或者减速,我们通过设置brakeForce的值来实现,brakeForce是由汽车的质量和引擎第一档提供的牵引力决定的:

brakeForce =Mathf.Sign(throttle) * engineForceValues[0] * rigidbody.mass;

这里我们还是使用throttle的符号,因为我们知道throttle在这种情况下与速度有相反的符号,这样就会计算出一个与我们行驶方向相反的力。

当确定汽车是加速还是减速后,我们就将计算出的力添加到刚体的向前方向上:

rigidbody.AddForce(transform.forward* Time.deltaTime * (throttleForce + brakeForce));

ApplySteering()

转向同制动一样重要,除非你正尝试着在直道上狂飙来创造一个速度世界纪录,所以我们将转向运用上去,让它变得更真实些。如果没有驱动轮在地面上我们就不会添加任何制动力,同样的,在这个函数中如果没有转向轮接触地面,我们就不会提供任何转向。

在这个函数的开头部分,我们根据用户输入(就是用户的按键事件)计算了一个名叫turnRadius的变量,这个计算式会在我们转向任何一边的时候增加turnRadius的值。我们还要通过访问EvaluateSpeedToTurn()函数来计算minMaxTurn值。

EvaluateSpeedToTurn()

这个函数将根据汽车行驶的速度返回一个转向值,就像我们在调整汽车部分所描述的一样。当我们的行驶速度越快的时候,这个值就越小,转向就越困难。

回到ApplySteering(),我们下面要计算的turnSpeed值跟turnRadius和汽车向前的行驶速度有直接关系,半径越大,每一帧转向的角度就越小,因为我们转向的圆就越大。

接着我们来旋转汽车:

transform.RotateAround(transform.position + transform.right * turnRadius *steer,transform.up,turnSpeed * Mathf.Rad2Deg * Time.deltaTime * steer );

RotateAround()函数会绕着一个点和一个轴向,按照所需要的旋转角度来选择一个transform。

  • 当没有发生转向的时候,我们转向所围绕的点是在汽车的中心。而当我们按下转向键的时候,这个点就会离开汽车,跑到它所转向的一边。记住,steer变量是从水平方向的用户输入(向左向右键)取得的,当我们左转的时候是一个负值,右转的时候则是一个正值。我们转向转过的越多,turnRadius和steer就会越大。当我们给他们乘以transform.right向量时,我们得到一个在汽车中心的基础上在它的转向方向发生移动的点,就像下面图片表现的一样:

  • 我们的旋转轴是y轴(向上轴),这意味着我们是在x-z平面上做旋转——我们绕着图中的红线旋转汽车。
  • 旋转角度是通过我们刚刚计算得到的turnSpeed乘以steer来得到左/右方向。

现在我们继续往下看:

if(initialDragMultiplierX > dragMultiplier.x)

在刹车的时候我们就认为这个条件为true,那么,我们就检测刹车的时候是否又发生转向。

如果我们不是在转向,我们就通过查看刚体的angularVelocity.y值来检测目前汽车是否在转弯的过程中。

如果这个值是0或者很小,我们就不转动或者只转很小,然后我们在旋转方向上使用一个随机值。这会使向前行驶的汽车在刹车的时候模拟车身摇晃。

如果这个值不是很小,那么我们就将实际的angularVelocity.y值使用到旋转方向上。如果实际上我们正在转向,那么当我们左转的时候,旋转方向是-1,右转的时候选择方向是1(因为默认情况下,旋转方向是根据steer的符号设置的)。

当刹车的同时旋转方向也发生变化时,我们也要将这个旋转添加到车上,但是这次是绕着另一个点:

frontWheels[0].localPosition +frontWheels[1].localPosition) * 0.5

这个点是在两个前轮位置之间,当汽车绕着它旋转的时候,会产生这样的结果,汽车的后部会向旋转的一侧移动,而同时前部保持原位——当你在一个很高的速度上转弯的并且使用刹车的时候会使得汽车以一种很酷的方式滑动。

现在整个过程都完成了,Update()和LateUpdate()函数都会从下一个循环的最顶部开始重复运行一遍。只是下一次的输入值会由于玩家输入和道路状况而不同,我们也会计算出不同的力,来创造一个真实的驾车感。

到这里就结束了,希望你会喜欢这个将一辆汽车组装好,用它的变量来驱动,并且和我们一起查看代码的这个过程。

如果你想了解更多,我们还为你准备了最后一部分内容,就是一个RealPhysics Model(真实的物理模型),给你的视觉/体验带来乐趣,但是这次你得靠你自己了。

RealPhysics Model

在project面板在最下面你会找到一个叫〜AlternatePhysicsMode的文件夹,这个文件夹包含了一些脚本和样例prefab(预物体)来在Unity中模拟汽车物理系统。这里展示的模拟没有使用Unity的wheel collider(车轮碰撞器),而是用它自己脚本里基于Physics.Raycast的wheel colliders来代替。

可能你根本不需要去了解物理模型的内部工作原理,如果你只是想让它动起来,那么最快速有效的方法就是使用样例prefab,并且去调整变量值,然后在看看会发生什么变化。如果你打开脚本,你会看到所以的参数都有注释,试着去一点点改变变量的值,体会下所发生的变化。

包含的prefab

这个文件夹带有5个汽车prefab以及一个skidmarks prefab。试一下,拖一个汽车prefab和skidmarks prefab到场景中(当然场景中可能已经存在skidmarks prefab了),现在你应该可以使用方向键来在场景中控制汽车了。

这里有四个包含不同特性的逼真的汽车模型,可以让你体验不同的物理参数设置,另外,还有一个更符合街机风格设置的运动汽车(超乎想象的高抓地力)。

这里用到的所有汽车模型都是从互联网上下载的,仅用于演示目的的,在实际的游戏中使用时不一定适合。

这些脚本都是符合现实物理规律的,除了用来通过输入的数字使fishtailing(摆尾)汽车更加可控的TractionHelper脚本,它有点儿像现实中的ESP系统(电子稳定装置ElectronicStablity Program,简称ESP)所做的。

包含的脚本

AerodynamicResistance.cs:这个脚本需要绑定到汽车上,用于计算汽车的空气动力学摩擦力。

AntiRollBar.cs:可选择添加到每一个轴上来更好地模拟anti-roll-bars(防侧倾杆)。

CarController.cs:这个脚本是处理汽车相关的输入的,每一辆汽车都需要这个脚本。如果想改变控制汽车的方式,或者运用AI(人工智能)或者网络来控制汽车,你可以自己编辑脚本。

Drivetrain.cs: 汽车的引擎和驱动装置。这是你设置变速箱和引擎规格的地方,每一辆汽车都需要。

Skidmarks.cs: 全局的skidmarks管理,添加一个skidmarks prefab到场景中,这个类是用于全部汽车skidmarks的渲染和管理的。

SoundController.cs:一个简单的类,用于播放引擎和汽车打滑的音效,每一辆汽车都应该添加一个。

TractionHelper.cs: 选择这个类的一个实例添加到每一辆车使得汽车更加稳定。

Wheel.cs: 这里模拟轮胎的牵引力和车轮悬架模型,用来替代Unity内置的车轮碰撞器。

Wing.cs: 如果你想要你的汽车模拟抬升或者下压的空气动力学就添加一个或者多个。

Unity赛车教程第三部分相关推荐

  1. Unity渲染教程的GAD中文翻译版本地址

    Unity 渲染教程(一):矩阵 :http://gad.qq.com/program/translateview/7181958 Unity 渲染教程(二):着色器基础 :http://gad.qq ...

  2. Unity渲染教程的GAD中文翻译版本地址。

    2019独角兽企业重金招聘Python工程师标准>>> Unity 渲染教程(一):矩阵 :http://gad.qq.com/program/translateview/71819 ...

  3. C#开发Unity游戏教程之游戏对象的行为逻辑方法

    C#开发Unity游戏教程之游戏对象的行为逻辑方法 游戏对象的行为逻辑--方法 方法(method),读者在第1章新建脚本时就见过了,而且在第2章对脚本做整体上的介绍时也介绍过,那么上一章呢,尽管主要 ...

  4. [Unity C#教程] 游戏对象和脚本

    文章转载自:https://www.cnblogs.com/UnityYork/p/7704803.html [Unity C#教程] 游戏对象和脚本 博主最近在学习Unity,发现一个英文教程很好. ...

  5. unity官方教程-TANKS(一)

    unity官方教程TANKS,难度系数中阶. 跟着官方教程学习Unity,通过本教程你可以学会使用Unity开发游戏的基本流程. 一.环境 Unity 版本 > 5.2 Asset Store ...

  6. Unity官方教程Ruby大冒险的自学笔记

    Unity官方教程Ruby大冒险的自学笔记 一. //正确例子: void Update(){//获取运动矢量moveX = Input.GetAxisRaw("Horizontal&quo ...

  7. Unity 使用教程 之 Unity3D常用的知识点归纳

    Unity 使用教程 之 Unity3D常用的知识点归纳 注意:数据结构和算法很重要!图形学也很重要!大的游戏公司很看重个人基础,综合能力小公司看你实际工作能力,看你的Demo. 1.什么是渲染管道? ...

  8. [Unity workflows] Unity Addressables 教程:学习基础知识

    英文原文:https://thegamedev.guru/unity-addressables/tutorial-learn-the-basics/ 欢迎来到这个 Unity Addressables ...

  9. [unity learning] RPG Leaning(三)

    [unity learning] RPG Leaning(三) 写这个文章的目的就是为了初学unity,然后更好的掌握unity中的内容[主要是代码] 学习unity的途径是 Sebastian La ...

最新文章

  1. 台北到淡水版Firefox玩网页游戏黑屏
  2. angularJs 跨控制器与跨页面传值
  3. 「SLAM」十四讲:第1讲 预备知识
  4. 【STM32】GPIO之按键
  5. 查看xxx.a库架构的命令
  6. JS获取屏幕浏览器网页高度和宽度属性
  7. ug建模文本怎么竖着_入门到成为UG编程高手,这些步骤你不得不了解
  8. 微软发布紧急更新,修复了多个 Windows Server 身份验证问题
  9. paip.提升安全性---WEB程序安全检测与防范
  10. Win10安装乌班图18双系统
  11. Class文件是个啥?
  12. H3C 交换机配置命令
  13. SQLyog数据库:主键外键代码添加
  14. 基于R语言对哺乳动物睡眠时间sleep数据集的分析
  15. WiFi、蓝牙、NFC哪家强?短距离无线通信技术对比分析
  16. MATLAB学习七(二):数组比较sortrows
  17. 把大脑从衰老的身体里分离出来?“长寿科技”让人类活200年不再遥远
  18. 3-6月计算机类学术会议合集
  19. C语言 有符号类型转换为无符号类型
  20. Eclipse配置Tomcat环境

热门文章

  1. 一个简单的视频播放器
  2. 浏览器自动注入js脚本
  3. unity粒子系统stop()和clear()
  4. SQL SERVER 2008 R2 错误代码 17000 - 17999
  5. ESP8266 初次使用
  6. 让预训练语言模型读懂数字:超对称技术联合复旦知识工场等发布10亿参数BigBang Transformer[乾元]金融大规模预训练语言模型
  7. 图片压缩算法,保证图片不失真
  8. div+css静态网页设计 web网页设计实例作业 ——茶叶文化-适应响应(12页) 学生HTML个人网页作业作品下载
  9. 江西理工大学南昌校区排名赛 F: 单身狗的骑马游戏
  10. 网络安全--红队资源大合集