HTML5 Canvas 碰撞检测

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>

您的碰撞函数没有问题——它会正确识别任何 2 个未旋转的矩形之间的碰撞。

从视觉上看,您的黑色矩形会掉到地板上,弹跳一两次,然后落在地板上。

虽然有 3 个问题...

  1. 我确实看到您的 setTimeout 设置为每 10 毫秒触发一次。这有点快,因为显示大约每 1000/60=16.67 毫秒才会刷新一次——因此您的 setTimeout 应该至少为 16.67。此外,您可以考虑使用 requestAnimationFrame 而不是 setTimeout,因为 rAF 会与显示器同步并提供更好的性能。

  2. 一个设计错误,即使用户不断按住右键以尽可能快地将矩形移向桶,矩形也永远不会得到足够的 "delta X" 来命中桶.

  3. 您正在测试与 playerbucket 的冲突,但您没有更新 player.x 和 player.y。因此,您的 collision() 仅测试玩家的初始位置,而不是其当前位置。

    // 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>

我只能附加到 markE 在他的回答中已经说过的内容(我们似乎正在同时进行此工作:))。

我先指出一个比较严重的代码错误:

您正在使用 fill(),但没有先使用 beginPath()。这将迅速减慢代码循环。将 fill() 替换为 fillRect()

另外,下一次绘制操作填满整个canvas:

时,也不需要使用clearRect()
//ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#EEE3B9";
ctx.fillRect(0, 0, canvas.width, canvas.height);

您正在使用全局变量 xx/yy。使用对象的一个​​要点是避免使用全局变量。 render 方法使用相同的方法,因此它似乎可以工作,但播放器的本地属性永远不会更新。删除全局变量,只使用对象的 xy(我建议进行一些重构,这样你就可以将对象传递给 physics() 等,而不是进行全局引用)。

var xx = 50;<br>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() 以防止按键移动影响浏览器的 window 滚动(并非所有用户都有大屏幕,因此 left/right 按键也会影响滚动)。

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>

复制粘贴

<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>