深入探讨Box2D中ghost collision问题解决方案

在使用Box2D引擎时,我们必须牢记它只能对物理世界进行近似的仿真。这其中最根本的原因是帧速受限,而且在pix<->meter换算以及其它乘除法的约算上,会衍生出大量的边界问题。在box2d官方的FAQ页上可以看到这样一条提示:What are the biggest mistakes made by new users?  Expecting Box2D to give pixel perfect results.  这句话应当引起我们相当的注意。

对于“高速刚体穿越碰撞块”以及本文将讨论的“ghost collision”问题,目前还没有一个100%完美的解决方案。有时我真为这种bug感到抓狂,但是细想一下,如果要我自己去设计这样一套引擎,未必能做得更好。

ghost collision问题如下图所示:

A块和B块代表固定的地面,蓝色块代表正在移动中的人物。虽然我们把A块和B块放在了一个水平面上,但仍有相当的机率使得当Hero在水平方向上移动时,卡在A、B块的交界处。一个典型的情境是:你在一个超级玛丽类的平台游戏中使用了贴片地图(TMX),在TMX中又设了一个地面层或是平台层之类的。你在场景初始化时遍历这个平台层的每一个Tile,对它赋一个polyshape的body,然后我们的玛丽奥大叔走着走着,就卡住不动了,仿佛面前有一堵隐形的墙一样。

究其根本原因,我们可以仔细思索一下“What are the biggest mistakes made by new users?  Expecting Box2D to give pixel perfect results. ”这句话中包含的信息量。事实上,box2d并不能保证在任何一帧中,Hero一定是处在AB水平面之上的。此时Hero与B的碰撞可以分为以下两种情形:

情形1                                          情形2

把hero与B的相交部分四边形高设为Δy, 宽设为Δx。如果如情形1所示,Δy < Δx,则碰撞后的应力向量是在y轴方向的,反之则在x轴上。我们可以想到,Δy始终都是在一个比较小值范围内波动的,但是 Δx的波动范围则直接与hero的移动速度相关。由于hero的移速一般是要比Δy大很多的,在一帧内所形成的Δx’也会是一个相对较大的值,因此大部分情况下我们遇到的是情形1。

为解决这个问题,有了这样一些解决方案:

1.将A、B拼合成一个polyshape

将A、B简单的拼合成一个polyshaper后,自然消除了Hero <-> B之间的碰撞。当然了,这里的拼合必然不是手动拼合,而是用代码实现的相邻块自动拼合。这样的做的麻烦在于:1.拼合算法本身要花点功夫。2.对于不规则图形束手无策。polyshape的默认的最大顶点数为8,不规则图形会轻易超出这个界限。

另一个问题是,拼合将消除A、B块自身的特性,使得他们在一次碰撞检测中变得难以分辨。例如:

对于这样一个情境,如果把所有5个tile拼合成一个polyshape,当我们的hero顶到了问号上或者是顶到了砖块上,将会触发同一个碰撞回调。这时想要区分到底是哪一个被撞到了,除了全局坐标外,几无它法。如果把polyshaper换成edge chain,同样无法解决内部区分这个问题。

2.消除过于分明的棱角

如果我们把hero原来的90度棱角全部做一个钝化处理,也能缓解完全卡住的现象。因为这样一来,至少不会出现一个完全是水平方向上的碰撞反馈。然而问题没这么简单,此方案副作用是我们的英雄在平地上走着走着,会莫名其妙地小跳一下,仿佛被小石头绊了一个踉跄似的。有人说把A、B的棱角消除后能解决此问题,我试过后仍觉无用。

3.使用Edge Shape作为每一个Tile的body形状

如果我们用上、下、左、右四个edge shape代替原来的那个polyshape,我们会发现这个问题会奇迹般的得到解决。网上大家都说使用edge shape可以在绝大多数情况下解决这个问题,但是还没有谁说得清楚这是为什么。其实在非常苛刻的情况下,edge shape同样会使hero完全卡住,这一点,可以参见iforce2d兄的一个演示视频http://www.youtube.com/watch?v=oP7ZLeyc9HU
如果想要用edge shape完美的解决卡顿,可以使用ghost vertices,这一点见iforce 2d的教程https://www.iforce2d.net/b2dtut/ghost-vertices

尽管edge shape 配合ghost vertices完美解决了人物的卡死,但这又连带出两个衍生问题:

1)多次碰撞

由于原本的1个shape现在由4个shape组成,而box2d中的碰撞是以fixture为单位而非body进行检测的,所以现在如果Hero从A的上表面完整的滑过去,会分别与左边、上边、右边进行三次碰撞回调。在某些情况下,我们会非常不愿意收到这种针对同一个body的重复碰撞回调。一个取巧的方法是把左边和右边的长度分别缩小几个小像点,如下图所示.

这样可以初步解决人物在仅在表面滑动时产生的多次碰撞。如果hero不仅仅是在表面滑动的话,这种削减左、右边的方法就不再适用,需要您根据具体情境来想办法了。

2)Hero被锁死在edge shape内部

在poly shape的方案中,由于polyshape是完全致密的,所以绝无可能在碰撞完毕后,hero被A块完全包络住。但是当我们采用了edge shape的方案后,由于中间部分完全是空心的,在一些特殊的情况下可能导致hero完全被锁死在A的内部空间中。这时,我们就需要开动聪明的脑瓜子,针对具体情况想点新办法了。

Over。

参考文献:

http://www.box2d.org/manual.html
https://www.iforce2d.net/b2dtut/ghost-vertices
http://www.cocos2d-iphone.org/forum/topic/31787

9 Responses

  1. 遇到过这个问题,解决办法是,上边沿向上加长一点(1像素),且左右长度拉长一点。左右边沿各向中间缩一点,但高度不变。就不会卡在某快tile中了。
    不太清楚为啥,自己瞎调的。
    在那个tube处如果直接从台子上冲下来会一下子冲进tube里(正常情况下不会有问题,会挡住),不明所以,于是我就很懒的在tube旁边放了一块砖头挡住了。
    嗯……

发表评论

电子邮件地址不会被公开。 必填项已用*标注