《canvas》之第16章 碰撞检测
- 第16章 碰撞检测
- 16.1 碰撞检测简介
- 16.2 外接矩形判定法
- 16.3 外接圆判定法
- 16.4 多物体碰撞
- 16.4.1 排列组合
- 16.4.2 多物体碰撞
第16章 碰撞检测
16.1 碰撞检测简介
检测物体与物体间是否发生碰撞。
16.2 外接矩形判定法
将物体抽象成矩形来检测碰撞。
//外接矩形判定法(碰撞检测)
window.tools.checkRect = function (rectA, rectB) {
return !(rectA.x + rectA.width < rectB.x ||
rectB.x + rectB.width < rectA.x ||
rectA.y + rectA.height < rectB.y ||
rectB.y + rectB.height < rectA.y);
}
Ball.prototype = {
getRect:function() {//获取包含小球的最小矩形
var rect = {
x: this.x - this.radius,
y: this.y - this.radius,
width: this.radius * 2,
height: this.radius * 2
}
return rect;
}
}
- 外接矩形碰撞检测
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var msg = document.getElementById("msg");
//定义一个位置固定的小球ballA
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以及它的外接矩形
ballA.fill(cxt);
cxt.strokeRect(rectA.x, rectA.y, rectA.width, rectA.height);
//定义一个位置不固定的小球ballB,小球追随鼠标
var ballB = new Ball(mouse.x, mouse.y, 30);
//获取ballB的外接矩形
var rectB = ballB.getRect();
//绘制ballB以及它的外接矩形
ballB.fill(cxt);
cxt.strokeRect(rectB.x, rectB.y, rectB.width, rectB.height);
//碰撞检测
if (tools.checkRect(rectA, rectB)) {
msg.innerHTML = "撞上了";
} else {
msg.innerHTML = "没撞上";
}
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="270" height="200" style="border:1px solid silver;"></canvas>
<p id="msg"></p>
</body>
</html>
- box.js
function Box(x, y, width, height, color) {
//小球中心的x坐标,默认值为0
this.x = x || 0;
//小球中心的y坐标,默认值为0
this.y = y || 0;
//小球宽度,默认值为80
this.width = width || 80;
//小球高度,默认值为40
this.height = height || 40;
this.color = color || "red";
//x和y速度
this.vx = 0;
this.vy = 0;
}
Box.prototype = {
//绘制“描边”矩形
stroke: function (cxt) {
cxt.save();
cxt.beginPath();
cxt.rect(this.x, this.y, this.width, this.height);
cxt.closePath();
cxt.strokeStyle = this.color;
cxt.stroke();
cxt.restore();
},
//绘制“填充”矩形
fill: function (cxt) {
cxt.save();
cxt.beginPath();
cxt.rect(this.x, this.y, this.width, this.height);
cxt.closePath();
cxt.fillStyle = this.color;
cxt.fill();
cxt.restore();
}
}
- 俄罗斯方块
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/box.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//定义一个用来存放方块的数组boxes
var boxes = [];
//定义一个“当前活动”的方块
var activeBox = createBox();
//定义方块的Y轴速度
var vy = 2;
//定义一个函数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 = tools.getRandomColor();
var box = new Box(x, y, width, height, color);
//添加到数组boxes中
boxes.push(box);
return box;
}
(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)”碰上了,则创建新的方块*/
if (activeBox !== box && tools.checkRect(activeBox, box)) {
activeBox.y = box.y - activeBox.height;
activeBox = createBox();
}
box.fill(cxt);
});
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="270" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>
- 键盘控制
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/box.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//定义一个用来存放方块的数组boxes
var boxes = [];
//定义一个“当前活动”的方块
var activeBox = createBox();
//定义方块的Y轴速度
var vy = 1.5;
//加入鼠标控制
var key = tools.getKey();
window.addEventListener("keydown", function () {
switch (key.direction) {
case "down":
activeBox.y += 5;
break;
case "left":
activeBox.x -= 10;
break;
case "right":
activeBox.x += 10;
break;
}
}, false);
//定义一个函数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 = tools.getRandomColor();
var box = new Box(x, y, width, height, color);
//添加到数组boxes中
boxes.push(box);
return box;
}
(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)”碰上了,则创建新的方块*/
if (activeBox !== box && tools.checkRect(activeBox, box)) {
activeBox.y = box.y - activeBox.height;
activeBox = createBox();
}
box.fill(cxt);
});
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="270" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>
16.3 外接圆判定法
物体看成圆来判定是否碰撞。
//外接圆判定法(碰撞检测)
window.tools.checkCircle = function (circleB, circleA) {
var dx = circleB.x - circleA.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;
}
}
- 外接圆判断
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var txt = document.getElementById("txt");
//定义一个位置固定的小球
var ballA = new Ball(cnv.width / 2, cnv.height / 2, 20, "#FF6699");
var mouse = tools.getMouse(cnv);
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//定义一个位置不固定的小球,小球追随鼠标
var ballB = new Ball(mouse.x, mouse.y, 20, "#66CCFF");
//碰撞检测
if (tools.checkCircle(ballB, ballA)) {
txt.innerHTML = "撞上了";
} else {
txt.innerHTML = "没撞上";
}
ballA.fill(cxt);
ballB.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
<p id="txt"></p>
</body>
</html>
- 小球碰撞
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//定义两个小球:ballA和ballB
var ballA = new Ball(0, cnv.height / 2, 12, "#FF6699");
var ballB = new Ball(cnv.width, cnv.height / 2, 12, "#66CCFF");
//定义小球X轴速度
var vx = 2;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ballA.x += vx;
ballB.x += -vx;
//如果发生碰撞,则速度取反
if (tools.checkCircle(ballB, ballA)) {
vx = -vx;
}
ballA.fill(cxt);
ballB.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="270" height="200" style="border:1px solid silver;"></canvas>
</body>
</html>
16.4 多物体碰撞
16.4.1 排列组合
n个物体,n×(n-1)/2种碰撞情况。
16.4.2 多物体碰撞
balls.forEach(function(ballA, i) {
for (var j = i + 1; j < balls.length; j++) {
var ballB = balls[j];
if (tools.checkCircle(ballB, ballA)) {
//...
}
}
});
- 存在小球重叠bug
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var n = 8;
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 = Math.random() * cnv.height;
ball.radius = 10;
ball.color = tools.getRandomColor();
ball.vx = Math.random() * 6 - 3;
ball.vy = Math.random() * 6 - 3;
//添加到数组balls中
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 = -ballA.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 > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
ball.vx = -ball.vx;
}
//碰到上边界
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.vy = -ball.vy;
//碰到下边界
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.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>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 碰撞小球加上偏移量
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var n = 8;
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 = Math.random() * cnv.height;
ball.radius = 10;
ball.color = tools.getRandomColor();
ball.vx = Math.random() * 6 - 3;
ball.vy = Math.random() * 6 - 3;
//添加到数组balls中
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 = -ballA.vy;
ballB.vx = -ballB.vx;
ballB.vy = -ballB.vy;
//每次碰撞,小球的x、y都加入偏移量,避免相互重叠
if (ballA.vx > 0) {
ballA.x += 5;
} else {
ballA.x -= 5;
}
if (ballA.vy > 0) {
ballA.y += 5;
} else {
ballA.y -= 5;
}
if (ballB.vx > 0) {
ballB.x += 5;
} else {
ballB.x -= 5;
}
if (ballB.vy > 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 > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
ball.vx = -ball.vx;
}
//碰到上边界
if (ball.y < ball.radius) {
ball.y = ball.radius;
ball.vy = -ball.vy;
//碰到下边界
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.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>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>