Как сделать коллизию двух блоков на Canvas JavaScript с учетом стороны колизии
Делаю игру на JavaScript на Canvas, сделал передвижение персонажа и гравитацию, но никак не могу сделать правильную коллизию, такую, что она работала при слишком больших/маленьких размерах блока. Также нужна сторона, с которой персонаж коллизит с блоком
Код для определения коллизий:
if (
rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y
) {
return true;
} else {
return false;
}
Код для определения сторон:
if (mob.x + mob.width >= ground.x && self.x + self.width <= ground.x) {
if (
(mob.y >= ground.y && mob.y <= ground.y + ground.height) ||
(mob.y + mob.height >= ground.y && mob.y + mob.height <= ground.y + ground.height)
) {
mob.x = ground.x - mob.width;
mob.collisions.left = true;
}
}
// (и так для каждой)
Проблема в том что если персонаж 50 на 50 пикс, то при колизии с блоком 200 на 10 пикселей, боковой колизии не происходит.
Вопрос срочный, любая помощь приветствуется.
Заранее спасибо!
Ответы (1 шт):
Примечательна здесь только функция detectCollision. Все пояснения добавил в комментариях к этой функции. Дополнительно надо иметь в виду, что неточное обнаружение коллизии здесь возможно если дискретный шаг смещения объектов больше половины ширины или высоты одного из объектов. Но эту корректировку можно добавить в код, имея положение на предыдущем шаге.
При коллизии оба объекта меняют направление движения по соответствующей координате. Поэтому если один объект догоняет другой, то эффект выглядит неестественно. Здесь нужны дополнительные доработки.
Для того, чтобы определить каким бортами было соударение можно задать условие if(dx > 0) rect1.sideX = 'left';...
const canvas = document.getElementById('game');
const w = canvas.width = 600;
const h = canvas.height = 200;
const ctx = canvas.getContext('2d');
const rect1 = {
w : 40,
h : 40,
cx : 200,
cy : 50,
sx : 1,
sy : -2
}
const rect2 = {
w : 20,
h : 60,
cx : 400,
cy : 120,
sx : 2,
sy : 1
}
draw([rect1,rect2]);
function draw(arr){
move(arr);
ctx.clearRect(0,0,w,h);
arr.forEach(r => {
ctx.fillRect(r.cx-r.w/2,r.cy-r.h/2,r.w,r.h);
})
requestAnimationFrame(() => {draw(arr)});
}
function move(arr){
detectCollision(arr);
arr.forEach(rect => {
detectBorder(rect);
rect.prevX = rect.cx;
rect.prevY = rect.cy;
rect.cx += rect.sx;
rect.cy += rect.sy;
})
}
function detectBorder(rect){
if(rect.cx + rect.w/2 >= w || rect.cx - rect.w/2 <= 0) rect.sx = -rect.sx;
if(rect.cy + rect.h/2 >= h || rect.cy - rect.h/2 <= 0) rect.sy = -rect.sy;
}
function detectCollision([rect1,rect2]){
let dx = rect1.cx - rect2.cx; // Расстояние между центрами по X
let dy = rect1.cy - rect2.cy; // Расстояние между центрами по Y
let rx = (rect1.w + rect2.w)/2; // Минимальноe расстояние по X
let ry = (rect1.h + rect2.h)/2; // Минимальное расстояние по Y
if(Math.abs(dx) < rx && Math.abs(dy) < ry){ // если есть коллизия
if(Math.abs(rect1.prevX - rect2.prevX) >= rx){// если по X совмещения не было, то коллизия произошла боковыми бортами
rect1.sx = -rect1.sx;
rect2.sx = -rect2.sx;
// Чтобы определить стороны столкновения:
// if(dx > 0){
// rect1.sideX = 'left';
// rect2.sideX = 'right';
// } else {
// rect1.sideX = 'right';
// rect2.sideX = 'left';
// }
}
if(Math.abs(rect1.prevY - rect2.prevY) >= ry){ // если по Y совмещения не было, то коллизия верх-низ
rect1.sy = -rect1.sy;
rect2.sy = -rect2.sy;
// if(dy > 0){
// rect1.sideY = 'top';
// rect2.sideY = 'bottom';
// } else {
// rect1.sideY = 'bottom';
// rect2.sideY = 'top';
// }
}
// Если совмещение не было, то коллизия углами
}
}
<canvas id="game"></canvas>