Cocos Creator 3.0 3D 物理讲解
为更好地帮助大家丝滑升级,减少开发阻碍,我们带来了「Cocos Creator 3.0 技术专栏」第三期,为大家详细讲解 Cocos Creator 3.0 3D 物理。
在此也统一回复下大家比较关心的问题:
本专栏将不定期保持更新,包括 2.X 升级攻略等,尽力帮大家降低升级成本。
关于文档的问题我们一直在持续更新中,有任何问题也可以随时反馈给我们。
今天我们将通过一些概念和案例,来为大家介绍如何使用 Creator 的 3D 物理框架,以便未进行过物理开发或者不熟悉该框架的开发者们可以轻松上手。
Creator 的 3D 物理是一套全新的开发框架,它的目标是使物理开发在兼具性能的同时变得更加简单。
以下内容主要讲解其中使用到的功能和技术原理,篇幅有限无法涉及到所有的细枝末节,但大部分内容在引擎文档均有介绍,可以配合阅读。
01
选择引擎
在 Creator 中进行 3D 物理类应用的开发,第一步就是选择物理引擎,目前提供了三种选择,分别为 builtin
\cannon.js
\bullet(ammo.js)
,它们之间主要的区别是包体、性能和功能特性。
物理引擎 | 特点 |
---|---|
builtin | 极其轻量的碰撞检测系统,仅支持盒、球、胶囊体形状和触发事件 |
cannon.js | 纯 js 开发的物理引擎,支持大部分特性,易于扩展,但性能不够好,包体约为 141 KB |
ammo.js | 由 bullet 物理引擎编译而来,支持所有特性,性能最优,但包体较大且不易扩展,其中 js 版本的包体约为 1.32 MB,wasm 版本的包体约为 690 KB |
其中主要的功能特性支持情况如下:
功能特性 | builtin | cannon.js | ammo.js |
---|---|---|---|
质心 | ✔ | ✔ | ✔ |
盒、球 | ✔ | ✔ | ✔ |
胶囊 | ✔ | 可以用基础形状拼凑 | ✔ |
凸包 | ✔ | ||
静态地形、静态平面 | ✔ | ✔ | |
静态网格 | 极其有限的支持 | ✔ | |
圆锥、圆柱 | ✔(凸包实现) | ✔ | |
单纯形 | 有限的支持 | ✔ | |
复合形状 | ✔ | ✔ | ✔ |
射线检测、掩码过滤 | ✔ | ✔ | ✔ |
多步模拟、碰撞矩阵 | ✔ | ✔ | ✔ |
触发事件 | ✔ | ✔ | ✔ |
自动休眠 | ✔ | ✔ | |
碰撞事件、碰撞数据 | ✔ | ✔ | |
物理材质 | ✔ | ✔ | |
静态、运动学 | ✔ | ✔ | ✔ |
动力学 | ✔ | ✔ | |
点对点、铰链约束(实验) | ✔ | ✔ | |
wasm | ✔ |
02
概念讲解
以下向大家介绍了物理模块比较重要的组件和功能。
Static / Kinematic / Dynamic
刚体类型 Static 静态刚体,可用于描述静止的建筑物,只与 Dynamic 和 Kinematic 类型的物体产生事件 Dynamic 动力学刚体,能够受到力的作用,物理引擎会接管该物体的运动,通过物理层的数值来操控 Kinematic 运动学刚体,通常用于表达电梯这类平台运动的物体,通过修改变换信息来操控 下图中白色为静态,蓝色为运动学,黄色为动力学。其中白色和蓝色都是操控的变换信息,很明显的看出几个表现
白色和蓝色之间会出现穿透现象 白色的静态物体也可以运动 两个黄色方块表现不同,白色上方的静止不动,蓝色上方的会跟随着运动
以上现象的原因是:
静态和运动学都不会受到力的作用,所以产生了穿透,这是正常现象 静态物体的确是可以运动的,静态是指在时空中,每一个时刻都是静态,不会考虑其它时刻的状态 与静态物体不同,运动学物体会根据附近时刻估算出运动状态(比如速度),又由于摩擦力的作用,因此带动了黄色方块 RigidBody / Collider / Trigger
组件类型 刚体组件(RigidBody) 它负责操控物理对象与运动相关的属性,以及配置分组,一个节点最多一个 碰撞器组件(Collider) 它负责操控物理对象上形状相关的属性,一个节点可以有多个 触发器(Trigger) 它是勾选上了 IsTrigger
的碰撞器组件,它会像幽灵一样穿透其它物体,并提供穿透开始、保持,以及结束的事件是否一定要加刚体组件是提问最多的,这里介绍一个小技巧:
需要配置分组,加 需要设置为运动学或者动力学类型(不加意味着是静态类型),加 Raycast / Mask Filter / Collision Matrix
功能模块 射线检测(Raycast) 利用射线与其它碰撞体进行碰撞检测,并记录相应的碰撞数据 掩码过滤(Mask Filter) 利用位运算实现两个物体间是否相互过滤,重要的碰撞检测优化技术 碰撞矩阵(Collision Matrix) 它用于初始化物体的分组和掩码,是掩码过滤的上层封装
这套框架还有很多需要打磨的地方,例如有些开发者认为在静态对象上配置分组不够便捷、分组不应该和节点的层剥离开、射线不应该有组。
这些我们都记录下来了,同时也在寻找更友好的方式。在这里感谢开发者们宝贵的反馈建议。
03
使用讲解
以下将会结合一个射箭范例中的几个问题场景,介绍对应使用到的功能特性和解题思路,以及涉及到的易误点。
用基础形状组合十字架——复合形状
下图中用两个盒形状组合一个十字架,节点上所有的碰撞体组合成了一个十字形状,这是实现带有凹面形状最基础的方法:
容易误用的地方是在多个节点中添加碰撞体拼凑出十字架后,希望它碰撞后可以保持整体结构进行运动,这在目前的结构中是无法做到的,只能往单个节点上添加碰撞体来实现。
射箭与回收箭——运动学、动力学、事件
射箭的第一步是拉弓,箭需要完全跟随弹性绳骨骼一起运动,不希望箭受到物理规则的影响,此时应将箭的刚体设置为 Kinematic
类型;第二步是松开弹性绳发射箭,这时希望给箭设置初速度后,可以按照物理规则进行运动,因此将箭的刚体设置为 Dynamic
类型
回收箭的大致过程是在箭射出去后,一旦触碰到触发区域就将其还原到弓上。这可以通过制作监听区域来实现,首先利用碰撞体组件拼凑出区域,同时将碰撞体组件的 IsTrigger
勾选上。(下图中的蓝色地板为监听区域)
易误点:
利用修改变换信息来操作动力学( Dynamic
)类型的刚体,应当通过速度、力或冲量等物理层的数值利用静态( Static
)类型的刚体来监听事件,静态刚体只会和带有类型为运动学或动力学的刚体产生事件,可以更改为运动学(Kinematic
)类型,或者事件通过另一方进行注册监听事件时仅监听了触发开始( OnTriggerEnter
),但是误以为包括了触发保持(OnTriggerStay
)和结束(OnTriggerExit
)
瞄准——碰撞矩阵(过滤检测)、射线检测、静态平面
瞄准是射箭前的步骤,准心处于箭头指向所在的射线上,在十字架前面加一个静态平面碰撞体,然后利用射线检测就可以得到准心的位置;
静态平面只是用来做射线检测,给它专门建立一个分组,并且不与箭、苹果等等物体进行检测,这是最通用的性能优化方法
调用射线检测方法时,设置传入掩码为仅和静态平面检测,即 0b10
(二进制表示法)。
易误点:
分不清刚体组件上的分组和节点上的层。这两者的概念类似,但是使用者不同,分组的使用者是物理模块,层的使用者是渲染模块 对掩码的理解不到位,不知传何值。这里提供一个小技巧,以一个能够筛选出 Others
的掩码举例,首先Others
的index
值为2
,哪么只要让二进制掩码从右往左的顺序第2
位为1
,就能让Others
通过筛选,也就是ob100
(这里强烈建议不要随便更改分组的索引)误认为射线检测接口的返回值是击中的数据。获取结果有专门的接口,此处设计是为了强调这是个复用对象。为了减少垃圾内存,每次调用接口只会更新它们的数据,而不是重新生成新的(若需要持久记录,哪么可以克隆一份)
射击苹果——静态网格、凸包、多步模拟(步长调整)
一般的苹果都带有凹面,处理好凹类或带连续平滑不规则曲面的模型都非常棘手,这是因为目前成熟的理论和技术都建立在离散、凸包的世界之上(微积分中用差分近似表示微分就是最典型的范例)。
在实时物理引擎中,对于这类物体只能支持到静态或运动学类型的刚体层级,对于动力学就束手无策了。然而不幸的是,真实的苹果运动表现强烈依赖动力学,这种情况只能给苹果填加凸包形式的网格碰撞体(需将 convex
勾选上),再加上一个动力学刚体,用近似物体去参与模拟
运动表现与模拟参数有非常大的关系,穿透是最具有代表性的现象,这可以通过缩减步长和增加步数来实现,调整步长有个小技巧:输入分式,即 1/Frame
,其中 Frame
表示帧率
易误点:
在带有未勾选
convex
的网格碰撞器上添加了的动力学刚体,与其它物体产生了穿透现象,或者说完全没有反应,这是典型的错误使用,只有勾上convex
的才能支持动力学刚体对一个顶点数极多的模型开启了
convex
,过多的顶点数会使凸包的面数增多,这对性能有很大的影响,而且实际上并不需要面数特别多的凸包,一般建议模型的顶点数应小于255
开启凸包后,模型的凹面处的接触不贴近,这是正常现象,现在的实时技术是将模型用多个凸包组合来解决,如下图所示
4. 只调整了步长,但未调整步数,这两者需要相互配合才有效果。小技巧是,步数可以随意设置较大的值,步长根据最大的速度值进行调整,值越大,步长应当越小
04
运动控制
以下将介绍如何利用碰撞数据实现一个动力学类型刚体的运动控制。
碰撞数据
如下图所示,红色的球表示的是碰撞点的位置,蓝色箭头指的是碰撞点所处面的法线。碰撞数据需要通过碰撞事件来获取(事件提供的碰撞数据是复用对象)。
地面判断
角色是否在地面上,可以通过法线的指向来得知,对应的条件为normal.y > 0
。有时候还需要知道地面的倾斜情况,这里通过normal.y
的大小就能知道,越接近于1
,哪么地面就越平(法线垂直向上);越接近于0
,哪么地面就越陡峭。
跳跃
一旦能够确定是否在地面后,就可以加上跳跃行为了。首先需要明确的一点是,这里使用的是动力学类型的刚体(原因是我希望通过物理引擎接管刚体后得到更加真实的物理反馈),所以需要通过改变物理数值来达到目的,例如直接将角色线性速度的y
分量设置为5
,如下图(图中碰撞体用的是胶囊体,这与实际场景相关,可自行更改)。
直接改y
轴速度,可以良好的工作在完全水平的地面上。但有时候跳跃还需要根据地面斜度来做出不同的行为,这里提供一个思路:提供一个配置因子,范围为0到1,根据因子将当前法线和垂直法线插值,得出目标法线,再乘以跳跃的速率,进而得出期望的跳跃速度,将其加入到角色的线性速度之中。
行走和站立
站立行为,如果希望角色不会摔倒,哪么直接将AngularFactor
全设置为0
即可。行走行为,可根据角色的运动状态来实现,假设需要将角色往x
轴运动,哪么就修改角色x
轴的线性速度。因此控制行走,只需要两个量,一个是运动方向,另一个是运动速率。
做出好的运动控制的唯一窍门就是控制好速度,但这也是最容易错误使用的点:
随意设置速度,这是运动不真实最根本的原因 设置过大的速度,这将很容易出现穿透现象,可以尝试上文介绍的多步模拟技术
没有掌握控制速度的方式,有些开发者直接修改为某个固定值大小,这当然不是错误的做法,但修改速度之前应当考虑清楚这是不是想要的结果。
有个小技巧是,如果是跳跃行为,可以考虑直接设置为固定值;
如果是行走行为,更好的方式是根据上个时刻的状态进行修改
使用了
Kinematic
类型的刚体,期望物理引擎会帮助你处理好碰撞行为,通过上文可以知道这是不可能的。但实际上有很多好的角色控制都基于
Kinematic
进行实现,这里面的技术细节远比本文介绍的复杂。
05
结语
以上大部分的内容案例均可在案例仓库中找到,比如上面的截图中用来辅助显示碰撞点信息的组件,其他内容比如弓箭、运动控制的案例也会持续合并进去,请感兴趣的同学持续关注。
相信大家看到上面糖豆人类的物理实现展示也很激动,3.0 引擎目前已经完全可以支撑同类型游戏的开发,大家可以按照这篇文章中的指引和参考我们的范例多多尝试。
未来在物理引擎上还有很广阔的探索空间,目前在计划中的包括:
原生平台 PhysX 物理引擎接入,将大大加强原生平台的物理性能 更多物理约束类型支持 布娃娃系统 物理驱动的动画系统 持续碰撞检测(CCD) 节点链组合
后续本专栏将不定期更新,为大家带来包括但不限于以下内容:
《Cocos Creator 3.0 里如何玩转 npm 海量资源》by 放空
《Cocos Creator 3.0 的资源系统》by Santy Wang
......
留言告诉我们其他你想看的内容,我们也会持续加强物理引擎的能力,帮助开发者做出超好玩的物理反馈游戏。
最后,欢迎对物理引擎开发有兴趣的同学将简历甩进我们 HR 的邮箱中!
邮箱地址:hr@cocos.com。
往期精彩