因此,我目前正在通过尝试为我的游戏引擎制作一个简单的物理引擎来重新发明轮子(并学到很多东西)。我一直在网上搜索,试图(但未能)解决我当前的问题。关于这个主题有很多资源,但我发现的资源似乎都不适用于我的案例。
问题简而言之:当两个矩形发生碰撞时,碰撞解决方案无法在某些 Angular 上按预期工作。它失败的方式因矩形的尺寸而异。我正在寻找的是一种“最短重叠”的碰撞解决方案或其他相当简单的解决方案(我愿意接受建议!)。(向下滚动以获得更好的解释和插图)。
警告:以下代码可能效率不高......
首先,这是我的物理循环。它只是循环遍历所有游戏实体并检查它们是否与任何其他游戏实体发生碰撞。它效率不高(n^2 和所有这些),但它现在有效。
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
下面是实际碰撞检测的代码。这似乎也可以正常工作。
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
这就是问题开始出现的地方。我希望实体简单地被“推出”目标实体并将速度设置为 0。只要实体和目标都是完美的正方形,这就可以正常工作。如果假设实体(gif 中的玩家形象)是一个矩形,那么当最长边(X 轴)与目标(正方形)发生碰撞时,碰撞将“滑动”。如果我调换播放器尺寸,使其变短变宽,那么 Y 轴就会出现同样的问题。
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
我该怎么做才能解决这个滑动问题?在过去的 5 天里,我一直在反对这个问题,所以如果有人能帮助我朝着正确的方向前进,我将不胜感激!
谢谢你:)
-- 编辑:--
如果仅沿左侧或右侧向一个方向移动,也会发生打滑。
-- 编辑 2 工作代码:-- 有关工作代码的示例,请参阅下面的答案!
最佳答案
你犯的重要逻辑错误是这一行:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
为您的 X 滑动示例考虑一个近乎极端的情况:如果它们几乎在拐 Angular 处接触:
虽然这个图有点夸张,但是你可以看到absoluteNormalizedX < absoluteNormalizedY
在这种情况下 - 您的实现将继续解决垂直碰撞而不是预期的水平碰撞。
另一个错误是无论碰撞发生在哪一侧,你总是将相应的速度分量设置为零:如果它与碰撞法线方向相反,你必须只将分量归零,否则你将无法离开表面。
克服这个问题的一个好方法是在进行碰撞检测时也记录碰撞面:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
为了提高效率,使用位标志而不是 bool 数组。然后可以将解析器函数重写为:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
请注意,与您的原始代码不同,上述代码还允许 Angular 发生碰撞 - 即沿两个轴解析速度和位置。
关于javascript - AABB 碰撞解决滑边,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46172953/