本节书摘来异步社区《Unity 4 3D开发实战详解》一书中的第6章,第6.5节,作者: 吴亚峰 , 杜化美 , 张月霞 , 索依娜 责编: 张涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。
Unity 4 3D开发实战详解
在前面的内容中,讲解了Unity开发平台下物理引擎的相关内容,正是这一完善的物理引擎,使得模拟现实变得极其简单。在本小节中,将通过一个交通工具的小案例来模拟现实生活中汽车的各种运动。下面将对交通工具案例的开发步骤进行介绍。
1.案例的构思
在开发案例之前,这里先介绍一下本案例的设计思路。
(1)首先要明确案例要达到的目的。本案例是为了演示使用Unity物理引擎模拟现实生活中交通工具的运动特性。
(2)接着要明确案例场景的设计。在本案例中,使用了地形引擎系统,在地形上有一些任意位置形状的小山丘。同时,场景中有一辆汽车模型,在汽车正前方摆放有两个油桶和3个木箱。这些场景的设计是为了观察汽车在地形上的运动以及跟油桶和木箱的碰撞情况。
(3)然后是对汽车模型的开发。导入的汽车模型是不能够运动的,必须给其添加必要的组件。在本案例中,汽车模型整体被添加了刚体和相应的脚本组件,车轮也被添加了车轮碰撞器组件和相应的脚本组件。
(4)最后就是把PC端的程序移植到Android设备上。本案例中采用Unity引擎自带的Single Joystick预制件来实现Android设备上的操纵杆控制汽车。
2.案例场景的设计
在明确了案例的构思后,就可以按照构思的步骤来进行开发了。主要的开发步骤如下。
(1)首先新建一个项目,选择File→New Project选项创建一个新项目,并命名为“VehicleDemo”。然后,选择File→Save Scene(或者按下快捷键Ctrl+S)保存场景为Car。最后,选择GameObject→Create Other→Directional light来创建一个光源,如图6-118所示。
(2)创建地形。选择Terrain→Create Terrain创建一个新的地形,然后使用地形编辑工具对地形进行适当的编辑,最终的地形效果如图6-119所示。
说明
因为地形引擎系统不是本小节的重点,该内容将在后续的章节里具体介绍,所以,本小节只是简单地说明,如果读者当前还不习惯使用地形引擎系统,可以创建一个平面来替代,相信对认真学习过前面章节内容的读者来说,创建一个平面应该不是什么难题。
(3)导入资源。将光盘中本章节对应文件夹下的“资源包”文件夹中的“Model.unitypackage”文件复制到第(1)步中新建项目时生成的“VehicleDemo”文件夹下。然后,选择Assets→Import Package→Custom Package导入“Model.unitypackage”中的所有文件,如图6-120所示。
(4)在场景中创建汽车和油桶模型。将“AssetsModel”文件夹下的“Car_Model”拖曳到Scene视图中,如果汽车模型没有显示贴图,则可以将“AssetsModelMaterials”文件夹下的“Car_Material”材质分别拖曳到汽车模型的车身和4个车轮上。然后用同样的方法,在场景中创建两个油桶模型,分别命名为“OilTank1”和“OilTank2”,如图6-121所示。
(5)在场景中创建3个木箱。选择GameObject→Create Other→Cube分别创建3个立方体,且分别命名为“Box1”、“Box2”、“Box3”。然后将“AssetsTexture”文件夹下的“WoodBoxTex”纹理贴图分别拖曳到新创建的3个立方体上,调整场景中各个模型的Transform参数,如图6-122所示。
3.案例主体部分设计
前面刚介绍完场景的搭建,接下来将介绍案例主体部分的设计。这部分主要是为了增加案例运行过程中的真实性和交互性,这就需要给场景中的各个游戏对象加上相应的组件,具体步骤如下。
(1)为油桶和木箱添加物理属性。选择Component→Physics→Rigidbody为油桶和木箱添加刚体,接着选择Component→Physics→Box Collider为油桶添加“Box Collider”组件(木箱已有该组件,不需重复添加)。同时,为木箱添加“Wood”材质,为油桶添加“Metal”材质,如图6-123所示。
(2)为汽车的车轮添加“Wheel Collider”组件。选择GameObject→Create Empty创建一个空对象,重命名为“FLCollider”,然后选择Component→Physics→Wheel Collider给“FLCollider”添加碰撞体,并将“FLCollider”的位置和汽车左前轮位置对齐。按照此操作步骤,依次创建“FRCollider”、“BLRCollider”、“BRCollider”,并组织好目录结构,如图6-124所示。
(3)为汽车的车身添加“Box Collider”组件。选择GameObject→Create Empty创建一个空对象,重命名为“BottomCollider”,然后选择Component→Physics→Box Collider给“BottomCollider”添加碰撞体,并使碰撞体包裹住车身下半部分。按照此操作步骤,创建“TopCollider”并使其包裹住车身上半部分,并组织好目录结构,如图6-125所示。
(4)为汽车的车轮编写脚本。该脚本的作用是控制车轮的位置和旋转参数,使车轮在车的移动过程中跟着转动,这样车轮的运动看起来更逼真。具体代码如下所示。
1 var CorrespondingCollider : WheelCollider;//定义车轮碰撞器
2 private var RotationValue : float = 0.0;//定义车轮旋转值,初始值为0
3 function Update () {
4 var hit : RaycastHit;//为射线碰撞定义一个RaycastHit类型的变量
5 //定义碰撞中心点为车轮碰撞器的中心点
6 var ColliderCenterPoint : Vector3 =
7 CorrespondingCollider.transform.TransformPoint( CorrespondingCollider. center );
8 //以下代码实现了发生碰撞后车的移动
9 if ( Physics.Raycast( ColliderCenterPoint, -CorrespondingCollider.transform.up,
10 hit, CorrespondingCollider.suspensionDistance + CorrespondingCollider.radius ) ) {
11 transform.position = hit.point + (CorrespondingCollider.transform.up *CorrespondingCollider.radius);
12 }else{
13 transform.position = ColliderCenterPoint - (CorrespondingCollider.transform.up *
14 CorrespondingCollider.suspensionDistance);
15 }
16 //重置车轮旋转值,保证车在移动时车轮跟着转
17 transform.rotation = CorrespondingCollider.transform.rotation *
18 Quaternion.Euler( RotationValue, CorrespondingCollider.steerAngle, 0 );
19 //计算车轮旋转值,其中rpm表示车轮转速
20 RotationValue += CorrespondingCollider.rpm * ( 360/60 ) * Time.deltaTime;
21
}```
- 第1-2行为变量的定义,第一个变量的值将在属性查看器中予以赋值。
- 第3-21行为Update方法的重写,主要是控制汽车模型的车轮的相关姿态,确保汽车在运动过程中车轮的运动和现实生活中相同。
(5)为汽车的车轮添加脚本。将“WheelAlignment.js”脚本挂载到车轮游戏对象“FrontLeftWheel”上,然后将“FLCollider”游戏对象拖曳到属性查看器的“CorrespondingCollider”变量处,如图6-126所示。同时,将该脚本分别挂载到其他3个车轮游戏对象上,做相同的操作。
(6)添加触摸操纵杆。选中“Assets”文件夹,右键选择Import Package→Standard Assets(Mobile)后弹出“Importing package”对话框,然后选择其中的“Single Joystick.prefab”、“Joystick.js”、“ JoystickThumb.psd”3个文件导入。导入后,找到“Standard Assets (Mobile)\ Prefabs”文件夹下的“Single Joystick”预制件,并将其拖曳到“Hierarchy”视图中,如图6-127所示。
![126_127](https://yqfile.alicdn.com/411f46e306597e805cbc8d4dabddb9babe770046.png)
(7)为汽车编写控制脚本。该脚本的作用是控制汽车的移动和转弯,如果是在PC上,则通过方向键或者“W”、“A”、“S”、“D”4个键来控制汽车的方向;在Android设备上,则通过触摸操纵杆来控制其方向。具体代码如下所示。
1 var FrontLeftWheel : WheelCollider;//定义左前轮碰撞器
2 var FrontRightWheel : WheelCollider;//定义右前轮碰撞器
3 var GearRatio : float[];//定义齿轮比数组,程序会根据齿轮数比来决定应该给车轮多大的扭矩
4 var CurrentGear : int = 0;//定义当前齿轮索引
5 var EngineTorque : float = 600.0;//定义发动机转矩
6 var MaxEngineRPM : float = 3000.0;//定义发动机最大转速
7 var MinEngineRPM : float = 1000.0;//定义发动机最小转速
8 private var EngineRPM : float = 0.0;//定义发动机转速
9 var moveJoystick : Joystick;//定义操纵杆
10 function Start () {
11 //给汽车质心的Y方向的位置赋值,确定汽车质心的位置是为了保证汽车在运动过程中不容易翻车
12 rigidbody.centerOfMass.y = -1.5;
13 }
14 function Update () {
15 rigidbody.drag=0.15 +rigidbody.velocity.magnitude/25;//设置汽车运行阻力,限制汽车速度
16 //计算汽车转速
17 EngineRPM = (FrontLeftWheel.rpm + FrontRightWheel.rpm)/2 * GearRatio[CurrentGear];
18 ShiftGears();//调用方法
19 if(Application.platform == RuntimePlatform.Android) {//如果程序运行在Android平台上
20 var touchKey_x : float = moveJoystick.position.x; //获取操作杆的坐标x
21 var touchKey_y : float = moveJoystick.position.y; //获取操作杆的坐标y
22 //根据操纵杆的y位置计算汽车速度
23 FrontLeftWheel.motorTorque = EngineTorque / GearRatio[CurrentGear] * touchKey_y;
24 FrontRightWheel.motorTorque = EngineTorque / GearRatio[CurrentGear] * touchKey_y;
25 //根据操纵杆的y位置计算汽车转向
26 FrontLeftWheel.steerAngle = 10 * touchKey_x;
27 FrontRightWheel.steerAngle = 10 * touchKey_x;
28 }else {//如果是其他平台
29 //计算汽车速度,当按下前进键或后退键,计算两个前轮的马达转矩
30 FrontLeftWheel.motorTorque = EngineTorque / GearRatio[CurrentGear] * Input.GetAxis("Vertical");
31 FrontRightWheel.motorTorque = EngineTorque / GearRatio[CurrentGear] * Input.GetAxis("Vertical");
32 //控制汽车转弯,当按下左右按键时,计算两个前轮的旋转角度
33 FrontLeftWheel.steerAngle = 10 * Input.GetAxis("Horizontal");
34 FrontRightWheel.steerAngle = 10 * Input.GetAxis("Horizontal");
35 }}
36 function ShiftGears() {//转换齿轮数的方法
37 if ( EngineRPM >= MaxEngineRPM ) {//如果引擎转速大于等于最大引擎速度
38 var AppropriateGear : int = CurrentGear;//定义一个合适齿轮数
39 for ( var i = 0; i < GearRatio.length; i ++ ) {//遍历所有的齿轮数比
40 //如果左前轮转速和齿轮数比的乘积小于最大引擎转速
41 if ( FrontLeftWheel.rpm * GearRatio[i] < MaxEngineRPM ) {
42 AppropriateGear = i;//赋值
43 break;//跳出循环
44 }}
45 CurrentGear = AppropriateGear;//赋值
46 }
47 if ( EngineRPM <= MinEngineRPM ) {//如果引擎转速小于等于最大引擎速度
48 AppropriateGear = CurrentGear;//赋值
49 for ( var j = GearRatio.length-1; j >= 0; j -- ) {//遍历所有的齿轮数比
50 //如果左前轮转速和齿轮数比的乘积大于最小引擎转速
51 if ( FrontLeftWheel.rpm * GearRatio[j] > MinEngineRPM ) {
52 AppropriateGear = j;//赋值
53 break;//跳出循环
54 }}
55 CurrentGear = AppropriateGear;//赋值
56 }}
- 第1-9行为变量的定义,其中的非私有变量都可以在属性查看器中看到或者赋值。
- 第10-13行为Start方法的重写,主要是初始化汽车质心的y轴。
- 第14-56行为Update方法的重写,主要是判断程序的运行平台,针对不同的平台启用不同的控制模式(PC上用键盘控制,移动设备上用操纵杆控制),然后就是计算车速和汽车转弯的角度。
(8)添加汽车控制脚本组件。将编写好的“CarControl.js”脚本挂载到“Car_Model”对象上,然后将“FLCollider”和“FRCollider”分别拖曳到属性查看器中的“Front Left Wheel”和“Front Right Wheel”变量处,并为“Gear Ratio”数组赋值,最后将“Single Joystick”拖曳到“Move Joystick”变量处,如图6-128所示。
![128](https://yqfile.alicdn.com/3bf13ce6601a12072a417e1b79d4a95dd049a3f1.png)
(9)为汽车添加刚体。选中“Car_Model”后选择Component→Physics→Rigidbody为汽车添加刚体,然后修改“Mass”变量值为1000,如图6-129所示。
(10)添加摄像机跟随脚本。选中“Assets”文件夹,右键选择Import Package→Scripts后弹出“Importing package”对话框,选择其中的“SmoothFollow.js”脚本文件导入,然后选中“Main Camera”后,选择Component→Camera-Control→Smooth Follow(或者直接将该脚本拖曳到主摄像机对象上),最后将“Car_Model”拖曳到“Target”变量处,并修改其他参数,如图6-130所示。
![129_130](https://yqfile.alicdn.com/4dc936e9102f7cbd7b5847755a6f01af8b9bb1fa.png)
(11)修改玩家设置选项中的参数。选择File→Build Settings后进入“Build Settings”对话框,然后选择Android平台,单击“Player Settings”按钮,接着将属性查看器中“Resolution and Presentation”选项下的“Default Orentation”属性修改为“Landscape Left”,即完成了横屏设置,如图6-131所示。
![131](https://yqfile.alicdn.com/bbfd3e3b329e113817445026934a406c4fcbeb48.png)
最后将“Other Settings”选项下的“Bundle Identifier”修改成合适的内容,这里修改为“com.bn.vehicle”,如图6-132所示。
![132](https://yqfile.alicdn.com/5a8e792938fdf651a1a7950b06e1dadcf52b9ae2.png)
>说明
>“Bundle Identifier”中的内容必须修改,不能是系统默认值,否则,Unity引擎将不允许将程序发布到Android端。
4.案例的最终效果
至此,案例的开发基本完成了,本案例可以发布成exe可执行文件在PC上运行,也可以直接点击Unity引擎里的运行按钮可以直接观看和体验运行效果,运行效果如图6-133所示。当然,也可以发布到Android设备上运行,其运行效果如图6-134所示。
![133_134](https://yqfile.alicdn.com/31eb9f262c8905a6d006eed86daf530537111bcc.png)
>说明