在2D物理引擎中实现包裹线(例如Worms Ninja Rope)


34

我最近一直在尝试一些绳索物理方法,但发现“标准”解决方案-用一系列由弹簧或关节串在一起的物体制成绳索是不令人满意的。尤其是当绳索摆动与游戏玩法相关时。我并不真正在乎绳索的缠绕或下垂能力(无论如何,这对于视觉效果来说都是伪造的)。

对于游戏而言,重要的是绳索能够环绕环境然后展开。它甚至不必表现得像绳索一样-由直线段组成的“线”就可以了。这是一个例子:

忍者绳,缠绕在障碍物上

这与蠕虫游戏中的“忍者绳”非常相似。

因为我使用的是2D物理引擎,所以我的环境由2D凸多边形组成。(特别是我在Farseer中使用SAT。)

所以我的问题是:您将如何实现“包装”效果?

似乎很明显,导线将由“分裂”和“接合”的一系列线段组成。移动对象所附着的那条线的最后(活动)线段将是固定长度的关节。

但是,确定活动线段何时和何处需要拆分所涉及的数学/算法是什么?何时需要将其与上一个细分市场合并?

(以前,这个问题也曾询问过如何在动态环境中执行此操作-我决定将其分解为其他问题。)

Answers:


18

要确定何时拆分绳索,必须查看绳索覆盖每一帧的区域。您要做的是对覆盖区域和关卡几何图形进行碰撞检查。秋千覆盖的区域应为弧形。如果发生碰撞,则需要对绳索进行新的分割。检查与摆动弧相撞的角。如果有多个拐角与摆动弧线发生碰撞,则应选择前一帧中绳索与碰撞点之间的角度最小的那个角。

忍者绳情况的有用图

进行碰撞检测的方法是,对于当前绳索段的根O,上一帧中绳索的末端位置A,当前帧上绳索的末端位置B,以及多边形中每个角点P级别的几何,您可以计算(OA x OP),(OP x OB)和(OA x OB),其中“ x”表示取两个向量之间叉积的Z坐标。如果所有三个结果的负号或正号相同,并且OP的长度小于绳段的长度,则点P在秋千覆盖的区域内,应将绳分开。如果您有多个碰撞角点,则需要使用第一个碰到绳索的角点,这意味着OA和OP之间的角度最小。使用点积确定角度。

至于连接线段,请在上一个线段和当前线段的弧之间进行比较。如果当前段已从左侧向右侧摆动,反之亦然,则应加入这些段。

对于连接线段的数学运算,我们将使用上一个绳索线段的连接点Q以及拆分情况下的连接点。所以现在,您将要比较向量QO,OA和OB。如果(QO x OA)的符号与(QO x OB)的符号不同,则绳索从左到右交叉,反之亦然,因此您应该将线段连接起来。当然,如果绳索摆动180度,也会发生这种情况,因此,如果希望绳索能够缠绕空间中的单个点而不是多边形,则可能需要为此添加特殊情况。

当然,此方法确实假设您对悬挂在绳索上的对象进行碰撞检测,以使它不会落在关卡几何体内部。


1
这种方法的问题是浮点精度误差使绳索有可能“通过”一个点。
安德鲁·罗素

16

自从我玩蠕虫游戏已经有一段时间了,但是根据我的记忆,当绳索缠绕事物时,绳索在任何时候都只有一根(直的)部分在运动。其余的绳子变成静止的

因此,涉及的实际物理学很少。活动部分可以建模为单个刚性弹簧,末端带有质量块

有趣的是将绳索的非活动部分与活动部分分开/接合的逻辑。

我猜想会有2个主要操作:

'裂开'-绳索已相交。在相交处将其拆分为非活动部分和新的较短的活动部分

“连接”-活动的绳索已移至不再存在最近的交叉点的位置(这可能只是简单的角度/点积测试?)。重新加入2个部分,创建一个新的,更长的活动部分

在由2D多边形构成的场景中,所有分割点都应位于碰撞网格的顶点上。碰撞检测可以简化为“如果在此更新中绳索越过顶点,则在该顶点处分裂/接合绳索?


2
这个家伙当场就对了...实际上,这不是“僵硬”的弹簧,您只能绕着一些直线旋转...
speeder

您的答案在技术上是正确的。但是我有点假设拥有线段并进行分割和合并是显而易见的。我对实际的算法/数学感兴趣。我的问题更具体了。
安德鲁·罗素

3

查看如何在Gusanos中使用忍者绳子:

  • 绳索就像颗粒一样,直到附着在某物上为止。
  • 一旦连接,绳子就向蠕虫施加力。
  • 附加到动态对象(如其他蠕虫)仍然是TODO:在此代码中。
  • 我想不起来是否支持环绕对象/角...

ninjarope.cpp的相关摘录:


void NinjaRope::think()
{
    if ( m_length > game.options.ninja_rope_maxLength )
        m_length = game.options.ninja_rope_maxLength;

    if (!active)
        return;

    if ( justCreated && m_type->creation )
    {
        m_type->creation->run(this);
        justCreated = false;
    }

    for ( int i = 0; !deleteMe && i < m_type->repeat; ++i)
    {
        pos += spd;

        BaseVec<long> ipos(pos);

        // TODO: Try to attach to worms/objects

        Vec diff(m_worm->pos, pos);
        float curLen = diff.length();
        Vec force(diff * game.options.ninja_rope_pullForce);

        if(!game.level.getMaterial( ipos.x, ipos.y ).particle_pass)
        {
            if(!attached)
            {
                m_length = 450.f / 16.f - 1.0f;
                attached = true;
                spd.zero();
                if ( m_type->groundCollision  )
                    m_type->groundCollision->run(this);
            }
        }
        else
            attached = false;

        if(attached)
        {
            if(curLen > m_length)
            {
                m_worm->addSpeed(force / curLen);
            }
        }
        else
        {
            spd.y += m_type->gravity;

            if(curLen > m_length)
            {
                spd -= force / curLen;
            }
        }
    }
}

1
嗯...这似乎根本无法回答我的问题。我的问题的全部要点是将绳索缠绕在由多边形组成的世界中。Gusanos似乎没有包装和位图世界。
安德鲁·罗素

1

恐怕我无法给您具体的算法,但是我发现只有两件事对检测忍者绳子的碰撞很重要:障碍物上的任何和所有可能碰撞的顶点在最后一个“分割”的半径内,等于该段的剩余长度;以及当前的摆动方向(顺时针或逆时针)。如果您创建了一个从“分割”顶点到每个附近顶点的角度的临时列表,则算法只需要关心段是否要针对任何给定顶点摆动超过该角度。如果是这样,则您需要执行拆分操作,就像做馅饼一样容易-它只是从最后一个拆分顶点到新拆分的一条线,然后计算出新的余数。

我认为只有顶点才重要。如果您有可能撞到障碍物的顶点之间的线段,则应该对挂在绳子末端的家伙进行正常的碰撞检测。换句话说,您的绳子只会“绊”在无论如何,都不会出现弯角,因此之间的线段将无关紧要。

抱歉,我没有什么具体的建议,但是希望从概念上讲,您可以到达这个目标。:)


1

这是一个帖子,链接到有关类似类型的模拟的论文(在工程/学术环境而不是游戏中):https : //gamedev.stackexchange.com/a/10350/6398

对于这种“线”模拟,我已经尝试了至少两种不同的方法来进行碰撞检测和响应(如游戏中的Umihara Kawase所示);至少,我认为这是您所追求的—这类模拟似乎没有特定的术语,我只是倾向于将其称为“线”而不是“绳”,因为这似乎是大多数人认为“绳”是“粒子链”的代名词。而且,如果您希望忍者绳子保持粘性(即它可以推动和拉动),则它更像是一根刚性钢丝,而不是绳索。无论如何..

Pekuja的答案很好,您可以通过求解三个点的有符号区域为0的时间来实现连续碰撞检测。

(我无法完全回忆起OTOH,但是您可以按以下方法进行处理:找到点a包含在通过b,c的直线中的时间t((我想我是通过求解何时dot(ab,cb)= 0以找到t的值,然后给定有效时间0 <= t <1,找到段bc上a的参数位置s,即a =(1-s)b + s c,如果a在b和c(即0 <= s <= 1)表示有效碰撞。

AFAICR也可以采用另一种方法(即求解s,然后将其插入以找到t),但是它的直观性要差很多。(很抱歉,如果这没有任何意义,我没有时间去整理笔记,已经过去了几年!)

因此,您现在可以计算事件发生的所有时间(即应插入或删除绳索节点);处理最早的事件(插入或删除节点),然后重复/递归直到t = 0和t = 1之间没有其他事件。

有关此方法的一个警告:如果绳索可以缠绕的对象是动态的(尤其是模拟它们及其对绳索的影响,反之亦然),那么如果这些对象夹住/穿过每个对象,则可能会出现问题。其他-电线可能会缠结。在box2d样式的物理模拟中,要防止这种交互作用/运动(对象的角相互滑过),绝对是具有挑战性的。在这种情况下,对象之间的少量渗透是正常行为。

(至少..这是我的“ wire”实现之一的问题。)

另一种解决方案更稳定,但在某些情况下会错过一些碰撞,它只是使用静态测试(即,不必担心按时间排序,只需在发现碰撞时递归细分每个碰撞段即可),可以坚固得多-导线不会在角落缠结,并且少量的穿透就可以了。

我认为Pekuja的方法也适用于此,但是还有其他方法。我使用的一种方法是添加辅助碰撞数据:在世界上的每个凸顶点v(即绳索可以缠绕的形状的角)处,添加一个点u形成有向线段uv,其中u是一些点“在角落内”(即,在世界内部,在“ v”之后;要计算u,您可以沿v的内插法线从v向内投射光线,并在v之后或在光线与世界的边缘相交之前停止一段距离,退出实心区域。或者,您也可以使用可视工具/关卡编辑器手动将线段绘制到世界中。

无论如何,您现在有了一组“角线段” uv;对于导线中的每个uv和每个段ab,检查ab和uv是否相交(即静态,布尔线段-线段交点查询);如果是这样,则递归(将线段ab分成av和vb,即插入v),记录绳索在v处弯曲的方向。然后对于导线中的每对相邻线段ab,bc,测试当前的弯曲方向是否在b处与生成b时相同(所有这些“弯曲方向”测试都只是有符号区域测试);如果不是,则将两个段合并为ac(即删除b)。

或者,也许我合并然后拆分了,但我忘了-但它确实可以至少在两种可能的顺序之一中起作用!:)

给定当前帧计算的所有线段,然后可以模拟两个线端点之间的距离约束(甚至可以包含内部点,即线与世界之间的接触点,但这要涉及更多点)。

无论如何,希望这会有所帮助...我链接的帖子中的论文也应该给您一些想法。


0

一种解决方法是将绳索建模为通过弹簧连接的可碰撞粒子。(相当僵硬的,甚至可能像骨头一样)。颗粒与环境碰撞,确保绳索缠绕物品。

这是一个带有源的演示:http : //www.ewjordan.com/rgbDemo/

(在第一层上移到右边,有一条红色的绳子可以与之互动)


2
嗯-这是我特别想要的(请参阅问题)。
安德鲁·罗素

啊。从最初的问题并不清楚。感谢您抽出宝贵的时间对此进行澄清。(很棒的图!)我仍然会使用一系列非常小的固定关节,而不是进行动态拆分-除非您的环境中存在巨大的性能问题,否则编写代码会容易得多。
Rachel Blum
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.