Unity 3D脚本编程与游戏开发(2.2)

3.2.7 修改物理材质

        每个物体都有着不同的摩擦⼒。光滑的冰⾯摩擦⼒很⼩,⽽地毯表⾯的摩擦⼒则很⼤。另外每种材料也有着不同的弹性,橡⽪表⾯的弹性⼤,硬质地⾯的弹性⼩。在Unity中这些现象都符合⽇常的理念。
        虽然从原理上讲,物体的摩擦⼒和弹性有着更复杂的内涵,例如普通的钢板看起来并没有太多弹性,但在合适的条件下却可以⽤来作为弹簧板。Unity的物理引擎对物体表⾯材料的性质做了简化处理,仅有5种常⽤属性,但可以满⾜⼤多数游戏的需求。
        在Project窗⼝中单击⿏标右键,选择Create→Physics Material,就可以创建⼀个物理材质。物理材质的参数被简单定义为Dynamic Friction(动态摩擦系数)、Static Friction(静态摩擦系数)、Bounciness(弹性系数)、与其他物体接触时的Friction Combine(摩擦⼒系数算法)和Bounce Combine(弹性系数算法),如图3-8所⽰。

        动态摩擦系数就是物体之间正在相对滑动时的摩擦系数。例如0.1代表很光滑的表⾯,0.9代表很粗糙的表⾯。
        静态摩擦系数就是物体之间没有相对滑动时的摩擦系数。现实⽣活中,物体的静态摩擦⼒⼀般略⼤于动态摩擦⼒,当然在游戏世界中可以随意调节它们的⼤⼩。弹性系数可以调节物体反弹⼒的⼤⼩。例如0.8可以代表充⽓很⾜的篮球,0则代表没有任何反弹⼒。弹性系数⼀般不能⾼于0.9,否则会导致物体反弹的速度⽐撞击前的速度还快,这样它会变得越来越快,没有⽌境。
        最后两个参数决定了两个物体表⾯都具有摩擦系数和弹性系数时,如何计算综合的摩擦系数和弹性系数。可选择取平均值、取最⼤值、取最⼩值或相乘4种⽅式。最后,有两点值得说明。
        ⼀是物理材质是配合碰撞体使⽤的。碰撞体有⼀个“材质”(Material)的属性,这⾥⾃然不是指渲染材质,⽽是指物理材质。将创建好的物理材质拖曳到该属性上即可指定该属性。
        ⼆是不指定任何物理材质时,碰撞体具有默认的物理材质。

3.2.8 FixedUpdate详解

        在第2章⾥提到过FixedUpdate,当时解释它是物理更新,会保证稳定的时间间隔。所谓Fixed的意思就是“固定的、稳定的”。获取两次Update之间的时间间隔⽤Time.deltaTime,获取两次FixedUpdate之间的时间间隔⽤Time.fixedDeltaTime。

        当设备运⾏不流畅、帧率下降时,会发现Time.deltaTime变⼤了(即帧与帧之间的时间间隔变⻓),但是Time.fixedDeltaTime却不会。⼀般Time.fixedDeltaTime会是⼀个固定的值(默认为0.02秒,可以通过选择主菜单的Edit→Project Settings→Time来修改)。
        FixedUpdate的作⽤和原理解释起来不是很容易,笔者尽可能详细说明。要理解FixedUpdate,需要先理解“物理系统对于时间是⾮常敏感的”。下⾯举两个例⼦说明物理系统对于时间的敏感性。
        (1)⼦弹从枪⼝射出,0.1秒后击中物体。假如物理更新频率不稳定,导致⼦弹接触物体时并没有及时检测,再晚0.02秒,⼦弹就已经穿过了物体。这样⼦弹就错过了碰撞的时机,导致后续结果完全不同。
        (2)在Unity中做⼀个在地上弹跳的⽪球,如图3-9所⽰,使⽤物理材质,弹性设置为0.8。由于没有外⼒作⽤,弹跳⾼度会越来越低。搭建场景做⼀个简单的实验,通过实验可得:默认物理帧率为50帧时,球会弹跳8次;物理帧率降低为20帧时,⼩球弹跳9次;物理帧率升⾼到100帧时,⼩球弹跳6次;⽽当物理帧率降低到10帧以下时,⼩球会穿过地板。

        事实证明,物理更新的时间间隔会极⼤影响物理效果的正确性,那么为什么不把默认的50帧变得更⼤⼀些呢?这是因为物理更新次数越⾼,硬件的计算负担就越重。引擎设计师不得不在性能和正确性上做出取舍,默认50帧是实验验证过的最合适的选择。
        除此之外,物理更新不仅要保证频率⾼,还要保证频率稳。不稳定的频率⼀样会带来糟糕的效果,因此所有的物理系统处理都在引擎循环中的⼀个专门环节上完成。有读者可能会深想⼀步:如果机器硬件确实卡顿了,例如⼿机或计算机正处于繁忙、⽆响应的状态,物理更新还能保证更新频率吗?答案是有办法间接保证这⼀点。
        简单来说,游戏世界的时间是⼀个虚拟的概念,⼀定程度上可以⼈为控制。如果在某个时刻T,硬件卡顿了0.06秒,正好错过了3次FixedUpdate()的调⽤时机,那么在下⼀次有机会运⾏的时候,FixedUpdate()函数会补上之前错过的3次,连续执⾏4次,⽽且还会“假装”这4次的调⽤时间点分别是T+0.02s、T+0.04s、T+0.06s、T+0.08s。通过这样的机制,就能确保⽆论硬件运⾏是否稳定,游戏都能保证“稳定”的物理更新,避免出现奇怪的结果。作为对⽐,Update()函数则没有这个特性。

⼩技巧
解决刚体移动过快的问题为了避免游戏中⼦弹⻜⾏过快,错过了碰撞体或触发器,Unity的刚体具有⼀个“Collision Detection(碰撞检测⽅式)”选项,将默认的“Discrete(离散)”改为“Continuous(连续)”,就可以避免错误碰撞。它的原理⼤致是,⾼速⻜⾏的⼦弹的路径在空间中是⼀些离散的点,通过在这些路径点之间连线,检查连线是否碰撞到物体,就能知道⼦弹是否碰撞到物体。
        在第2章的游戏实例中,讲解过“跟随式摄像机”的制作⽅法。在当时的设计⾥,玩家⾓⾊是在Update()函数中移动的,摄像机也是在Update()函数或LateUpdate()函数中移动的。但是,如果玩家⾓⾊是⼀个通过对刚体施加⼒控制的⼩球,就可能会出现⼀些⼩问题。尝试⼀下会发现,如果⼩球是物理移动,⽽摄像机在Update()函数或LateUpdate()函数中移动,那么会导致屏幕有抖动的情况,画⾯不是很稳定,⼩球运动越快则抖动越明显。
        这是由于刚体因速度或受⼒⽽产⽣的运动,属于物理更新。⽽Update()函数和LateUpdate()函数不属于物理更新,这其中有着微妙的时间差。要解决这个问题并不难,针对物理移动的刚体,只要将跟随摄像机的移动也编写到FixedUpdate()⾥,抖动的问题就会消失了。

3.2.9 修改⾓速度

        与修改刚体速度类似,可以直接修改刚体的⾓速度让刚体旋转。以下代码可以让刚体在按下R键时旋转起来。

Void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
rigid.angularVelocity = new Vector3(0, 60, 0);
}
}

3.2.10 质⼼

        拼接⼀个简单的模型,使⽤物理系统调整它的重⼼就可以制作⼀个不倒翁,如图3-11所⽰。重⼼需要通过脚本,即在Start()函数中设置,其⽅法如下。

public class Tumbler : MonoBehaviour
{
Rigidbody rigid;
void Start () {
rigid = GetComponent<Rigidbody>();
// 设置centerOfMass就可以指定重⼼了(本地坐标系)
rigid.centerOfMass = new Vector3(0, -1, 0);
}
}

        游戏物体的重⼼不受真实世界的限制,不但可以设置在物体的任意位置,⽽且还可以超出物体本⾝的范围。对不倒翁来说重⼼越低就越稳定,因此甚⾄可以把重⼼设置在物体下⽅10⽶处。
        本节专门提到“质⼼”并不是为了讲解如何制作不倒翁,⽽是引出⼀个结论:对物体施加⼒时,施加⼒的位置不同,最终的效果也不同。例如,⽤⼿推桌⼦上的杯⼦,如果推杯⼦的下半部分,那么杯⼦会平移,如果推杯⼦的上半部分,杯⼦就可能会倒。
        严格来说,对⼀个不受任何⼒的物体(在Unity⾥就是去掉了重⼒,也不与其他物体接触的刚体),如果受⼒的⽅向通过了该物体的质⼼,物体就不会获得⾓速度。如果受⼒的⽅向错过了质⼼,那么物体就会有旋转的趋势。质⼼到受⼒线的距离越远,旋转的趋势就越强。

3.2.11 更多施加⼒的⽅式

        ⼒的位置⾮常重要,但是前⽂所讲的施加⼒的函数AddForce()并没有位置参数。可以猜想,函数AddForce()施加⼒时,就是从物体的质⼼位置施加的。如果要模拟更复杂的情况,让脚本给物体的不同位置施加⼒,可以使⽤以下函数。

void AddForceAtPosition(Vector3 force, Vector3 position);
void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode);

        AddForceAtPosition()函数的第1个参数force代表施加的⼒,⽤向量表⽰;第2个参数就是施加⼒的位置,以世界坐标表⽰(不是相对坐标,因此使⽤时可能需要转换坐标)。
        之前讲解函数AddForce()时,忽略了它的最后⼀个参数——ForceMode(⼒的模式),AddForceAtPostion()函数同样也有该参数。“⼒的模式”参数是⼀个枚举类型,定义如下。

public enum ForceMode
{
// 默认⽅式为持续施加⼒,符合⽜顿⼒学
Force = 0,
// 设置为瞬间爆发⼒,适合表现快速猛烈的⼒,例如爆炸
// ⼒的持续时间有区别,但仍然符合⽜顿⼒学
Impulse = 1,
// 瞬时改变刚体速度,不考虑物体质量
VelocityChange = 2,
// 直接改变加速度,不考虑物体质量
Acceleration = 5
}

        也就是说,施加⼒的时候,可以通过改变参数mode来让施加⼒的含义发⽣变化。以上4个枚举值中,前两种是⽐较常⽤的,第三种完全可以⽤直接修改刚体速度的velocity属性代替。

3.2.12 刚体约束

        对⼀个刚体来说,它的移动、旋转往往被物理系统所控制。物体会因物理因素的影响⽽移动、旋转和倒地等,但是很多时候并不需要它完全⾃由运动。例如⼀个推箱⼦的游戏,只需要箱⼦在地板上平移⽽不是转来转去;推动油桶的时候并不需要油桶⻜起来(y轴的值增⼤)或倒下(沿x轴或z轴旋转)。这时候就可以利⽤“Rigidbody Constraints(刚体约束)”解决这⼀问题。3D刚体的约束有6个选项,分别是冻结(锁定)沿x轴、y轴、z轴移动和冻结(锁定)沿x轴、y轴和z轴的旋转,如图3-12所⽰。根据需要锁定⼀些⾃由度,可以让刚体的⾏为更可控。

冻结刚体的位移和旋转⾃由度,影响着因物理原因⽽产⽣的移动,主要包含以下⼏种情况。
⼀是受重⼒影响。
⼆是被其他物体推动或撞击。
三是脚本施加的⼒改变了物体速度。
四是脚本修改了刚体速度或⾓速度。
以上情况均受冻结的影响,⽽直接修改Transform的位置和朝向,则不受刚体约束的限制。
除了在编辑器界⾯上修改刚体约束,也可以在脚本中随时修改,其⽅法如下。

// 冻结所有的缩放和旋转
rigid.constraints = RigidbodyConstraints.FreezeAll;
// 仅冻结沿x轴的位移,取消所有其他约束
rigid.constraints = RigidbodyConstraints.FreezePositionX;
// 仅冻结所有旋转,取消位移约束
rigid.constraints = RigidbodyConstraints.FreezeRotation;
// 冻结沿x轴和z轴的旋转,冻结沿y轴的位移
rigid.constraints = RigidbodyConstraints.FreezeRotationX
| RigidbodyConstraints.FreezeRotationZ
| RigidbodyConstraints.FreezePositionY;

        由于刚体约束⽤⼀个整数代表多种状态,因此指定多个状态时需要⽤到⼆进制运算,特别是“按位或”运算符(符号为| )。

3.3 实例:基于物理系统的2D平台闯关游戏

        本节运⽤前⽂所讲解的有关物理系统的知识做⼀个2D平台闯关游戏,如图3-13所⽰。这个游戏画⾯⽐较简单,但是可以⽤它学习和使⽤多种物理技巧,最终做出有特⾊的游戏关卡。

3.3.1 游戏设计

        此游戏是⼀个平台跳跃类的闯关游戏,玩家控制图中的机器⼈使⽤移动和跳跃的⽅式通过搭设好的关卡,⽽关卡的各种机制会尽量使⽤Unity的物理系统来实现。创建⼀个2D模板的新项⽬,此实例使⽤的Unity的版本为2019.2.13f1。接着打开“Asset Store(资源商城)”并搜索Standard Assets,下载并导⼊⼯程,如图3-14所⽰。

StandardAssets中含有⼀些常⻅游戏类型的模板,这⾥找到CharacterRobotBoy,⽂件夹为Standard Assets/2D/Prefabs,将它作为主⾓素材,如图3-15所⽰。

猜你喜欢

转载自blog.csdn.net/qq_43416206/article/details/135097120