HTML5 Canvas Collision Detection

Так что я искал и пробовал учебники, но я не могу заставить работать какие-либо системы обнаружения столкновений. Если кто-то сможет объяснить, что я делаю неправильно или какие-либо синтаксические ошибки, это было бы здорово.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bouncing Ball</title>
<style>
    #mycanvas {
        outline: 1px solid #000;
    }

</style>
</head>

<body>
    <canvas id="mycanvas" width="1280" height="750"></canvas>

    <script>

        var canvas = document.getElementById("mycanvas");
        var ctx = canvas.getContext('2d');

        var xx = 50;
        var yy = 100;

        var velY = 0;
        var velX = 0;
        var speed = 6;
        var friction = 0.7;
        var keys = [];

        var velocity = 0;
        var acceleration = 1;




        function physics() {
            velocity+=acceleration;
            yy += velocity;

            if(yy   >   597) {
                var temp =0;
                temp =velocity/4;
                velocity=-temp;
                yy =    597;
            }
        }


        function collision(first, second){
            return !(first.x > second.x + second.width || first.x + first.width < second.x || first.y > second.y + second.height || first.y + first.height < second.y);
        }


        var player = {
            color: "#2B2117",
            x: xx,
            y: yy,
            width: 75,
            height: 75,
            draw: function() {
                ctx.fillStyle = this.color;
                ctx.fillRect(xx, yy, this.width, this.height);
            }
        }

        var floor = {
            color: "#A67437",
            x: 0,
            y: 670,
            width: 1280,
            height: 80,
            draw: function() {
                ctx.fillStyle = this.color;
                ctx.fillRect(this.x, this.y, this.width, this.height);
            }
        }


        var bucket = {
            color: "#B25E08",
            x: 300,
            y: 600,
            width: 50,
            height: 100,
            draw: function() {
                ctx.fillStyle = this.color;
                ctx.fillRect(this.x, this.y, this.width, this.height);
            }
        }


        function update() {
            if (keys[39]) {
                if (velX < speed) {
                velX+=3;
                }
            }
            if (keys[37]) {
                if (velX > -speed) {
                    velX--;
                }
            }
            if (keys[32]) {
                velY -= 1.5;
                velY += 1;
            }
            velX *= friction;
            xx += velX;
            yy += velY;


            physics();
            ctx.clearRect(0,0,canvas.width, canvas.height);
            ctx.rect(0,0,canvas.width, canvas.height);
            ctx.fillStyle = "#EEE3B9";
            ctx.fill();
            floor.draw();
            bucket.draw();
            player.draw();

            if  (collision(player, bucket)) {
                console.log('collision');
            }


            setTimeout(update, 10);
        }

        update();
        document.body.addEventListener("keydown", function (e) {
                keys[e.keyCode] = true;
        });
        document.body.addEventListener("keyup", function (e) {
                keys[e.keyCode] = false;
        });
    </script>
</body>

3 ответа

Решение

Я могу только добавить к тому, что markE уже говорит в своем ответе (мы, кажется, работаем над этим одновременно:)).

Сначала я укажу на более серьезную ошибку в коде:

Ты используешь fill() без beginPath() первый. Это быстро замедлит цикл кода. замещать fill() с fillRect() вместо.

Также нет необходимости использовать clearRect() когда следующая операция рисования заполняет весь холст:

//ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#EEE3B9";
ctx.fillRect(0, 0, canvas.width, canvas.height);

Вы используете глобальные переменные xx / yy, Одна из основных целей использования объектов - избегать глобальных переменных. Метод рендеринга использует то же самое, поэтому он работает, но локальные свойства проигрывателя никогда не обновляются. Удалить глобальные переменные и работать только с объектами x а также y (Я бы порекомендовал некоторый рефакторинг, чтобы вы могли передать объект в physics() и т. д. вместо того, чтобы делать глобальные ссылки).

var xx = 50;
var yy = 100;

Для того, чтобы:

var player = {
  x: 50,    // numbers cannot be referenced, their value
  y: 100,   // is copied. Use these properties directly instead..
  // ...
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);  // and here..
  }
}

а также

function physics() {
  //...
  player.y += velocity;

  if (player.y > 597) {
    //...
    player.y = 597;
  }
}

и в цикле:

player.x += velX;
player.y += velY;

добавлять preventDefault() в ваших клавишных обработчиках, чтобы предотвратить перемещение клавиш, чтобы повлиять на прокрутку окна браузера (не у всех пользователей большой экран, поэтому левая / правая клавиша также будет влиять на прокрутку).

document.body.addEventListener("keydown", function(e) {
  e.preventDefault();
  ...

И, конечно же, использовать requestAnimationFrame зациклить анимацию.

Я также рекомендовал бы использовать объект функции, который может быть создан, поскольку у вас есть методы во всех них. С помощью функционального объекта вы можете создать прототип draw() метод и разделить его между всеми экземплярами без необходимости дополнительной памяти (и это хорошо для внутреннего оптимизатора). Это если вы планируете нарисовать больше, чем эти три объекта.

Обновленный код

var canvas = document.getElementById("mycanvas");
var ctx = canvas.getContext('2d');

var velY = 0;
var velX = 0;
var speed = 6;
var friction = 0.7;
var keys = [];

var velocity = 0;
var acceleration = 1;

function physics() {
  velocity += acceleration;
  player.y += velocity;

  if (player.y > 597) {
    var temp = 0;
    temp = velocity / 4;
    velocity = -temp;
    player.y = 597;
  }
}

function collision(first, second) {
  return !(first.x > second.x + second.width || first.x + first.width < second.x || first.y > second.y + second.height || first.y + first.height < second.y);
}

var player = {
  color: "#2B2117",
  x: 50,
  y: 100,
  width: 75,
  height: 75,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

var floor = {
  color: "#A67437",
  x: 0,
  y: 670,
  width: 1280,
  height: 80,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

var bucket = {
  color: "#B25E08",
  x: 300,
  y: 600,
  width: 50,
  height: 100,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}

function update() {
  if (keys[39]) {
    if (velX < speed) {
      velX += 3;
    }
  }
  else if (keys[37]) {
    if (velX > -speed) {
      velX -= 3;
    }
  }
  else if (keys[32]) {
    velY -= 1.5;
    velY += 1;
  }
  velX *= friction;

  player.x += velX;
  player.y += velY;
  physics();

  //ctx.clearRect(0, 0, canvas.width, canvas.height); // not really needed
  ctx.fillStyle = "#EEE3B9";
  ctx.fillRect(0, 0, canvas.width, canvas.height);    // as this clears too...
  floor.draw();
  bucket.draw();
  player.draw();

  if (collision(player, bucket)) {
    console.log('collision');
  }

  requestAnimationFrame(update);
}
update();

document.body.addEventListener("keydown", function(e) {
  e.preventDefault();
  keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function(e) {
  e.preventDefault();
  keys[e.keyCode] = false;
});
#mycanvas {outline: 1px solid #000;}
<canvas id="mycanvas" width="1280" height="750"></canvas>

С вашей функцией столкновения проблем нет - она ​​будет правильно распознавать столкновение между любыми двумя не повернутыми прямоугольниками.

Визуально ваш черный прямоугольник падает на пол, отскакивает один или два раза, а затем оседает на полу.

3 вопроса, хотя...

  1. Я вижу, что ваш setTimeout настроен на срабатывание каждые 10 мс. Это немного быстро, так как дисплей будет обновляться только примерно каждые 1000/60=16,67 мс, поэтому ваш setTimeout должен быть не менее 16,67. Кроме того, вы можете рассмотреть возможность использования requestAnimationFrame вместо setTimeout, потому что rAF синхронизируется с дисплеем и дает лучшую производительность.

  2. Один сбой дизайна, даже если пользователь постоянно удерживает нажатой правую клавишу, чтобы максимально быстро перемещать прямоугольник в направлении сегмента, прямоугольнику никогда не дается достаточно "дельта Х", чтобы попасть в блок.

  3. Вы тестируете столкновения с player а также bucket но вы не обновляете player.x & player.y. Поэтому ваше столкновение () проверяет только начальную позицию игрока, а не его текущую позицию.

    // in update()
    ...
    player.x=xx;
    player.y=yy;
    

Демонстрация переработанного кода:

var canvas = document.getElementById("mycanvas");
var ctx = canvas.getContext('2d');

var xx = 50;
var yy = 100;

var velY = 0;
var velX = 0;
var speed = 6;
var friction = 0.7;
var keys = [];

var velocity = 0;
var acceleration = 1;




function physics() {
  velocity+=acceleration;
  yy += velocity;

  if(yy   >   597) {
    var temp =0;
    temp =velocity/4;
    velocity=-temp;
    yy =    597;
  }
}


function collision(first, second){
  return !(first.x > second.x + second.width || first.x + first.width < second.x || first.y > second.y + second.height || first.y + first.height < second.y);
}


var player = {
  color: "#2B2117",
  x: xx,
  y: yy,
  width: 75,
  height: 75,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(xx, yy, this.width, this.height);
  }
}

var floor = {
  color: "#A67437",
  x: 0,
  y: 670,
  width: 1280,
  height: 80,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}


var bucket = {
  color: "#B25E08",
  x: 50,
  y: 600,
  width: 50,
  height: 100,
  draw: function() {
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }
}


function update() {
  if (keys[39]) {
    if (velX < speed) {
      velX+=3;
    }
  }
  if (keys[37]) {
    if (velX > -speed) {
      velX--;
    }
  }
  if (keys[32]) {
    velY -= 1.5;
    velY += 1;
  }
  velX *= friction;
  xx += velX;
  yy += velY;

  //
  player.x=xx;
  player.y=yy;


  physics();
  ctx.clearRect(0,0,canvas.width, canvas.height);
  ctx.rect(0,0,canvas.width, canvas.height);
  ctx.fillStyle = "#EEE3B9";
  ctx.fill();
  floor.draw();
  bucket.draw();
  player.draw();

  if  (collision(player, bucket)) {
    alert('collision when player at:'+player.x+"/"+player.y);
  }else{
    setTimeout(update, 1000/60*3);
  }


}

update();
document.body.addEventListener("keydown", function (e) {
  keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function (e) {
  keys[e.keyCode] = false;
});
<canvas id="mycanvas" width="1280" height="750"></canvas>

Скопировать и вставить

<html>
<head>
<title> new </title>
<script>
var ctx1;
var ctx;

var balls = [
{x:50, y:50, r:20, m:1, vx:1, vy:1.5},
{x:200, y:80, r:30, m:1, vx:-1, vy:0.3},
];

function start() 
{
    ctx1 = document.getElementById("ctx");
    ctx = ctx1.getContext("2d");
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    setInterval(draw, 10); 
}

function draw()
{
    ctx.clearRect(0, 0, ctx1.width, ctx1.height);
    for (var a = 0; a < balls.length; a ++) 
{
    for (var b = 0; b < balls.length; b ++)
{
    if (a != b) 
{
    var distToBalls = Math.sqrt(Math.pow(balls[a].x - balls[b].x, 2) + Math.pow(balls[a].y - balls[b].y, 2));
    if (distToBalls <= balls[a].r + balls[b].r) 
{
    var newVel = getBallCollision(balls[a], balls[b]); 
    balls[a].vx = newVel.ball1.vx;
    balls[a].vy = newVel.ball1.vy;
    balls[b].vx = newVel.ball2.vx;
    balls[b].vy = newVel.ball2.vy;
    balls[a].x += balls[a].vx;
    balls[a].y += balls[a].vy;
    balls[b].x += balls[b].vx;
    balls[b].y += balls[b].vy;
}
}
}
}

for (var a = 0; a < balls.length; a ++) 
{
    ctx.beginPath();
    ctx.arc(balls[a].x, balls[a].y, balls[a].r, 0, Math.PI*2, true);
    ctx.fill();
    ctx.closePath();
    if (balls[a].x <= balls[a].r) balls[a].vx *= -1;
    if (balls[a].y <= balls[a].r) balls[a].vy *= -1;
    if (balls[a].x >= ctx1.width - balls[a].r) balls[a].vx *= -1;
    if (balls[a].y >= ctx1.height - balls[a].r) balls[a].vy *= -1;
    balls[a].x += balls[a].vx;
    balls[a].y += balls[a].vy;
}
}
function getBallCollision(b1, b2) 
{
var dx = b1.x - b2.x;
var dy = b1.y - b2.y;
var dist = Math.sqrt(dx*dx + dy*dy);
if (Math.abs(dy) + Math.abs(dx) != 0 && dist <= b1.r + b2.r) 
    {
    var colAng = Math.atan2(dy, dx);
    var sp1 = Math.sqrt(b1.vx*b1.vx + b1.vy*b1.vy);
    var sp2 = Math.sqrt(b2.vx*b2.vx + b2.vy*b2.vy);
    var dir1 = Math.atan2(b1.vy, b1.vx);
    var dir2 = Math.atan2(b2.vy, b2.vx);
    var vx1 = sp1 * Math.cos(dir1 - colAng);
    var vy1 = sp1 * Math.sin(dir1 - colAng);
    var vx2 = sp2 * Math.cos(dir2 - colAng);
    var vy2 = sp2 * Math.sin(dir2 - colAng);
    var fvx1 = ((b1.m - b2.m) * vx1 + (2 * b2.m) * vx2) / (b1.m + b2.m);
    var fvx2 = ((2 * b1.m) * vx1 + (b2.m - b1.m) * vx2) / (b1.m + b2.m);
    var fvy1 = vy1;
    var fvy2 = vy2;
    b1.vx = Math.cos(colAng) * fvx1 + Math.cos(colAng + Math.PI/2) * fvy1;
    b1.vy = Math.sin(colAng) * fvx1 + Math.sin(colAng + Math.PI/2) * fvy1;
    b2.vx = Math.cos(colAng) * fvx2 + Math.cos(colAng + Math.PI/2) * fvy2;
    b2.vy = Math.sin(colAng) * fvx2 + Math.sin(colAng + Math.PI/2) * fvy2;
    return { ball1:b1, ball2:b2 }; 
    }
else return false;
}
</script>
</head>
<body onLoad="start();">
<canvas id="ctx" width="500" height="300" style = "border : 2px solid #854125 ;"> </canvas>
</body>
</html>
Другие вопросы по тегам