虽然我发现了许多相似的问题,但是没有一个解决方案解决了我的问题。
我一直在用C ++(Visual C ++)和实体组件系统(ECS)试验SDL2。但我无法弄清楚碰撞响应中的错误。
所以这就是:当我遇到像岩石(简单的灰色瓷砖)之类的东西时,我的玩家有时会回到原点。但有时它会直接通过并卡在另一边或者最终结束。
我只能假设它与帧之间更改的数据有关,所以它并不总是被捕获。但是对于我的生活,我无法理解。
这是我的矩形检测方法:
bool Collision::RectIntersect(const SDL_Rect& a, const SDL_Rect& b, SDL_Rect& intersect)
{
intersect = { 0, 0, 0, 0 };
int leftX = std::max(a.x, b.x);
int rightX = std::min(a.x + a.w, b.x + b.w);
int topY = std::max(a.y, b.y);
int bottomY = std::min(a.y + a.h, b.y + b.h);
if (leftX < rightX && topY < bottomY)
{
intersect = { leftX, topY, rightX - leftX, bottomY - topY };
return true;
}
return false;
}
这是我的代码片段,处理我的输入,然后在代码实际移动任何内容之前解决任何碰撞检测:
void InputComponent::handleEvents(SDL_Event* e)
{
const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
if (e != nullptr)
{
/*
keyHeld: array of 4 for each direction (+/- x, +/- y (WASD))
hold value true, if pressed down, otherwise false
*/
if (keyboardState[SDL_SCANCODE_A])
{
keyHeld[0] = true;
}
else
{
keyHeld[0] = false;
}
if (keyboardState[SDL_SCANCODE_D])
{
keyHeld[1] = true;
}
else
{
keyHeld[1] = false;
}
if (keyboardState[SDL_SCANCODE_W])
{
keyHeld[2] = true;
}
else
{
keyHeld[2] = false;
}
if (keyboardState[SDL_SCANCODE_S])
{
keyHeld[3] = true;
}
else
{
keyHeld[3] = false;
}
}
/*
tmpVel: Vector to store the assumed velocity in x- and y-direction
*/
Vector2D tmpVel(0.0f, 0.0f);
// left and right (A and D)
if (keyHeld[0] && !keyHeld[1]) // left
{
tmpVel.x = -1.0f;
}
else if (!keyHeld[0] && keyHeld[1]) // right
{
tmpVel.x = 1.0f;
}
else
{
tmpVel.x = 0.0f; // left and right cancel each other out
}
// up and down (W and S)
if (keyHeld[2] && !keyHeld[3]) // up
{
tmpVel.y = -1.0f;
}
else if (!keyHeld[2] && keyHeld[3]) // down
{
tmpVel.y = 1.0f;
}
else
{
tmpVel.y = 0.0f; // up and down cancel each other out
}
/*
check for collision with presumed direction according to tmpVel
*/
SDL_Rect intersection;
// get current player position
SDL_Rect movedPlayer = entity->getComponent<CollisionComponent>().getCollider();
// add trajectory of theoretical movement
movedPlayer.x += static_cast<int>(tmpVel.x * vel_->getSpeed());
movedPlayer.y += static_cast<int>(tmpVel.y * vel_->getSpeed());
bool hasCollided = false;
// collect all collidable objects
for (auto& c : manager_->getGroup(GroupLabel::GR_COLLIDERS))
{
// check player against each collidable tile
//if (SDL_IntersectRect(&movedPlayer, &c->getComponent<CollisionComponent>().getCollider(), &intersection))
if (Collision::RectIntersect(movedPlayer, c->getComponent<CollisionComponent>().getCollider(), intersection))
{
// collision on x-axis
if (intersection.w > 0)
{
// set velocity on x-axis to 0
vel_->setVelocityX(0.0f);
// reset player position back according to width of intersected rectangle
pos_->setPosX(pos_->getPos().x + (static_cast<float>(intersection.w) * (-tmpVel.x)));
}
// collision on y-axis
if (intersection.h > 0)
{
// set velocity on y-axis to 0
vel_->setVelocityY(0.0f);
// reset player position back according to height of intersected rectangle
pos_->setPosY(pos_->getPos().y + (static_cast<float>(intersection.h) * (-tmpVel.y)));
}
hasCollided = true;
}
}
if (!hasCollided)
{
vel_->setVelocity(tmpVel);
}
}
任何人都可以让我朝着正确的方向前进吗?
答案 0 :(得分:0)
当玩家的右边缘恰好等于岩石的左边缘时会发生什么?看起来没有检测到碰撞,因为测试是针对(leftX < rightX)
的。因此,速度会更新,玩家会被速度移动。 (奇怪的是,您只需更新速度,然后移动播放器,而不是将它们移动到新计算的位置。)如果您将支票更改为(leftX <= rightX)
,问题是否仍然存在?
答案 1 :(得分:0)
据我所知,碰撞检测有两个问题。首先,您应该在测试(leftX < rightX && topY < bottomY)
时测试(leftX <= rightX && topY <= bottomY)
。如果您修复此问题,您的代码将在大多数情况下运行。
你所遇到的第二个问题,可能不会立即显现,就是你正在对谨慎的时间点进行碰撞检测。如果您的播放器具有足够大的速度矢量,您可能会遇到这种情况:
您的AABB测试是正确的,但玩家已通过隔离墙。解决这个问题的天真方法是更频繁地测试(更新1.5可能已经显示出碰撞),或限制玩家的速度。这两种方法都需要进行大量的微调,尤其是在处理可以以不同速度和不同厚度的墙壁移动的物体时。
更稳健的方法是考虑测试中的速度。由于您知道AABB的速度,因此您可以沿其速度矢量投影此形状。如果你对两个AABB都这样做,你最终会得到两个细长的形状,你可以相互测试。如果它们重叠,那么您就知道它们的路径交叉并且可能是一个碰撞。
当然,知道可能发生碰撞并没有多大帮助。问题是一个AABB可能移动得非常慢而另一个非常快,所以即使它们都通过相同的空间(它们的细长形状相交),它们也不会同时通过它。
弄清楚它们是否同时通过同一个空间很难,所以我们作弊。如果从B
的速度中减去A
的速度,然后使用此修改的速度投影A
的细长形状,则可以有效地将B
视为静止对象仍然得到正确的结果。知道了这一点,你的测试现在“B
与A
的细长形状重叠了吗?”。这只是一个简单的AABB与Ngon问题。
虽然上面的内容会给你一个关于两个移动AABB是否发生碰撞的布尔值,但它不会告诉你何时碰撞,这对计算篮板等事情也很有用。
我非常推荐这本书Real Time Collision Detection by Christer Ericson,这本书几乎可以预订任何有抱负的游戏开发者的碰撞检测。
以下是随本书附带的CD-ROM的代码片段。它测试移动的AABB对抗另一个移动的AABB,并提供首次接触的时间。
// Intersect AABBs ‘a’ and ‘b’ moving with constant velocities va and vb.
// On intersection, return time of first and last contact in tfirst and tlast
int IntersectMovingAABBAABB(AABB a, AABB b, Vector va, Vector vb, float &tfirst, float &tlast)
{
// Exit early if ‘a’ and ‘b’ initially overlapping
if (TestAABBAABB(a, b)) {
tfirst = tlast = 0.0f;
return 1;
}
// Use relative velocity; effectively treating ’a’ as stationary
Vector v = vb - va;
// Initialize times of first and last contact
tfirst = 0.0f;
tlast = 1.0f;
// For each axis, determine times of first and last contact, if any
for (int i = 0; i < 3; i++) {
if (v[i] < 0.0f) {
if (b.max[i] < a.min[i]) return 0;
// Nonintersecting and moving apart
if (a.max[i] < b.min[i]) tfirst = Max((a.max[i] - b.min[i]) / v[i], tfirst);
if (b.max[i] > a.min[i]) tlast = Min((a.min[i] - b.max[i]) / v[i], tlast);
}
if (v[i] > 0.0f) {
if (b.min[i] > a.max[i]) return 0;
// Nonintersecting and moving apart
if (b.max[i] < a.min[i]) tfirst = Max((a.min[i] - b.max[i]) / v[i], tfirst);
if (a.max[i] > b.min[i]) tlast = Min((a.max[i] - b.min[i]) / v[i], tlast);
}
// No overlap possible if time of first contact occurs after time of last contact
if (tfirst > tlast) return 0;
}
return 1;
}
Elsevier的软件许可协议要求以下归属:
“来自Christer Ericson的Real-Time Collision Detection,由Morgan Kaufmann出版社出版,©2005 Elsevier Inc”