C++/SDL2 - 一起球 bouncing/glitching
C++ / SDL2 - Ball bouncing/glitching together
我正在尝试使用 SDL2 在 C++ 中编写一些球弹跳程序。我很难让速度交换正确,但到目前为止它工作得很好。我现在唯一的问题是球有时会 glitching/stucking 在一起,几秒钟后它们又会自行释放。
这是我的 update()
函数,每帧都会调用它:
void Game::update() {
updateFPS();
checkBallCollision();
updateCanCollide();
int newtime = SDL_GetTicks();
int diff = newtime - lasttime;
if (diff > 10)
diff = 10;
for (Ball *ball : balls) {
ball->x = ball->x + ball->velocity->x * (float) diff / 100;
ball->y = ball->y + ball->velocity->y * (float) diff / 100;
checkBorderCollision(ball);
}
lasttime = newtime;
}
我猜球越来越接近,不会在球的边界反弹。因此,我试图给每个球一个布尔值 canCollide
,它总是正确的,除非球正在碰撞。然后它保持错误,直到两个球不再重叠。
这是我的 checkBallCollision()
和 updateCanCollide()
函数:`
void Game::updateCanCollide() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (ballArr[i]->canCollide)
continue;
bool updatedCollide = true;
for (int k = i + 1; k < length; k++) {
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
updatedCollide = false;
}
}
ballArr[i]->canCollide = updatedCollide;
}
}
// do all collision checks and update the velocity
void Game::checkBallCollision() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (!ballArr[i]->canCollide)
continue;
for (int k = i + 1; k < length; k++) {
if (!ballArr[k]->canCollide)
continue;
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
// ball1 and ball2 are colliding
// update the velocity of both balls
float m1 = ball1->radius * ball1->radius * 3.14159;
float m2 = ball2->radius * ball2->radius * 3.14159;
Vector2D *v1 = new Vector2D(ball1->velocity->x, ball1->velocity->x);
Vector2D *v2 = new Vector2D(ball2->velocity->x, ball2->velocity->x);
ball1->velocity->x = ((v1->x * (m1 - m2) + 2 * m2 * v2->x) / (m1 + m2));
ball1->velocity->y = ((v1->y * (m1 - m2) + 2 * m2 * v2->y) / (m1 + m2));
ball2->velocity->x = ((v2->x * (m2 - m1) + 2 * m1 * v1->x) / (m1 + m2));
ball2->velocity->y = ((v2->y * (m2 - m1) + 2 * m1 * v1->y) / (m1 + m2));
ball1->canCollide = false;
ball2->canCollide = false;
}
}
}
}
正确的修复
主要问题是您让球彼此重叠,然后更新它们的速度。但是,如果下一个时间步长比上一个时间步短,则可能是在更新它们的位置后,它们仍然重叠。然后你认为它们再次碰撞,并更新它们的速度,但这很可能会导致它们再次靠得更近。这解释了为什么他们会卡住。
解决这个问题的正确等待是计算两个移动球碰撞的确切时间点。这可以通过分析来完成,例如将时间视为三维,然后计算 line-sphere intersection。如果在时间步长期间发生这种情况,则将时间提前到碰撞发生的时间点,然后更新速度,然后执行该步长的其余部分。如果你有两个以上的球,请注意你可以有两个以上的球在同一时间步内相互碰撞。这个也是可以解决的,只要计算出所有碰撞发生的时间点,select最早的那个,更新那个点的速度,然后重新计算碰撞次数,以此类推,直到时间步内没有碰撞。
解决方法
您的解决方法可能会解决两个球相互粘连的问题,但结果在物理上并不准确。当你开始增加球的密度时它就会崩溃,因为在某个时候,一对应该碰撞的球中至少有一个在前一个时间步发生碰撞的可能性非常高,然后它们都会开始通过时时刻刻都在互相穿梭。
另一个问题是您必须检查 updateCanCollide()
中所有可能的球对,效率不高。这个问题有一个更简单和更常见的解决方法:当两个球碰撞时,在更新它们的速度之后,立即更新它们的位置,这样球就不再碰撞了。你可以试着计算一下到底移动多少让它们不再重叠,或者如果你不想涉及数学,你可以只用一个while循环做一小步直到它们不再重叠。
代码中的其他问题
请注意,您的代码中还有其他一些您可以改进的地方:
- 不要
new
临时 Vector2D
,只需在堆栈上声明它即可。如果由于某种原因这是不可能的,至少 delete
v1
和 v2
之后。
- 如果您要对结果进行平方,则无需调用
abs()
。
- 使用
std::hypot()
计算距离。
- 您
Vector2D
是自己写的还是来自图书馆?如果是后者,也许它已经具有反映两个二维向量的功能?如果是前者,请考虑使用像 GLM 这样的库,即使您没有使用 OpenGL。
- 使用适当的π值。一个简单、可移植的解决方案是声明
static constexpr pi = std::atan(1) * 4
.
我正在尝试使用 SDL2 在 C++ 中编写一些球弹跳程序。我很难让速度交换正确,但到目前为止它工作得很好。我现在唯一的问题是球有时会 glitching/stucking 在一起,几秒钟后它们又会自行释放。
这是我的 update()
函数,每帧都会调用它:
void Game::update() {
updateFPS();
checkBallCollision();
updateCanCollide();
int newtime = SDL_GetTicks();
int diff = newtime - lasttime;
if (diff > 10)
diff = 10;
for (Ball *ball : balls) {
ball->x = ball->x + ball->velocity->x * (float) diff / 100;
ball->y = ball->y + ball->velocity->y * (float) diff / 100;
checkBorderCollision(ball);
}
lasttime = newtime;
}
我猜球越来越接近,不会在球的边界反弹。因此,我试图给每个球一个布尔值 canCollide
,它总是正确的,除非球正在碰撞。然后它保持错误,直到两个球不再重叠。
这是我的 checkBallCollision()
和 updateCanCollide()
函数:`
void Game::updateCanCollide() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (ballArr[i]->canCollide)
continue;
bool updatedCollide = true;
for (int k = i + 1; k < length; k++) {
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
updatedCollide = false;
}
}
ballArr[i]->canCollide = updatedCollide;
}
}
// do all collision checks and update the velocity
void Game::checkBallCollision() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (!ballArr[i]->canCollide)
continue;
for (int k = i + 1; k < length; k++) {
if (!ballArr[k]->canCollide)
continue;
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
// ball1 and ball2 are colliding
// update the velocity of both balls
float m1 = ball1->radius * ball1->radius * 3.14159;
float m2 = ball2->radius * ball2->radius * 3.14159;
Vector2D *v1 = new Vector2D(ball1->velocity->x, ball1->velocity->x);
Vector2D *v2 = new Vector2D(ball2->velocity->x, ball2->velocity->x);
ball1->velocity->x = ((v1->x * (m1 - m2) + 2 * m2 * v2->x) / (m1 + m2));
ball1->velocity->y = ((v1->y * (m1 - m2) + 2 * m2 * v2->y) / (m1 + m2));
ball2->velocity->x = ((v2->x * (m2 - m1) + 2 * m1 * v1->x) / (m1 + m2));
ball2->velocity->y = ((v2->y * (m2 - m1) + 2 * m1 * v1->y) / (m1 + m2));
ball1->canCollide = false;
ball2->canCollide = false;
}
}
}
}
正确的修复
主要问题是您让球彼此重叠,然后更新它们的速度。但是,如果下一个时间步长比上一个时间步短,则可能是在更新它们的位置后,它们仍然重叠。然后你认为它们再次碰撞,并更新它们的速度,但这很可能会导致它们再次靠得更近。这解释了为什么他们会卡住。
解决这个问题的正确等待是计算两个移动球碰撞的确切时间点。这可以通过分析来完成,例如将时间视为三维,然后计算 line-sphere intersection。如果在时间步长期间发生这种情况,则将时间提前到碰撞发生的时间点,然后更新速度,然后执行该步长的其余部分。如果你有两个以上的球,请注意你可以有两个以上的球在同一时间步内相互碰撞。这个也是可以解决的,只要计算出所有碰撞发生的时间点,select最早的那个,更新那个点的速度,然后重新计算碰撞次数,以此类推,直到时间步内没有碰撞。
解决方法
您的解决方法可能会解决两个球相互粘连的问题,但结果在物理上并不准确。当你开始增加球的密度时它就会崩溃,因为在某个时候,一对应该碰撞的球中至少有一个在前一个时间步发生碰撞的可能性非常高,然后它们都会开始通过时时刻刻都在互相穿梭。
另一个问题是您必须检查 updateCanCollide()
中所有可能的球对,效率不高。这个问题有一个更简单和更常见的解决方法:当两个球碰撞时,在更新它们的速度之后,立即更新它们的位置,这样球就不再碰撞了。你可以试着计算一下到底移动多少让它们不再重叠,或者如果你不想涉及数学,你可以只用一个while循环做一小步直到它们不再重叠。
代码中的其他问题
请注意,您的代码中还有其他一些您可以改进的地方:
- 不要
new
临时Vector2D
,只需在堆栈上声明它即可。如果由于某种原因这是不可能的,至少delete
v1
和v2
之后。 - 如果您要对结果进行平方,则无需调用
abs()
。 - 使用
std::hypot()
计算距离。 - 您
Vector2D
是自己写的还是来自图书馆?如果是后者,也许它已经具有反映两个二维向量的功能?如果是前者,请考虑使用像 GLM 这样的库,即使您没有使用 OpenGL。 - 使用适当的π值。一个简单、可移植的解决方案是声明
static constexpr pi = std::atan(1) * 4
.