这是我的第一篇文章,所以我试图让我的问题尽可能清楚。我正在制作一款游戏,我想改进我的碰撞检测。这是因为我想检查哪一侧被击中并阻止玩家越过它,而无需使用像 if(collision(player, enemy)) player.x= enemy.x - player.w(width)这样的一般内容,因为如果玩家与顶部相撞,它不会让玩家保持在顶部。
在代码中,它检查任何一个语句是否为真,然后返回它,但它没有告诉我哪个语句等于 true,因此我可以阻止玩家相应地移动,如果这有意义的话。如果您有更有效的碰撞检测供我使用,将不胜感激。
我已经尝试过使一个位置变量等于被碰撞到的任何一侧,然后阻止玩家越过它,但它仅适用于左侧,不会让我的玩家跳过敌人或阻挡。
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
//Only works for the left side
if(collision(player, enemy)) player.x = enemy.x - player.w
我希望它能够告诉我哪一侧被碰撞,然后阻止玩家过去/进入它,并且玩家能够在块/敌人的顶部,而不仅仅是被推到左边。
您需要计算 x 和 y 之间的距离,并使用它们可能沿每个轴碰撞的最小距离来查找沿两个轴的深度。然后,您可以选择较小的深度并沿着该深度移动。下面是一个示例:
if(collision(player, enemy)){
// Most of this stuff would probably be good to keep stored inside the player
// along side their x and y position. That way it doesn't have to be recalculated
// every collision check
var playerHalfW = player.w/2
var playerHalfH = player.h/2
var enemyHalfW = enemy.w/2
var enemyHalfH = enemy.h/2
var playerCenterX = player.x + player.w/2
var playerCenterY = player.y + player.h/2
var enemyCenterX = enemy.x + enemy.w/2
var enemyCenterY = enemy.y + enemy.h/2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if(depthX != 0 && depthY != 0){
if(Math.abs(depthX) < Math.abs(depthY)){
// Collision along the X axis. React accordingly
if(depthX > 0){
// Left side collision
}
else{
// Right side collision
}
}
else{
// Collision along the Y axis.
if(depthY > 0){
// Top side collision
}
else{
// Bottom side collision
}
}
}
}
工作示例
这是一个你可以尝试的工作示例。使用箭头键移动玩家。
player = {
x: 9,
y: 50,
w: 100,
h: 100
}
enemy = {
x: 100,
y: 100,
w: 100,
h: 100
}
output = document.getElementById("collisionType");
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d")
function collision(object1, object2) {
return !(
object1.x > object2.x + object2.w ||
object1.x + object1.w < object2.x ||
object1.y > object2.y + object2.h ||
object1.y + object1.h < object2.y
)
}
function draw() {
ctx.clearRect(0, 0, 400, 400)
ctx.lineWidth = "5"
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.rect(player.x, player.y, player.w, player.h);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.rect(enemy.x, enemy.y, enemy.w, enemy.h);
ctx.stroke();
}
function handleCollision() {
if (collision(player, enemy)) {
var playerHalfW = player.w / 2
var playerHalfH = player.h / 2
var enemyHalfW = enemy.w / 2
var enemyHalfH = enemy.h / 2
var playerCenterX = player.x + player.w / 2
var playerCenterY = player.y + player.h / 2
var enemyCenterX = enemy.x + enemy.w / 2
var enemyCenterY = enemy.y + enemy.h / 2
// Calculate the distance between centers
var diffX = playerCenterX - enemyCenterX
var diffY = playerCenterY - enemyCenterY
// Calculate the minimum distance to separate along X and Y
var minXDist = playerHalfW + enemyHalfW
var minYDist = playerHalfH + enemyHalfH
// Calculate the depth of collision for both the X and Y axis
var depthX = diffX > 0 ? minXDist - diffX : -minXDist - diffX
var depthY = diffY > 0 ? minYDist - diffY : -minYDist - diffY
// Now that you have the depth, you can pick the smaller depth and move
// along that axis.
if (depthX != 0 && depthY != 0) {
if (Math.abs(depthX) < Math.abs(depthY)) {
// Collision along the X axis. React accordingly
if (depthX > 0) {
output.innerHTML = "left side collision"
} else {
output.innerHTML = "right side collision"
}
} else {
// Collision along the Y axis.
if (depthY > 0) {
output.innerHTML = "top side collision"
} else {
output.innerHTML = "bottom side collision"
}
}
}
} else {
output.innerHTML = "No collision"
}
}
keyStates = []
function handleKeys() {
if (keyStates[39]) {
player.x += 2 //Move right
} else if (keyStates[37]) {
player.x -= 2 //Move left
}
if (keyStates[38]) {
player.y -= 2 //Move up
}
if (keyStates[40]) {
player.y += 2 //Move down
}
}
function main() {
handleKeys();
draw();
handleCollision();
window.requestAnimationFrame(main);
}
window.onkeydown = function(e) {
keyStates[e.keyCode] = true
}
window.onkeyup = function(e) {
keyStates[e.keyCode] = false
}
main();
<h2 id="collisionType"></h2>
<canvas id="canvas" width='300' height='300'></canvas>
对碰撞的反应
既然您知道碰撞发生在哪一边,那么决定如何反应应该是相当微不足道的。这将与您当前为左侧所做的非常相似,只需翻转一些标志并更改轴即可。
其他注意事项
- 您可能需要考虑玩家的速度(如果有),否则检测可能会失败。
- 如果玩家的速度太高,它可能会"隧道"穿过敌人,并且不会检测到碰撞。
- 如果碰撞时速度没有停止,玩家的移动也会看起来抖动
- 您的对象可以旋转或具有 4 个以上的边吗?如果是这样,您可能需要使用下面所述的另一种方法。
这是对另一篇深入讨论碰撞引擎的文章的一个很好的回答
其他方法
至于其他碰撞检测方法,有很多,但我想到的是分离轴定理,它比你拥有的要复杂一些,但可以处理更复杂的凸形状和旋转。它还告诉您移动以解决碰撞所需的方向和距离。这是一个具有交互式示例并深入了解该主题的网站。它似乎没有给出完整的实现,但可以在其他地方找到。