作者: 吴亚峰 , 杜化美 , 张月霞 , 索依娜 责编: 张涛
6.1 刚体
Unity 4 3D开发实战详解
6.1.1 刚体特性
在Unity内建物理引擎中,首先要介绍的是刚体(Rigidbody)的概念。包含有该类组件的游戏对象,会遵循万有引力定律,在重力的作用下,使物体垂直下落。刚体组件还会影响物体发生碰撞时的反应,使物体遵循惯性定律,并在其他物体运动冲击作用下产生速度或者形变。
刚体作为物理引擎中的最基本组件,保证了所有物体受到重力的约束。Unity开发平台中,对刚体设置了很多属性和变量,并对应封装了多个相关方法,下面进行分别介绍。
1.刚体属性
为了利于开发者控制物理系统,Unity提供了多个属性接口。开发者可以通过更改这些参数来控制物体的物理状态。实际开发中,这些参数都被详细地罗列在属性查看器中,开发者很容易就可以对其进行更改。接下来对这些属性进行介绍。
(1)质量(Mass)
该属性表示刚体的质量,其数据类型是float,默认值为1。其在属性查看器中的位置如图6-1所示。该属性的大小是有严格要求的,一般来说,大部分物体的质量属性值接近0.1才符合日常生活的感官感受,一旦超过10.0,就会失去仿真所需达到的效果。
(2)平移阻力(Drag)
该属性是物体的平移阻力,其数据类型是float,初始值为0。Drag属性在属性查看器中的位置如图6-2所示。在现实生活中,物体会受到各方面的影响,速度会渐渐衰减,为了模拟这一效果,Unity设定了平移阻力属性。这一属性值越高,物体的速度衰减越严重。
(3)旋转阻力(Angular Drag)
该属性表示物体的旋转阻力,其数据类型是float,初始值为0.05。其在属性查看器中的位置如图6-3所示。当一个物体进行旋转的时候,其旋转的角速度也会受各方面影响衰减,为模拟这一现象,Unity设定了旋转阻力属性。此属性值越高,物体的角速度衰减越严重。
(4)使用重力(Use Gravity)
该属性表示的是该物体是否受到重力的影响,其数据类型是boolean,初始值为true。其在属性查看器中的位置如图6-4所示。这一属性被设为false时可以模拟物体在外太空状态下的失重状态。有时候也可以根据需要用于某些特殊的场合。
注意
当该值被设定为false的时候,物体将不受重力影响,但刚体的其他特性还是存在的。
(5)是否遵循运动学(Is Kinematic)
该属性表示本游戏对象是否遵循牛顿运动学物理定律,其数据类型是boolean,初始值为false。其在属性查看器中的位置如图6-5所示。如果该属性被置为true,本物体的运动状态将不受外力、碰撞和关节的影响,而只受到动画以及附加在物体上的脚本的影响。
另外,虽然当该属性被置为true时物体将不受物理定律的约束,但是该物体还是会影响其他的物体,改变其他物体的运动状态。在游戏开发中此属性会被经常用到,想象一下在第一人称射击游戏中,敌人被击杀后会倒地不动,因为这个敌人对象Rigidbody的Is Kinematic属性被设置为了true。
(6)插值(Interpolate)
该属性表示的是该物体运动的插值模式。其在属性查看器中的位置如图6-6所示。在默认状态下,插值Interpolate是被禁用的,可以选择Interpolate模式,在此模式下物理引擎会在物体的运动帧之间进行插值,使得运动更加自然。
不得不提的是,插值导致了物理模拟和渲染的不同步,进而产生物体轻微抖动的现象。建议在开发中,对主要角色使用插值,而其他的则禁用此功能,以达到折中效果。
(7)冻结旋转(Freeze Rotation)
该属性表示的是该物体的旋转是否受到物理定律的约束。其在属性查看器中的位置如图6-7所示。默认状态下任意轴的旋转是受物理定律控制的。该属性的值是修改在每个轴上的旋转属性来实现的,实际开发中可以灵活地使用。
在第一人称射击游戏中,通过此属性去除物理属性对旋转的控制,可以使玩家完全控制视角旋转。
(8)碰撞检测模式(Collision Detection)
该属性表示的是碰撞检测模式。其在属性查看器中的位置如图6-8所示。默认状态下,碰撞检测模式是Discrete 。在没有碰撞检测的情况下,碰撞物体会穿过对方,产生所谓的“穿透”现象。为避免此类问题的发生,碰撞检测工作是必须做的。
关于碰撞模式的选择,可选的有不连续模式(Discrete)、连续模式(Continuous)和动态连续模式(ContinuousDynamic),其中动态连续模式适用于高速运动的物体,但是耗费资源较大。连续模式仅仅可以用于球体、胶囊和盒子碰撞者的刚体,而且会严重影响物体的运动表现。所以大部分的物体采用不连续模式作为碰撞检测模式。
2.刚体变量
为了获取或者更改物体的运动状态,Unity还预留了多个变量接口,这些接口使得运动处理变得相当简单。接下来将具体地介绍一下这些变量。
(1)刚体的速度(velocity)
此变量表示物体的速度值,在Unity中单位1表示现实生活中的1米。在开发过程中,直接更改此变量的值并不是一种明智的做法,因为Unity中物体的运动经过非常复杂的计算使得物体的运动自然平滑,如果有外加干预,会迫使物体的运动模拟失真。
下面的代码可以实现一个物体的速度骤增,以实现瞬移效果,具体代码如下:
1 function FixedUpdate () {
2 if (Input.GetButtonDown ("GO")) { //按下按钮“GO”
3 rigidbody.velocity = Vector3(10,0,0); //给予_x_方向速度
4 }}
(2)是否使用重力(useGravity)
此变量表示当前的物体是否受到重力的约束。在开发过程中,灵活地更改此变量可以达到一些特殊的场景要求,比如在外太空环境下的失重状态。
可以编写代码实现物体的失重状态,具体代码如下:
1 function Start (other : Collider) { //脚本被激活
2 if (other.attachedRigidbody) { //此物体包含有刚体组件
3 other.attachedRigidbody.useGravity = false; //设置物体不受重力约束
4 }}
(3)冻结旋转(freezeRotation)
此变量表示当前的物体的旋转是否受到物理定律的约束。通过此变量,可以更改x、y、z三个方向中的某个方向的旋转约束,以达到开发者想要达到的旋转效果。
该变量使用的具体语法代码如下所示:
1 rigidbody.freezeRotation = true;
(4)刚体的位置(position)
此变量表示了刚体的位置,改变此变量时,引擎会在物理阶段结束后,将物体放置到指定的位置。
下面的脚本实现了将物体放置于原点的功能,具体代码如下:
1 function Start () { //脚本被激活
2 rigidbody.position = Vector3.zero; //将物体放置在(0,0,0)位置
3 }
说明
这种做法不适用于旋转物体,可以使用MovePosition代替。
(5)刚体的旋转(rotation)
此变量表示了物体的旋转状态,改变此变量时,引擎会在物理阶段结束后,将物体旋转到指定的状态。
下面的脚本实现了设置物体无旋转,具体代码如下:
1 function Start () { //脚本被激活
2 rigidbody.rotation = Quaternion.identity;//将物体的旋转状态设置为原始态(无旋转)
3 }
说明
这种做法不适用于旋转物体。可以使用MoveRotation方法来代替。
3.刚体常用方法
在讲解了刚体的属性与变量后,有必要讲解一下Unity提供的相关方法。
(1)给刚体施加力(AddForce)
此方法被调用时,将会施加给刚体一个瞬时力,然后物体在力的作用下,产生一个初速度,接着物体在初速度的作用下开始运动。
下面的代码实现了对物体施加竖直向上的力,具体代码如下:
1 function FixedUpdate () {
2 rigidbody.AddForce (Vector3.up * 10); //施加竖直向上的力
3 }
注意
AddForce方法尽量不要用在Update方法中,因为这样的方法在Update中会被不停地调用,与被调用一次不同,这种是模拟了一种加速度的效果。
(2)移动刚体位置(MovePosition)
此方法被调用时,会使刚体按照参数移动到某个位置。此方法经常用于FixedUpdate方法中。具体用法如下列代码所示:
1 private var speed : Vector3 = Vector3 (3, 0, 0); //声明速度向量
2 function FixedUpdate () {
3 rigidbody.MovePosition(rigidbody.position + speed * Time.deltaTime); //按照speed为速度平移刚体
4 }
(3)旋转刚体(MoveRotation)
此方法被调用时,会使刚体按照参数旋转到某个方位。此方法经常用于FixedUpdate方法中。具体用法如下列代码所示:
1 var eulerAngleVelocity : Vector3 = Vector3 (0, 100, 0); //声明角速度
2 function FixedUpdate () {
3 var deltaRotation : Quaternion = Quaternion.Euler(eulerAngleVelocity * Time. deltaTime);
4 rigidbody.MoveRotation(rigidbody.rotation * deltaRotation);//以eulerAngleVelocity为角速度旋转
5 }
6.1.2 物理管理器
前面的内容简单介绍了刚体的属性、变量和常用方法。接下来这一部分将讲解一下物理管理器(Physics Manager)的相关内容。
Unity作为一个优秀的游戏开发平台,其出色的管理模式是令人称赞的。在Unity中,不仅可以对单个分组进行属性设置,也可以对场景全局进行设置。这一部分讲解Unity中场景的全局物理参数是如何设置的。
1.物理管理器预览
(1)打开Unity开发平台,在菜单栏中选择“Edit”选项,会弹出编辑列表,如图6-9所示。在编辑列表中,选择“Project Settings”选项,弹出全局设置列表,如图6-10所示。在全局设置列表中选择“Physics”,即可进入物理管理器。
(2)按照第(1)步进行操作以后,会在属性查看器中呈现物理管理器编辑器,如图6-11所示。
2.物理管理器参数
在前面的内容中,讲解了物理管理器的打开操作。接下来介绍的是物理管理器中的相关参数的含义和用法。
(1)重力(Gravity)。
该属性表示的是项目中的重力加速度,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-12所示,该属性3个数值分别指定在x、y、z方向上的重力加速度,一般重力是竖直向下的,所以只有y轴上有一个负值,大小默认为单位是-9.81米/(秒^2)。
(2)默认材质(Default Material)。
该属性表示当物体没有被指定物理材质的时候,该物体的默认材质。其在物理管理器中的位置,如图6-13所示。默认状态下是没有指定值的,因为在Unity中,每个物体的物理材质可能会有很大的不同,有时候指定默认材质并不是有太大的用途。
(3)反弹阈值(Bounce Threshold)。
该属性表示的是项目中的反弹阈值,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-14所示,如果两个碰撞体的相对速度低于该值,那么将不会反弹。该值也用于减少抖动,所以不建议设置为很低的值。
(4)休眠速度(Sleep Velocity)。
该属性表示的是项目中刚体自动进入休眠时状态的速度值。其在物理管理器中的位置如图6-15所示。该值是休眠的默认线速度,低于该速度的物体将会进入休眠。如果没有这种机制,项目中的所有物体将永远都在计算中,对于资源的回收利用很不利。
(5)休眠角速度(Sleep Angular Velocity)。
该属性表示的是项目中的休眠角速度,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-16所示,如果角速度低于该值,物体将进入休眠状态。
(6)最大角速度(Max Angular Velocity)。
该属性表示的是项目中刚体最大的角速度值。其在物理管理器中的位置如图6-17所示,设定最大值可以避免角速度数值不稳定现象的发生。在脚本中可以用语法Rigidbody.maxAngular Velocity来更改此值。
(7)最小穿透禁区(Min Penetration For Penalty)。
该属性表示的是项目中的最小穿透禁区,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-18所示,该属性的具体含义是在碰撞检测器将两个物体分开前,可以穿透多少米的距离。较高的值会导致较多的物体穿透,不过可以减少抖动。
(8)求解迭代次数(Solver Iteration Count)。
该属性表示的是项目中关节和连接计算过程中的迭代次数。其在物理管理器中的位置如图6-19所示,决定了关节和连接的计算精度。求解迭代次数越高精度越高,但是次数过高会浪费过多的资源,不利于项目的正常运行,一般来说此值设置为7就可以适用于大多数情况。
(9)射线检测命中触发器(Raycasts Hit Triggers)。
该属性表示的是项目中的射线检测命中触发器,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-20所示。如果启用,那么进行射线检测时命中碰撞体会返回一个命中消息,如果关闭,则不返回命中消息。
(10)层碰撞矩阵(Layer Collision Matrix)。
该属性表示的是项目中的层碰撞矩阵,该参数将被应用于所有刚体。其在物理管理器中的位置如图6-21所示。定义层碰撞检测系统的行为。
说明
层碰撞矩阵在后面的介绍中会有比较详细的解释,此处不再赘述。
6.1.3 碰撞与休眠
1.碰撞的使用
与碰撞相关的内容包括碰撞器(Collision)类、碰撞检测模式选择和最小穿透禁区设置,后两个内容已经在前面的内容中进行了详细的介绍,这里不再赘述。下面重点讲解一下碰撞器类及其相关方法的使用。
(1)碰撞器类的变量如表6-1所示,碰撞器有很多变量供碰撞处理使用,其中除了碰撞接触点(contacts)变量,其他变量都是只读的,读者请务必注意。
(2)碰撞器相关方法。
碰撞触发(OnCollisionEnter)
此方法在碰撞发生时被系统自动调用。该方法会传递一个Collision类,该类包含了碰撞点和相对速度等信息。开发者可以利用这些信息对物体进行碰撞处理,下面的代码就是一个简单的例子:
1 var explosionPrefab : Transform;
2 function OnCollisionEnter(collision : Collision) { //碰撞开始
3 var contact : ContactPoint = collision.contacts[0]; //获取0号碰撞点
4 var rot : Quaternion =
5 Quaternion.FromToRotation(Vector3.up, contact.normal); //旋转以使_y_轴面向接触点法向量
6 Instantiate(explosionPrefab, pos, rot); //实例化爆炸对象
7 Destroy (gameObject); //销毁游戏对象
8 }
说明
该方法只有在两个物体都遵循物理定律的情况下发生,即两者的Is kinematic属性都为false。
- 碰撞状态(OnCollisionStay)
此方法在碰撞时两物体接触过程中被调用。该方法同样会传递一个Collision类,该类包含了碰撞点和相对速度等信息,下面的代码就是一个简单的例子:
1 function OnCollisionStay(collisionInfo : Collision) { //碰撞进行中
2 for (var contact : ContactPoint in collisionInfo.contacts) {
3 Debug.DrawRay(contact.point, contact.normal * 10, Color.white); //绘制所有的接触点和其法向量
4 }}
- 退出碰撞(OnCollisionExit)
此方法在碰撞时停止触碰刚体或者碰撞的时候调用。该方法同样会传递一个Collision类,该类包含了碰撞点和相对速度等信息,下面的代码就是一个简单的例子:
**1 function OnCollisionExit(collisionInfo : Collision) { //碰撞结束
2 print("不再和" + collisionInfo.transform.name+"发生碰撞。"); //打印**碰撞结束信息
3 }
2.睡眠的使用
(1)睡眠(Sleep)。
该方法被调用时,会强制一个刚体进入睡眠状态至少一帧的时间。一般用在Awake方法中,设置脚本开始执行时刚体睡眠。刚体进入睡眠后,物体相当于没有刚体存在,其具体的语法如下:
1 rigidbody.Sleep();JavaScript
(2)强制唤醒(WakeUp)。
该方法被调用时,会强制使一个处于睡眠状态刚体被唤醒,其具体的语法如下:
1 rigidbody.WakeUp();