欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

十六.碰撞检测,外接圆检测,外接矩形检测,多个物体两两比较

程序员文章站 2022-03-01 17:49:50
...

简单的碰撞检测的常见方法有:

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

在线demo:

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


相关标签: 碰撞检测