![Unity3D网络游戏实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/295/847295/b_847295.jpg)
2.5 车辆行驶
到现在为止,已编写了两套坦克控制模式,然而,此时的坦克还不能紧贴起伏的地面,只要地面不平坦,坦克就会穿插在山体里或悬浮在半空中(如图2-41所示);其次坦克转弯不太自然,坦克的旋转半径一般不为0,不会原地旋转。 Unity3D内置的一种碰撞器WheelCollider,可实现对车辆的操控。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0074_0002.jpg?sign=1739309424-Qx6BjI9ZBgBSHsdoqIVJMjUAWXER1nW1-0-910ff613af9c5c5e3c8a549fef3ebba5)
图2-41 坦克穿插在山体里
2.5.1 Unity3D的物理系统
在实现车辆控制之前,有必要先了解Unity3D的物理系统。物理系统中最常用的组件是Rigidbody和Collider。
Rigidbody(刚体)使物体能在物理规律下运动,它是物体系统的基础组件。可以从力和碰撞两方面来理解物理系统。
1.力(重力)
例如让一个带有Collider和Rigidbody的物体从高空落下,因为受到力的作用,在物体碰到地面后会倒下。读者可以新建一个工程试一试物理系统,在场景中添加一个圆柱体和立方体(它们默认附带有Collider组件),然后给圆柱体添加Rigidbody组件(Component→PhysicsRigidbody),再让圆柱体倾斜一定的角度(为了更明显地看出效果),如图2-42所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0075_0001.jpg?sign=1739309424-bQUS01iAyaYGT6zObBwWEirs1XiKato9-0-b768dac467bacbd452eec8a582d45569)
图2-42 将带有Rigidbody组件和一定初始角度的圆柱体放到场景中
运行游戏,可以看到圆柱体受到重力的影响掉到地面上,然后倒下,如图2-43所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0076_0001.jpg?sign=1739309424-4bjPWmwdEEV6acceLXoE1VqS2SxtJpVz-0-52a6ecb10322c6fb20085b07cf423760)
图2-43 圆柱体受到重力影响掉到地面上,然后倒下
2.力(附加力)
通过程序给物体施加力,即可改变物体的运动轨迹。例如编写如下的代码,把它附加到上述例子的圆柱体上。如果按下空格键,则是通过Rigidbody的AddForce方法给物体施加一个力(这里施加的是一个方向向上,大小为50N的力)。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0076_0002.jpg?sign=1739309424-HJiIdddVb6dbk9hybAwNqgLoDQAGC0WZ-0-242e689572f398299fafc137933a1e1b)
运行游戏后按下空格键,由于物体受到附加力和重力的共同影响(如图2-44所示),如果附加力大于重力(长按空格键),则物体将会向上飞起。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0077_0001.jpg?sign=1739309424-RPfGaaQErgjPNJAPgfgU4PxGXTD5BNrX-0-2a2cca780bddf956710bfc1e5c22caa8)
图2-44 物体受到附加力和重力的共同影响
3.碰撞
当“带有Rigidbody和Collider的游戏对象”碰撞到场景中“带有Collider的游戏对象”时,OnCollisionEnter()方法将被调用,后面会利用这一特性判断炮弹是否击中。相关的方法有6个,具体见表2-3。
表2-3 碰撞器的相关方法
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0077_0002.jpg?sign=1739309424-yZrRGn1QmlFtwrmdBUvrgiE9L00Ga3LI-0-9136e117ba1c3c0a574d6036dafc3cb5)
例如,在上面圆柱体掉落例子的TestForce类中,添加如下的语句。
//当碰撞到物体 void OnCollisionEnter(Collision collisionInfo) { Debug.Log("碰撞到 " + collisionInfo.gameObject.name); }
运行游戏,在圆柱体碰到地面的时候,OnCollisionEnter方法会被调用,如图2-45所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0078_0001.jpg?sign=1739309424-d5IdtKdulYElgYpzdlRZiHAaZeQtfa2C-0-8208ac941deb0a39081034de343a4198)
图2-45 碰撞检测
坦克行驶符合物理规律,可以使用Unity3D内置的车轮碰撞器来实现。
2.5.2 车轮碰撞器
WheelCollider(车轮碰撞器)是一种特殊的地面车辆碰撞器,它具有内置的碰撞检测、车轮物理引擎和一个基于滑移的轮胎摩擦模型。WheelCollider是专门为有轮子的车辆所做的设计,也适用于坦克。
在添加轮子碰撞器之前,先给坦克车身添加碰撞器。由于Rigidbody是物理系统的基础组件,因此需要添加该组件后碰撞器才会生效。先给坦克模型添加Rigidbody组件,如图2-46所示。由于坦克一般都比较重,因此需要在Rigidbody中调整坦克的质量(Mass属性,这里调整为300)。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0079_0001.jpg?sign=1739309424-l1oWSTh5aRYHxHjNw9sj49StuOLUhahC-0-e5b419cfc6a88109ee91acefe58d2260)
图2-46 给坦克模型添加Rigidbody组件
暂且把坦克当成一辆拥有四个轮子的汽车。在坦克模型下建立名为PhysicalBody的空物体(Empty Object),用于存放坦克的碰撞器组件。再在PhysicalBody里添加wheelL1、wheelR1、wheelL2和wheelR2这4个空物体,4个wheel将代表车辆的4个轮子。在PhysicalBody里添加两个名为collider的空物体,用于给车身添加碰撞器(BoxCollider)。此时坦克模型的层次结构如图2-47所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0079_0003.jpg?sign=1739309424-A8zlTIRFOQ5iGw1TfciisFphOcR5cQSD-0-dbb28a5f4bebc373e2c58e4d105f9ded)
图2-47 Tank的层次结构
给两个collider添加BoxCollider组件(Component→Physics→Box Collider),然后点击Box Collider属性面板的调整碰撞体的大小和位置(点击后,碰撞体会出现控制点,拖动它们即可调整),调整后的碰撞体如图2-48所示。添加车身碰撞体可以让程序检测坦克是否碰撞了障碍物,或者是否被子弹打中(将在第3章中实现)。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0079_0004.jpg?sign=1739309424-FuHjCIAprxuWlWgiLfJjRGmS4QO6M5mS-0-9d5de2a1420a78d2221427d2a66413ac)
图2-48 车身碰撞体
接下来给坦克添加车轮碰撞体,如图2-49所示。给4个wheel分别添加Wheel Collider(Component→ Physics→WheelCollider),调整它们的位置和大小(通过Wheel-Collider组件的Radius属性调整大小),使wheelL1代表前方左轮,wheelR1代表前方右轮,另外两个轮子代表后方的轮子。轮子位置和角度的细微差别对物理性能的影响很大,切记,看着Transform的数值进行调整,使它们对称。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0080_0001.jpg?sign=1739309424-nz3qX21FFgc6qeobUCJJhiHRe4QyKCDi-0-a5768141dcd7f3fe2740a2678f848d23)
图2-49 坦克的碰撞体
WheelCollider是用Unity3D制作汽车类型游戏的关键所在,它不仅可以模拟轮子的碰撞过程,还模拟了汽车的悬挂系统、引擎系统、轮胎摩擦等汽车的关键物理特性。WheelCollider的一些属性如图2-50和表2-4所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0080_0002.jpg?sign=1739309424-XMNe3mkTtXjOQaU6Jp93J3RScSfRq2Uh-0-05eca139ce36a90b5aca8c5bfa8c4560)
图2-50 WheelCollider的属性面板
表2-4 WheelCollider的属性及说明
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0081_0001.jpg?sign=1739309424-5W6cRpeZ4tG9PMVjcOsEy23gY9G5An2x-0-c7dc9a1700388f21bd51edeb275557df)
车轮是由motorTorque、brakeTorque和steerAngle属性控制的,它们的含义见表2-5。
表2-5 车轮控制属性
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0081_0002.jpg?sign=1739309424-c1Z0XCpueo0Do2pO0EVyVfTWJnV5kwfq-0-ba428aa9ab0a2b104bebbcbba22a15f7)
一般来说,重心、悬挂系统和轮胎(摩擦力属性)对汽车的性能有着很大的影响。
❑重心:大家都知道重心低的车辆不容易翻倒,所有被应用到汽车刚体上的力都会作用到质心,默认情况下,Unity3D会将所有附加到刚体上的碰撞器(不管是该游戏对象还是它的子对象)的重心设为刚体重心。由于车辆的重心通常不是车的中心位置,若要得到更真实的效果,可以通过代码设置Rigidbody.centerOfMass来调整重心位置。
❑悬挂系统:另一个可以影响汽车行为的因素是悬挂系统(如图2-51所示)。汽车的悬挂系统可以增强轮胎和路面的摩擦,设想一辆没有安装悬挂系统的汽车行驶在颠簸的路面时,车轮肯定会时不时被抬起,轮胎的摩擦便无从谈起。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0081_0003.jpg?sign=1739309424-48pmh8vooSD5FoGxfa0yoH78YBQmgl2l-0-1fc21a02baf33a4f3650bfe96c678b74)
图2-51 汽车的悬挂系统
与悬挂系统的相关的参数如表2-6所示。
表2-6 与悬挂系统相关的参数及说明
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0082_0001.jpg?sign=1739309424-TeGuOksazGaDNVYvZdXwiXXh0nkgyo4H-0-d766e67c884de7922c7c77762055d9ab)
❑轮胎摩擦力:Forward Friction是前后方向的摩擦力属性,影响motorTorque和brakeTorque的效果。Sideways Friction是左右方向的摩擦力属性,影响steerAngle的效果。车轮摩擦力受滑动摩擦力和滚动摩擦力的综合影响。图2-52是车轮摩擦力曲线图,假设要拉动车辆,一开始车轮静止,需要较大的力去克服惯性(从原点到Extremum),接着只需要一个与摩擦力相同的拉力便可以使车辆做匀速运动(Asymptote),这样就是图2-52所展示的曲线。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0082_0002.jpg?sign=1739309424-GmAUDoUIn61zGEf5kJRX3TwVO0MJgm20-0-85d022c46728b9f7f6affdf2019d9578)
图2-52 车轮摩擦力曲线图
Forward Friction和Sideways Friction中每一项的5个参数的含义见表2-7。
表2-7 摩擦力相关的参数及说明
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0082_0003.jpg?sign=1739309424-ODOv735Rzw6TwRUITuG1a00SPAP4PkFo-0-d12f9c425f5641ff54c865961a8677c4)
给坦克添加碰撞体、了解车轮碰撞器的属性后,接下来便要控制坦克的行进了。
2.5.3 控制车辆
汽车的前轮和后轮分别悬挂在两条轴上,每条轴上两个轮子的步调是一致的。新建名为AxleInfo.cs的文件,添加代表车轴信息的AxleInfo类,代码如下所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0082_0004.jpg?sign=1739309424-JHoNAyyERaQoIvoTr4D8EID90ZTdF4QM-0-1ece68dfcbd2dc588d1d08639c3e1d36)
在上述代码中,leftWheel和rightWheel代表在某一条轴上的两个车轮碰撞器,例如左前轮和右前轮就是同一轴上的leftWheel和rightWheel。Motor指明是否将发动机的马力传送给轴上的轮子,就像汽车有前驱、后驱和四驱一样,对于前驱的汽车,马力传送给前轮而不传送给后轮。Steering指明轮子是否转向,汽车都是前轮转向,因此前轴的steering为true,后轴的steering为false。
接着在Tank类中编写控制车轴的方法(之前编写的两种操作模式已然无用,可以删去)。定义如图2-53下面的代码所示的变量,其中axleInfos是上面定义的AxleInfo类型的列表,代表坦克中的各个车轴,普通汽车只有前轮和后轮两个车轴,axleInfos的长度应该设置为2。其他机构变量分别代表马力、制动(刹车)和转向角。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0083_0002.jpg?sign=1739309424-zHy37HSHTuojzkcKmybzuERFYLjJQaZX-0-a4831b8021d52e437ebbeee6f9645879)
图2-53 列表
axleInfos为List(列表)类型,List类型“System.Collections.Generic”命名空间中,需要在Tank类代码的最前面添加using语句“using System.Collections.Generic; ”。List<AxleInfo>即定义了AxleInfo类型的列表,也可以简单地把它理解为一个数组(如图2-53所示)。列表对象的常用方法有:Add (添加项)、Clear(清空列表)、Remove(删除元素)、RemoveAt(删除指定索引的元素),并可以通过“[索引]”获取指定索引的内容(如axleInfos[0])。
代码如下所示。
//轮轴 public List<AxleInfo> axleInfos; //马力/最大马力 private float motor = 0; public float maxMotorTorque; //制动/最大制动 private float brakeTorque = 0; public float maxBrakeTorque = 100; //转向角/最大转向角 private float steering = 0; public float maxSteeringAngle;
接着在Tank类中定义PlayerCtrl方法,用来处理用户的输入。当玩家按键盘的“上”或“下”键时,motor等于maxMotorTorque或它的负值;按“左”或“右”键时, steering等于maxSteeringAngle或它的负值。若玩家使用摇杆或方向盘等设备,motor会在-maxMotorTorque到maxMotorTorque之间取值,steering也会在-maxSteeringAngle到maxSteeringAngle之间取值,代码如下所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0084_0001.jpg?sign=1739309424-hk5KIsyULiDX4ZslRXijGTcomrkGGLMi-0-1188720c2828c8685159d08051f79ef6)
然后在Update()中调用PlayerCtrl,把玩家操作的语句放到PlayerCtrl中而不是直接放到Update中,是因为后面还要实现电脑控制的功能,这样处理后,后续只需要添加类似PlayerCtrl的方法,而不必修改代码结构。在Update方法中通过foreach遍历每个车轴,依照车轴的steering和motor属性,给轮子施加转向、马力和制动,代码如下所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0084_0002.jpg?sign=1739309424-qD2ZkUm5bSTnIKGqpeA7yrbaFn8UqLN9-0-f3711ac23ea67605345afc01e7b3d375)
至此,我们已经编写完控制坦克的代码,最后只需要再做一些设置,便能让坦克行动起来。在属性面板中将坦克模型的AxleInfos的Size设置为2(前后两条车轴),并将4个WheelCollider拖入其中,设置为后驱(如图2-54所示)。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0085_0002.jpg?sign=1739309424-Ye811zpi8Gfyyt1AHD8WRZIImIGY7kSS-0-16844767298d98ca669dedb6a53b20f5)
图2-54 Tank的属性设置
选择坦克的4个WheelCollider,调整参数(具体参数的含义请参照2.5.2节),以获得更好的驾驶体验,如图2-55所示。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0085_0003.jpg?sign=1739309424-N8CRqwADZAAEFx2sIvfJBFfmUccpGe80-0-ae117fae2c3d2ad337d13d567b90d73b)
图2-55 WheelCollider的属性
这时再运行游戏,便可以控制坦克行进了,此时履带会紧贴起伏的地表,如图2-56所示。如果容易翻倒,可以调整Rigidbody的质量、坦克重心和马力等数值,以达到良好的驾驶体验。因为WheelCollider的详细参数涉及不少物理知识,这里不再展开,读者可以查阅更多资料,以便更好地调整数值。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0086_0001.jpg?sign=1739309424-M70geCDDkBFdLUCkoB3zNZ6HV9dC9yDY-0-f221dfdbe11f28e3826e259df654c063)
图2-56 坦克行驶在陡坡上
如果读者觉得把坦克当成四轮汽车,还不能很真实地模拟履带行驶,可以尝试添加更多的轮子,以求真实模拟(如图2-57所示)。
![](https://epubservercos.yuewen.com/FABC1B/6158700004756901/epubprivate/OEBPS/Images/figure_0086_0002.jpg?sign=1739309424-D1A31JRRqBhZaNl0RSJBKO3R6ZZdus8y-0-527109343982574ae2798bbc2e391764)
图2-57 添加更多轮子的坦克
2.5.4 制动(刹车)
本小节将处理坦克的刹车功能,玩家按下后退键,又分为以下两种情况。
1)坦克向前行进时,应当刹车。
2)坦克静止时,应当后退。
第一个问题便是如何判断坦克是在向前行进还是静止,车轮碰撞器有一个rpm(转速)属性,可以通过它判断坦克的状态。如果转速大于某个值(这里取5),可以粗略地视为坦克在前进;如果小于某个值(这里取-5),可以视为坦克在后退;如果转速接近0,则视为静止。
修改PlayerCtrl,通过轮子的rpm属性(转速)判断坦克的运行状态。如果坦克在移动,则设置brakeTorque的值,使坦克刹车,代码如下所示。
public void PlayerCtrl() { //马力和转向角 motor = maxMotorTorque * Input.GetAxis("Vertical"); steering = maxSteeringAngle * Input.GetAxis("Horizontal"); //制动 brakeTorque=0; foreach(Axlelnfo axlenlnfo in axlenfos) { if (axleInfo.leftWheel.rpm > 5 && motor < 0) //前进时,按下"下"键 brakeTorque = maxBrakeTorque; else if (axleInfo.leftWheel.rpm < -5 && motor > 0) //后退时,按下"上"键 brakeTorque = maxBrakeTorque; continue; } //炮塔炮管角度 turretRotTarget = Camera.main.transform.eulerAngles.y; turretRollTarget = Camera.main.transform.eulerAngles.x; }
运行游戏,即可体验到刹车的效果。