修复基于速度的二维运动的简单碰撞检测
Fixing simple collision detection with velocity based 2d movement
在这个 codePen demo 中,你可以用箭头移动 "player" 方块,用 space 放置一个灯,并且应该被推到任何方向停止越过蓝线相反的方向。 "player" 使用 x 和 y 速度变量来创建运动,如果检测到碰撞,则将它们乘以 -1(+ 某个值)。
问题是,在被推离墙壁后,"player" 卡在一个只能从墙壁向后移动的位置,同时看起来卡在与其垂直的轴上。 (例如 - 如果墙在玩家的顶部,你只能移动到底部,而不是在撞到墙后向左或向右移动)
从理论上讲,我想要一个平滑的滑动碰撞检测,卡在墙上的玩家会慢慢地向左或向右滑动
取决于是否按下左箭头或右箭头。 (玩玩我能够实现这一点,但总是一个方向会 "flow" 让玩家向某个方向滑动)
我考虑过使用射线或其他一些方法来检测命中,但它们似乎需要更多计算时间比简单的方法。将不胜感激构建可扩展碰撞检测的任何输入和任何建议,
这是我在演示中进行移动和碰撞检测的基本代码:
let xVelocity = 0;
let yVelocity = 0;
var blockedMapGrid = [[0,30],[0,50],[0,100],[0,150],[0,200],[0,250],
[50,0],[100,0],[150,0],[200,0],[250,0],[300,0]];
var animate = function() {
if (keyState[37]) {
xVelocity -= 1;
}
if (keyState[38]) {
yVelocity += 1;
}
if (keyState[39]) {
xVelocity += 1;
}
if (keyState[40]) {
yVelocity -= 1;
}
for (var i = 0; i < blockedMapGrid.length; i++) {
if (Math.abs(player.position.x - blockedMapGrid[i][0]) +
Math.abs(player.position.y - blockedMapGrid[i][1]) < 36) {
xVelocity = -xVelocity * 1.2;
yVelocity = -yVelocity * 1.2;
console.log("Blocked by " + blockedMapGrid[i][0])
};
}
player.position.x = player.position.x + xVelocity;
player.position.y = player.position.y + yVelocity;
yVelocity *= 0.80;
xVelocity *= 0.80;
camera.position.x = player.position.x;
camera.position.y = player.position.y;
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
您检测器的这一部分是错误的:
Math.abs(player.position.x - blockedMapGrid[i][0]) +
Math.abs(player.position.y - blockedMapGrid[i][1]) < 36
基本上,在这里,您通过使用添加的绝对值而不是平方和的根来近似从玩家到网格上的点的距离。事实上,你不需要这么复杂的网格(重复线)和距离。
您似乎在进行轴对齐边界框 (AABB) 检测。网上有plenty of resources 如何优化它。
但一般的做法是这样的。您的网格阵列应由具有 (x,y,w,h)
个度量值的框组成。可以是细的,长的,方形的,任何东西。
我们还假设您的播放器有一个边界框 (player.x, player.y, player.w, player.h)
,然后
for (var i = 0; i < grid.length; i++) {
if (player.x < grid[i].x + grid[i].w &&
player.x + player.w > grid[i].x &&
player.y < grid[i].y + grid[i].h &&
player.y + player.h > grid[i].y) {
//collision detected! move player to previous known position
break;
}
}
您可以改变检测到碰撞时的操作,但使用 4 个条件判断两个框是否重叠是这里的关键。
更新
问题中的代码出现的另一个问题是检测到碰撞后"bouncing"或"getting stuck"。
根据经验,你不应该在碰撞后使用 velocity = -velocity
,同时确保角色回到 "clear",即玩家的边界框不与任何障碍物重叠。否则,您将陷入无限循环 collision? -> vel = -vel, pos += vel*t -> collision -> ...
,速度从负向正向反弹,并且不允许玩家离开墙。
最简单的修复方法是先在临时变量中计算玩家的新位置,检查新位置是否没有碰撞,然后才将其永久化并调用render()
,否则直接忽略它并在不移动播放器的情况下进行渲染。
另一种方法是记住最后已知的 "good" 位置,并且仅在角色返回到之前的位置时(可能是在动画或一系列无法控制的动作之后)才交还角色的控制权。
还有更复杂的方法,主要涉及某种物理模拟,让角色在多个障碍物中弹跳,前提是控制输入不会克服惯性——想想汽车在湿滑的道路上或船撞到多棵树。但无论哪种方式,在检测到碰撞之后和调用 "render()" 之前,您都必须将角色放置在物理上可能的位置,否则它将是众所周知的 "stuck in textures"。
在这个 codePen demo 中,你可以用箭头移动 "player" 方块,用 space 放置一个灯,并且应该被推到任何方向停止越过蓝线相反的方向。 "player" 使用 x 和 y 速度变量来创建运动,如果检测到碰撞,则将它们乘以 -1(+ 某个值)。
问题是,在被推离墙壁后,"player" 卡在一个只能从墙壁向后移动的位置,同时看起来卡在与其垂直的轴上。 (例如 - 如果墙在玩家的顶部,你只能移动到底部,而不是在撞到墙后向左或向右移动)
从理论上讲,我想要一个平滑的滑动碰撞检测,卡在墙上的玩家会慢慢地向左或向右滑动
取决于是否按下左箭头或右箭头。 (玩玩我能够实现这一点,但总是一个方向会 "flow" 让玩家向某个方向滑动)
我考虑过使用射线或其他一些方法来检测命中,但它们似乎需要更多计算时间比简单的方法。将不胜感激构建可扩展碰撞检测的任何输入和任何建议,
这是我在演示中进行移动和碰撞检测的基本代码:
let xVelocity = 0;
let yVelocity = 0;
var blockedMapGrid = [[0,30],[0,50],[0,100],[0,150],[0,200],[0,250],
[50,0],[100,0],[150,0],[200,0],[250,0],[300,0]];
var animate = function() {
if (keyState[37]) {
xVelocity -= 1;
}
if (keyState[38]) {
yVelocity += 1;
}
if (keyState[39]) {
xVelocity += 1;
}
if (keyState[40]) {
yVelocity -= 1;
}
for (var i = 0; i < blockedMapGrid.length; i++) {
if (Math.abs(player.position.x - blockedMapGrid[i][0]) +
Math.abs(player.position.y - blockedMapGrid[i][1]) < 36) {
xVelocity = -xVelocity * 1.2;
yVelocity = -yVelocity * 1.2;
console.log("Blocked by " + blockedMapGrid[i][0])
};
}
player.position.x = player.position.x + xVelocity;
player.position.y = player.position.y + yVelocity;
yVelocity *= 0.80;
xVelocity *= 0.80;
camera.position.x = player.position.x;
camera.position.y = player.position.y;
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
您检测器的这一部分是错误的:
Math.abs(player.position.x - blockedMapGrid[i][0]) +
Math.abs(player.position.y - blockedMapGrid[i][1]) < 36
基本上,在这里,您通过使用添加的绝对值而不是平方和的根来近似从玩家到网格上的点的距离。事实上,你不需要这么复杂的网格(重复线)和距离。
您似乎在进行轴对齐边界框 (AABB) 检测。网上有plenty of resources 如何优化它。
但一般的做法是这样的。您的网格阵列应由具有 (x,y,w,h)
个度量值的框组成。可以是细的,长的,方形的,任何东西。
我们还假设您的播放器有一个边界框 (player.x, player.y, player.w, player.h)
,然后
for (var i = 0; i < grid.length; i++) {
if (player.x < grid[i].x + grid[i].w &&
player.x + player.w > grid[i].x &&
player.y < grid[i].y + grid[i].h &&
player.y + player.h > grid[i].y) {
//collision detected! move player to previous known position
break;
}
}
您可以改变检测到碰撞时的操作,但使用 4 个条件判断两个框是否重叠是这里的关键。
更新
问题中的代码出现的另一个问题是检测到碰撞后"bouncing"或"getting stuck"。
根据经验,你不应该在碰撞后使用 velocity = -velocity
,同时确保角色回到 "clear",即玩家的边界框不与任何障碍物重叠。否则,您将陷入无限循环 collision? -> vel = -vel, pos += vel*t -> collision -> ...
,速度从负向正向反弹,并且不允许玩家离开墙。
最简单的修复方法是先在临时变量中计算玩家的新位置,检查新位置是否没有碰撞,然后才将其永久化并调用render()
,否则直接忽略它并在不移动播放器的情况下进行渲染。
另一种方法是记住最后已知的 "good" 位置,并且仅在角色返回到之前的位置时(可能是在动画或一系列无法控制的动作之后)才交还角色的控制权。
还有更复杂的方法,主要涉及某种物理模拟,让角色在多个障碍物中弹跳,前提是控制输入不会克服惯性——想想汽车在湿滑的道路上或船撞到多棵树。但无论哪种方式,在检测到碰撞之后和调用 "render()" 之前,您都必须将角色放置在物理上可能的位置,否则它将是众所周知的 "stuck in textures"。