十六.碰撞检测,外接圆检测,外接矩形检测,多个物体两两比较
简单的碰撞检测的常见方法有:
1.外接矩形判断
2.外接圆判断
一,外接矩形判断
判断两个矩形是否发生碰撞,只需要判断:两个矩形左上角的坐标所处的范围,如果两个矩形的左上角的坐标满足一定的条件,两个矩形就发生碰撞
语法:
window.tools.checkRect=function(rectA,rectB){
return (
rectA.x+rectA.width<rectB.x||
rectA.y+rectA.height<rectB.y||
rectB.x+rectB.width<rectA.x||
rectB.y+rectB.height<rectA.y
);
};
代码在tools.js里面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p id="msg"></p>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assit/arrow.js"></script>
<script src="../assit/tools.js"></script>
<script src="../assit/ball.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var msg=document.getElementById("msg");
//定义一个位置固定的小球
var ballA=new Ball(cnv.width/2,cnv.height/2,30);
//获取ballA的外接矩形
var rectA=ballA.getRect();
var mouse=tools.getMouse(cnv);
;(function frame(){
window.requestAnimationFrame(frame);
cxt.clearRect(0,0,cnv.width,cnv.height);
ballA.fill(cxt);
//绘制ballA以及他的外接矩形
cxt.strokeRect(rectA.x,rectA.y,rectA.width,rectA.height);
//定义一个位置不固定的小球,小球追随鼠标
var ballB=new Ball(mouse.x,mouse.y,30);
//获取ballB的外接圆
var rectB=ballB.getRect();
//绘制ballB以及他的外接矩形
cxt.strokeRect(rectB.x,rectB.y,rectB.width,rectB.height);
ballB.fill(cxt);
//碰撞检测
if(tools.checkRect(rectA,rectB)){
msg.innerHTML="碰到了";
}
else{
msg.innerHTML="没碰到";
}
})();
}
</script>
</body>
</html>
外接矩形的应用(简单俄罗斯方块)在线demo
这里新增了一个Box.js文件来生成随机宽高和随机颜色的方块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<canvas id="canvas" width="300" height="400"></canvas>
<script src="../assit/arrow.js"></script>
<script src="../assit/tools.js"></script>
<script src="../assit/ball.js"></script>
<script src="../assit/Box.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//定义一个用来存放方块的数组
var boxes = [];
//定义一个当前的方块
var activeBox = createBox();
//定于y轴方向速度
var vy = 1;
//定义一个函数createBox,用户创建新的方块
function createBox() {
var x = Math.random() * cnv.width;
var y = 0;
var width = Math.random() * 40 + 10;
var height = Math.random() * 40 + 10;
var color = window.tools.getRandomColor();
console.log(color)
var box = new Box(x, y, width, height, color);
//添加到数组
boxes.push(box);
return box;
};
//获取键盘
//获取按键方向
var key = tools.getKey();
window.addEventListener("keydown", function() {
cxt.clearRect(0, 0, cnv.width, cnv.height);
console.log("这里只执行一次")
//根据key.direction的值,判断物体的移动方向、
switch (key.direction) {
case "down":
activeBox.y += 5;
break;
case "left":
activeBox.x -= 5;
break;
case "right":
activeBox.x += 5;
break;
}
}, false);
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
activeBox.y += vy;
//边界检测,如果到底底部,则创建一个新的box
if (activeBox.y > cnv.height - activeBox.height) {
activeBox.y = cnv.height - activeBox.height;
activeBox = createBox();
}
//遍历数组boxes,一遍单独处理每一个BOX
boxes.forEach(function(box) {
//如果单钱遍历的box不是活动(activeBox),并且单钱遍历的方块与“活动方块(activeBox)”碰上,则创建新的方块
//console.log(activeBox,"box",box)
if (activeBox !== box && tools.checkRect(activeBox, box)) {
activeBox.y = box.y - activeBox.height;
activeBox = null;
activeBox = createBox();
}
box.fill(cxt);
})
})();
}
</script>
</body>
</html>
二,外接圆判断方法
如果一个物体是一个圆或者接近圆,我们可以把这个物体抽象为一个圆,然后用两个圆来进行碰撞检测
在实际开发中,什么时候用外接矩形,什么时候用圆取决于物体的形状,那个方法误差大,就用那个
外接圆碰撞检测只需要判断两个圆的圆心距
语法:
window.tools.checkCircle=function(circleB,circleA){
var dx=circleB.x-cirlceA.x;
var dy=circleB.y-circleA.y;
var distance=Math.sqrt()dx*dx+dy*dy;
if(distance<(circleA.radius+circleB.radius)){
return true;
}
else{
return false;
}
}
外接圆碰撞检测,在线demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="txt"></div>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assit/arrow.js"></script>
<script src="../assit/tools.js"></script>
<script src="../assit/ball.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//定义一个位置固定的小球
var ballA = new Ball(10, cnv.height / 2, 20, "#FF6699");
var ballB = new Ball(cnv.width - 10, cnv.height / 2, 20, "#000000");
var vx=2;//定义小球的X轴速度
;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ballA.x+=vx;
ballB.x-=vx;
//如果发生碰撞,速度都取反,tools.checkCircle(ballB,ballA)
if(tools.checkCircle(ballB,ballA) ||(ballB.x>cnv.width - 10&&ballA.x<10)){
vx=-vx;
}
ballA.fill(cxt);
ballB.fill(cxt);
})();
}
</script>
</body>
</html>
三,多个物体两两比较碰撞检测
两两比较有个公式是:
n*(n-1)/2,这个公式是我四年级时候学的,现在还很印象深刻。
意思是N个物体两两比较的时候需要对比n*(n-1)/2次
下面在回顾下N年前的知识,同时用代码展示一次
案例,现有小球5个,分别是b0,b1,b2,b3,b4,两两比较分别有一下几种情况
b0与 b0,b1,b2,b3,b4,
b1与 b0,b1,b2,b3,b4,
b2与 b0,b1,b2,b3,b4,
b3与 b0,b1,b2,b3,b4,
b4与 b0,b1,b2,b3,b4,
对应代码,balls=[ b0,b1,b2,b3,b4];
balls.forEach(ballA,i){
for(var j=0;balls.length;j++){
var ballB=balls[j];
if(tools.checkCircle(ballA,ballB)){
//******
}
}
}
弊端:
1.每个求都和自己对比了
2.b0和b1对比后,b1还会和b0对比
解决方法:
b0与b1,b2,b3,b4
b1与b2,b3,b4
b2与b3,b4
b3与b4
b4与没有任何物体对比
代码:
balls.forEach(function(ballA,i){
for(var j=0;balls.length;j++){
var ballB=balls[i];
if(i!j&&tools.checkCircle(ballA,ballB)){
//*****
}
}
})
多个外接圆碰撞(有bug)在线demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="txt"></div>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assit/arrow.js"></script>
<script src="../assit/tools.js"></script>
<script src="../assit/ball.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var n = 10;
var balls = [];
//生成N个小球,小球的x、y、color、vx,vy素质取得都是随机值
for (var i = 0; i < n; i++) {
ball = new Ball();
ball.x = Math.random() * cnv.width;
ball.y = 10;
console.log(ball.x,ball.y)
ball.radius = 10;
ball.color = tools.getRandomColor();
ball.vx = Math.random() * 6 - 3;
ball.vy = Math.random() * 6 - 3;
//添加到数组
balls.push(ball);
}
//碰撞检测(小球和小球)
function checkCollision(ballA, i) {
for (var j = i + 1; j < balls.length; j++) {
var ballB = balls[j];
//如果两个小球碰撞后,则碰撞的vx,vy都取反
if (tools.checkCircle(ballB, ballA)) {
ballA.vx = -ballA.vx;
ballA.vy = -ballB.vy;
ballB.vx = -ballB.vx;
ballB.vy = -ballB.vy;
}
}
};
//边界检测(小球和边界)
function checkBorder(ball) {
//碰撞到最左边
if (ball.x < ball.radius) {
ball.x = ball.radius;
ball.vx = -ball.vx;
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
ball.vx = -ball.vx;
}
//碰到最顶上
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.vy = -ball.vy;
console.log("yxiaoyu")
} else if (ball.y > cnv.height - ball.radius) {
console.log("y大于")
ball.y = cnv.height - ball.radius;
ball.vy = -ball.vy;
}
}
//绘制小球
function drawBall(ball){
ball.fill(cxt);
ball.x+=ball.vx;
ball.y+=ball.vy;
}
;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//碰撞检测
balls.forEach(checkCollision);
//边界检测
balls.forEach(checkBorder);
balls.forEach(drawBall);
})();
}
</script>
</body>
</html>
会有小球重叠的问题,解决方案是碰撞的同事把小球加上一个偏移量(重叠问题,可能是物体的速度问题),但是如果物品再多一点,还是有这个问题
改良的添加偏移量的在线demo:
这里我故意吧个数调多了,如果想看正常的可以把个数改小一点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="txt"></div>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="../assit/arrow.js"></script>
<script src="../assit/tools.js"></script>
<script src="../assit/ball.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var n = 20;
var balls = [];
//生成N个小球,小球的x、y、color、vx,vy素质取得都是随机值
for (var i = 0; i < n; i++) {
ball = new Ball();
ball.x = Math.random() * cnv.width;
ball.y = 10;
console.log(ball.x, ball.y)
ball.radius = Math.random()*10+10;
ball.color = tools.getRandomColor();
ball.vx = Math.random() * 6 - 3;
ball.vy = Math.random() * 6 - 3;
//添加到数组
balls.push(ball);
}
//碰撞检测(小球和小球)
function checkCollision(ballA, i) {
for (var j = i + 1; j < balls.length; j++) {
var ballB = balls[j];
//如果两个小球碰撞后,则碰撞的vx,vy都取反
if (tools.checkCircle(ballB, ballA)) {
ballA.vx = -ballA.vx;
ballA.vy = -ballB.vy;
ballB.vx = -ballB.vx;
ballB.vy = -ballB.vy;
//每次碰撞,小球的,x,y都添加一个半径偏移量,避免相互重叠,只是一定程度减少重叠,不能根治
if (ballA.vx > 0) {
ballA.x += 10;
} else {
ballA.x -= 10;
}
if (ballA.y > 0) {
ballA.y += 10;
} else {
ballA.y -= 10;
}
if (ballB.vx > 0) {
ballB.x += 10;
} else {
ballB.x -= 10;
}
if (ballB.y > 0) {
ballB.y += 5;
} else {
ballB.y -= 5;
}
}
}
};
//边界检测(小球和边界)
function checkBorder(ball) {
//碰撞到最左边
if (ball.x < ball.radius) {
ball.x = ball.radius;
ball.vx = -ball.vx;
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
ball.vx = -ball.vx;
}
//碰到最顶上
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.vy = -ball.vy;
console.log("yxiaoyu")
} else if (ball.y > cnv.height - ball.radius) {
console.log("y大于")
ball.y = cnv.height - ball.radius;
ball.vy = -ball.vy;
}
}
//绘制小球
function drawBall(ball) {
ball.fill(cxt);
ball.x += ball.vx;
ball.y += ball.vy;
}
;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//碰撞检测
balls.forEach(checkCollision);
//边界检测
balls.forEach(checkBorder);
balls.forEach(drawBall);
})();
}
</script>
</body>
</html>