稳了!Cocos Creator 3.0 解决角色在爬坡时的抖动问题
App Annie 近日发布数据显示,5月中国休闲游戏总下载量达3072万,其中,跑酷/竞速、解谜/脑筋急转弯和 io 类游戏为下载量前三的品类;此外,跑酷/竞速类游戏还占据了美国 App Store 、安卓市场5月休闲游戏下载量品类榜第二位。可见,跑酷/竞速类游戏依然是休闲类游戏市场的“香饽饽”。
>>跑酷类游戏《长发我最美》等
带动女性题材跑酷游戏热度高涨
本篇文章将聚焦在跑酷类游戏中频繁出现的爬坡需求,剖析在爬坡时人物抖动的原因和解决方案。此次采用的是 bullet 物理引擎,由于不同物理引擎实现方式各不相同,所以还需要具体问题具体分析。
资源下载
本讲解案例 Demo 工程由 Cocos 引擎 Demo Team 官方出品,您可以将工程中的代码、模型资源用于自己的项目或二次商用。下载链接:
1.https://github.com/cocos-creator/parkour-character-control
2.http://store.cocos.com/app/detail/2989
>>最终效果 Demo
1
静态网格
当前讲解案例中的道路是由多个道路模型拼接而成(可参考下图),道路部分有上下起伏。为了让人物能“贴合”地面行走,通常会给人物增加刚体组件,让人物因为重力而落在地面上。
同时,还会在道路模型身上添加网格碰撞体,但此时不需要添加刚体,碰撞体默认自带静态刚体(一个不受物理效果影响的质量极大的刚体)。网格碰撞体会自动检测模型的网格信息,将网格信息记录到当前碰撞体身上,用来做之后的碰撞检测。
为整条道路添加完网格碰撞体之后,效果如下所示:
实际运行看看最终的效果如何(图中人物身上的绿色指示标代表的是碰撞法线,道路上的黑色线是碰撞网格信息):
观察图中的绿色人物可以看出,在其中一段平“面”上运动时,当运行到两个三角形交汇处,碰撞信息多了一份,这是因为 bullet 自动将这个平面识别为了两个“三角面”。
即使在我们的视角上这就是一个平面,就应该只返回一份碰撞信息,但是因为 bullet 将它分成两个三角面检测,因此返回了两个碰撞信息,导致最终人物在运动过程中出现偏移和抖动,图中白色的人物最为明显。
为了解决这样的问题,此时就需要取消静态网格,采用凸包。
在了解凸包之前,先了解一下什么是凸多边形。一个凸多边形从直观上说就是没有任何凹陷的多边形,例如:三角形、正方形等都是凸多边形。那么,下面这个“凸”字形,是不是凸多边形呢?
其实并不尽然,凸多边形在数学上有严格定义。假设在一个多边形上(包括多边形边界)取任意两个点并以一条线段连接,如果线段上的每一个点都在此多边形上,那么便说这个多边形是凸的。
在此“凸”字形上取两个点,如下图,连接 A、B 两点的线段,会发现有一部分并不在该多边形上,因此判断此“凸”字形不是凸边形。
了解了凸边形的原理之后,就可以很轻松地知道什么是凸包。凸包其实就是在平面上能包含所有给定点的最小凸多边形。实际应用中也可以理解为用一个橡皮筋包含住所有给定点所呈现的形态。
2
网格凸包
明白了什么是凸包,此处就可以用凸包来处理。凸包的原理是用“面”来解决,减少三角面,这样就不会出现抖动的现象。
因此,美术提供的模型资源需要进行拆分,将赛道细分成如下:
注意:此处之所以把路面和边缘也单独拆分,是因为边缘有一个凸起的部分,如果用凸包网格,这部分高度也会被计算进去,人物在跑道上就会出现“悬浮”现象。
此时,地面和赛道两边护栏以及坡道上方的平地都可以直接采用盒碰撞体。
只有坡道需要采用网格碰撞,同时勾选网格碰撞组件上的 convex 属性,将碰撞检测改用凸包形式。
最终坡道的网格变化如下:
运行看看效果:
此时,人物偏离的问题得到了解决,抖动问题也有了很大改善。但同时也新增了一个问题,就是每次在上坡和下坡的时候,人物都会跳一下。用一张图来说明问题发生原因:
从图中可以看出,现在的做法在平行道路和斜坡处容易产生一个很明显的接缝。这就好比骑着自行车要上天桥一样,突然有了一个折角,此时没有任何缓冲,自然是会突然震一下的。当然,细心的朋友也会发现,在这个过程中人物其实还有一段轻微的“悬浮”现象,同样也是这个原因导致的。
3
平滑运动
从运行效果来看,上述处理思路与方式都是正确的,因此,在明白了人物产生跳动和悬浮的原因之后,只需对症下药进行一些优化调整,避免出现任何的抖动现象。
由于此时人物是运行在物理检测的所谓地面,因此我们可以通过射线检测的方式检测出模型提供的、也就是人物真实应该所处的地面,然后将产生“悬浮”处的人物位置“往下压”就能解决这样的问题。接下来看看如何处理。
首先,为要进行射线检测的人物真实地面新建一个碰撞分组,这个分组只为了检测真实的地面位置所设。在项目设置 -> 物理配置处设置单独分组。
其次,原样拷贝一份模型的节点树结构(可以去除护栏部分的节点),这份模型节点树不需要渲染,只需要全部添加网格碰撞(无论是平路还是坡道都需要,这是因为 bullet 的算法模型决定了抖动不可避免,所以此处的做法尽可能的避免抖动),以及为刚体配置新建的物理分组。
然后,在运行时,根据射线检测的位置,重新更新人物位置即可(该脚本挂在人物身上):
1import { _decorator, Component, Node, director, Director, PhysicsSystem, geometry, Vec3, physics } from 'cc';
2const { ccclass, property } = _decorator;
3const tmpRay = geometry.Ray.create(0, 0, 0, 0, -1, 0);
4@ccclass('VerticalRaycast')
5export class VerticalRaycast extends Component {
6
7 @property({ type: physics.PhysicsGroup })
8 group: physics.PhysicsGroup = 0;
9
10 start() {
11 director.on(Director.EVENT_AFTER_PHYSICS, this.raycast, this);
12 }
13
14 raycast() {
15 const pos = this.node.worldPosition;
16 let y = pos.y;
17 Vec3.copy(tmpRay.o, pos);
18 tmpRay.o.y += 1;
19 if (PhysicsSystem.instance.raycastClosest(tmpRay, this.group, 1.1)) {
20 y = PhysicsSystem.instance.raycastClosestResult.hitPoint.y
21 }
22 tmpRay.o.y = y;
23 this.node.worldPosition = tmpRay.o;
24 }
25}
总结
当用物理引擎 bullet 实现角色爬坡,通常会将跑道分为多个部分,并且采用网格碰撞体进行碰撞检测。由于 bullet 物理引擎实现会对一个平面(有两个三角形网格)单独检测,导致在三角面接触的地方出现两份碰撞数据导致人物偏航甚至抖动。此时,可以采用的解决方案就是将网格碰撞改成凸包检测模式。
采用凸包检测就要求模型在资源层面就是分离的,比如:原本一个 mesh 包含左边上坡、右边平地,此时就需要单独拆分成一个 mesh 上坡,一个 mesh 平地。如果上坡路面边上还有护栏,护栏高出路面,此时护栏和道路再进行一次拆分。接着,坡道路面采用网格碰撞并启用凸包检测,平路采用盒碰撞。
最后,再建立一组实际运行路面的网格碰撞组用作射线检测,将由于凸包检测导致的“悬浮”的人物位置在 y 轴上“压”回到正确的位置。具体的节点树组织如下图:
以上就是关于 Cocos Creator 3.0 角色爬坡抖动问题的解决方案,如果有疑问或是有不同的解决方案,欢迎在评论区给我们留言,也可以登录 Cocos 官方论坛一起交流。