2D平台游戏的碰撞处理

5

我正在尝试创建一个2D平台游戏(类似马里奥),但在处理碰撞时遇到了一些问题。我使用C++编写此游戏,使用SDL进行输入、图像加载、字体加载等操作。我还使用FreeGLUT库结合SDL使用OpenGL显示图形。

我的碰撞检测方法是AABB (轴对齐边界框),这基本上就是我需要的。我需要一种简单的方法来检测发生碰撞的那一侧,并正确地处理碰撞。所以,如果玩家与平台顶部发生碰撞,则将其重新定位到顶部;如果有侧面碰撞,则将玩家重新定位回对象的侧面;如果底部发生碰撞,则将玩家重新定位到平台下方。

我尝试过很多不同的方法,例如尝试找到渗透深度并向后重新定位玩家。可惜的是,我尝试的所有方法都不能正常工作。玩家的移动变得非常卡顿,而且会在我不想要的时候重新定位玩家。可能部分原因是因为我觉得这是一件非常简单的事情,但我却想复杂了。

如果有人认为他们可以帮助,请查看下面的代码,并在可能的情况下帮我改进。如果可能,我希望避免使用库来处理此问题(因为我想自己学习),或者像SAT(分离轴定理)这样的东西。提前感谢你的帮助!

void world1Level1CollisionDetection()
{
for(int i; i < blocks; i++)
{
    if (de2dCheckCollision(ball,block[i],0.0f,0.0f)==true)
    {
        de2dObj ballPrev;
        ballPrev.coords[0] = ball.coords[0];
        ballPrev.coords[1] = ball.coords[1];
        ballPrev.coords[2] = ball.coords[2];
        ballPrev.coords[3] = ball.coords[3];
        ballPrev.coords[0] -= ball.xspeed;
        ballPrev.coords[1] -= ball.yspeed;
        ballPrev.coords[2] -= ball.xspeed;
        ballPrev.coords[3] -= ball.yspeed;

        int up = 0;
        int left = 0;
        int right = 0;
        int down = 0;

        if (ballPrev.coords[0] < block[i].coords[0] && ballPrev.coords[2] < block[i].coords[0] && (((ball.coords[1] < block[i].coords[1]) || (ball.coords[3] < ball.coords[1]))  || ((ball.coords[1] < block[i].coords[3]) || ball.coords[3] < block[i].coords[3])))
        {
            left = 1;
        }

        if (ballPrev.coords[0] > block[i].coords[2] && ballPrev.coords[2] > block[i].coords[2] && (((ball.coords[1] < block[i].coords[1]) || (ball.coords[3] < ball.coords[1]))  || ((ball.coords[1] < block[i].coords[3]) || (ball.coords[3] < block[i].coords[3]))))
        {
            right = 1;
        }
        if(ballPrev.coords[1] < block[i].coords[1] && block[i].coords[1] < ballPrev.coords[3] && ballPrev.coords[3] < block[i].coords[3])
        {
            up = 1;
        }
        if(block[i].coords[1] < ballPrev.coords[1] && ballPrev.coords[1] < block[i].coords[3] && block[i].coords[3] < ballPrev.coords[3])
        {
            down = 1;
        }

        cout << left << ", " << right << ", " << up << ", " << down << ", " << endl;

        if (left == 1)
        {
            ball.coords[0] = block[i].coords[0] - 18.0f;
            ball.coords[2] = block[i].coords[0] - 2.0f;
        }
        else if (right == 1)
        {
            ball.coords[0] = block[i].coords[2] + 2.0f;
            ball.coords[2] = block[i].coords[2] + 18.0f;
        }
        else if (down == 1)
        {
            ball.coords[1] = block[i].coords[3] + 4.0f;
            ball.coords[3] = block[i].coords[3] + 20.0f;
        }
        else if (up == 1)
        {
            ball.yspeed = 0.0f;
            ball.gravity = 0.0f;
            ball.coords[1] = block[i].coords[1] - 17.0f;
            ball.coords[3] = block[i].coords[1] - 1.0f;
        }
    }
    if (de2dCheckCollision(ball,block[i],0.0f,0.0f)==false)
    {
        ball.gravity = -0.5f;
    }
}
}

为了解释这段代码的含义:
变量blocks基本上是存储块或平台数量的整数。我使用for循环检查所有块,循环当前正在进行的数字由整数i表示。 坐标系可能看起来有点奇怪,因此值得解释。 coords [0]表示对象的x位置(左侧)(在x轴上开始的位置)。 coords [1]表示对象的y位置(顶部)(在y轴上开始的位置)。 coords [2]表示对象的宽度加上coords [0](右侧)。 coords [3]表示对象的高度加上coords [1](底部)。 de2dCheckCollision执行AABB碰撞检测。 向上为负y,向下为正y,这在大多数游戏中都是如此。
希望我提供的信息足够帮助你成功。如果我漏掉了什么可能至关重要的内容,请让我知道,我会提供必要的信息。最后,对于任何可以帮助的人,提供代码将非常有帮助并且非常感激。
感谢您的帮助!
编辑2:我已经使用新算法更新了我的代码,该算法检查球在碰撞之前所在的位置。角落案例现在在单个平台上可以正确工作,当我有一堵墙的物体时,我可以正确地沿着它滑动。唯一剩下的问题是当我在地面上时会出现小的抖动效果,球似乎不断上下移动,就像被重力拉扯然后球重新掉进物体里。
编辑:这里是一个URL,尝试显示我遇到的问题类型的图像: http://img8.imageshack.us/img8/4603/collisionproblem.png 如果图片中的解释不太清楚,请问我,我会尽力澄清更多。

这个问题可能更适合于gamedev.stackexchange.com,然而...那里的人口似乎在各方面都有所不足。综合考虑后,在这里提问可能更好。 - Sion Sheevok
就算价值不高,平台游戏中的移动代码确实包含了很多手工调整和故障。甚至有一群人反向工程这些代码,制作“工具辅助速通”视频。 - Heatsink
我之前听说过“工具辅助快速通关”。感谢你提醒我平台游戏的移动代码问题。看起来我需要做很多手动调整才能使其正常运行。希望有人能提出一些解决这个问题的想法。 - defender-zone
1个回答

12

你的代码中存在一个问题,就是只能检测到这样的情况:

ball

如果圆恰好完全在方块内部,那么你根本不会重新定位。这是一个问题。

你试图将模拟看作连续的,但要记住它是离散的。一般来说,如果你查看球的当前状态,你无法知道它与哪一侧发生了碰撞。看看这两种可能性: cases

首先想到的解决方案是同时查看球的上一个位置,更准确地说,查看其增量向量。看看增量向量是否与墙壁相交。如果是的话,就朝着与增量向量相交的墙壁重新定位到轴对齐方向。

编辑:当我说“增量向量”时,我忘记了您正在移动一个正方形而不是单个点。因此,如果您只查看左上角的增量向量,那将是不够的,因为它可能无法检测到球的某个部分进入了方块。相反,您可以查看所有4个角的增量向量。


+1 非常感谢您不仅提供了解决方案,还进行了演示。我自己也在尝试中。 - Sion Sheevok
感谢您为我澄清这个问题。我似乎想不出一个合适的算法来检测碰撞发生的哪一侧。我一直认为两种情况都会返回true并引起问题。我是否可以对现有算法进行一些简单的更改,还是必须重写一个新的算法? - defender-zone
2个案例?你是指我在编辑中提到的4个案例中的其中2个吗?是的,有时候会。好的,假设你检查了4个增量向量并找到了n个交点(如果你穿过整个块,n可以大于4)。诀窍在于n个交点中一个发生得最早。哪一个?嗯,所有4个角落都以相同的速度移动。因此,通过将距离除以速度,您可以获得时间。因此,第一次碰撞发生在最后一帧中位置最接近的交点处。好的,现在您知道碰撞发生的位置,就知道应该沿哪堵墙滑动了。 - Stefan Monov
如果图片中的解释不太清楚,球就不能向左移动超过物体的角落,除非我跳过它。然而,球可以向右移动,但在移动时会重新定位到物体的右侧,这是不必要的。这实际上创建了一个跳跃运动,当我向右移动时,它看起来像球跳过了物体的一半左右。如果这不清楚,请问我,我会尽力澄清。 - defender-zone
@defender-zone:看起来de2dCheckCollision在边缘情况下返回true,其中一个矩形的最高坐标等于另一个矩形的最低坐标。对于这种情况,它应该返回false。 - Stefan Monov
显示剩余15条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接