1. 程式人生 > >【H5/JS】遊戲常用演算法-碰撞檢測-畫素檢測演算法

【H5/JS】遊戲常用演算法-碰撞檢測-畫素檢測演算法

使用畫素碰撞檢測法算是最精確的演算法了,當然,帶來的代價也是比較明顯的,那就是效率上的低下。除非是在極為特殊的情況下,要求使用非常精確的碰撞,否則,一般情況下在遊戲中是不建議使用這種演算法,特別是在執行效率不太高的HTML5遊戲中。

一般來說在使用畫素碰撞檢測之前會使用AABB矩形包圍盒先檢測兩個精靈是否有碰撞,如果AABB包圍盒檢測沒有碰撞,那一定是沒有碰撞到,反之,則不一定,需要進一步進行畫素檢測。如下圖所示,很明顯,雖然兩個精靈的包圍盒發生了碰撞,但兩個精靈本身沒有發生碰撞,所以在這種精靈的形狀極為不規則的情況下,除非使用多邊形包圍盒,並且需要多邊形和精靈的形狀極為接近,才能夠獲取好的效果,而且,別忘了,多邊形只適合凸多邊形的情況。


這樣,我們就只能採用畫素檢測演算法達到精確檢測的目的。接下來,先來看看畫素碰撞的原理,首先,我們知道所有的精靈都是由畫素點組成,而在canvas的畫素資料中每個點都是由RGBA四個資料組成。如果某個點的A(alpha值,透明度)為0則表示該點是透明的,比如在圖6-19中兩個精靈的空白部分的點的A值為0。如果兩個精靈發生碰撞,則表示兩個精靈有畫素點發生了重疊,即兩個精靈的畫素點座標相同,如下圖所示。


根據這個原理,基本上我們可以採取以下步驟來進行檢測。

(1)選擇需要檢測的兩個精靈。

(2)先檢測兩個精靈是否發生包圍盒碰撞,如果沒有則退出,否則獲取兩個矩形的相交區域,並繼續。

(3)把一個精靈繪製到和遊戲螢幕等大的空白的後臺緩衝區中,獲取緩衝區中在相交區域的畫素資料。

(4)清除後臺緩衝區。

(5)對另一個精靈進行步驟3的操作。

(6)得到兩個精靈在同一個相交矩形的畫素資料後,迴圈比較每一個畫素點,如果兩個精靈在同一位置的透明度不都是0,則表示兩個精靈有相交,退出迴圈,返回真。

需要注意的一點是,在第3步獲取相交區域的畫素資料中,需要在後臺另外的一個和遊戲螢幕等大的空白區域中繪製,而不能直接獲取遊戲畫面中的相交區域,因為兩個精靈在相交區域中的畫素已經發生了重疊【包圍盒】。

由以上的演算法可以看出,在進行畫素檢測的時候,需要另外一個緩衝區,把需要檢測的精靈繪製一次,需要清空緩衝區,最後還要使用一個for迴圈檢測畫素。如果相交區域為100×100個畫素點,則最壞的情況需要迴圈10 000 次,由此看來,這真不是一個省時的工作。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta charset="UTF-8">
    <title>盒包圍碰撞演算法-畫素檢測演算法</title>
    <style>
        #stage {
            border: 1px solid lightgray;
        }
    </style>
</head>
<body>
<h1>包圍盒是否碰撞:<span class="hitTestBox">否</span></h1>
<h1>畫素檢測是否碰撞:<span class="hitTestPixel">否</span></h1>
<canvas id="stage"></canvas>
<img src="./images/penguin.png" alt="penguinImg" id="penguinImg" style="display:none" />
<img src="./images/giraffe.png" alt="giraffeImg" id="giraffeImg" style="display:none" />
</body>
<script>
    window.onload = function () {
        var stage = document.querySelector('#stage'),
            ctx = stage.getContext('2d');
        stage.width = 600;
        stage.height = 600;

        //建立後臺canvas
        var bC = document.createElement("canvas");
        bC.width = stage.width;
        bC.height = stage.height;
        var backBuf = bC.getContext("2d");

        var penguin = document.querySelector('#penguinImg'),giraffe = document.querySelector('#giraffeImg');
        var penguinImg = {
            x:stage.width/2,
            y:stage.height/2,
            r:128,
            data:null
        },giraffeImg = {
            x:100,
            y:100,
            r:128,
            data:null
        };

        function drawImageBox(img,x,y,width,height){
            ctx.beginPath();
            ctx.rect(x,y,width,height);
            ctx.stroke();
            ctx.drawImage(img,x,y,width,height);
        }
        //快取畫布繪製方法
        function drawImageBC(img,x,y,width,height){
            backBuf.clearRect(0,0,stage.width,stage.height);
            backBuf.save();
            backBuf.drawImage(img,x,y,width,height);
            backBuf.restore();
        }

        //獲取兩個矩形相交區域
        function getInRect(x1,y1,x2,y2,x3,y3,x4,y4) {
            return [Math.max(x1,x3),Math.max(y1,y3),Math.min(x2,x4),Math.min(y2,y4)];
        }


        document.onkeydown = function (event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            //根據地圖陣列碰撞將測
            switch (e.keyCode) {
                case 37:
                    console.log("Left");
                    if (penguinImg.x > 0) {
                        penguinImg.x -= 2;
                    }
                    break;
                case 38:
                    console.log("Top");
                    if (penguinImg.y > 0) {
                        penguinImg.y -= 2;
                    }
                    break;
                case 39:
                    console.log("Right");
                    if (penguinImg.x < stage.width) {
                        penguinImg.x += 2;
                    }
                    break;
                case 40:
                    console.log("Bottom");
                    if (penguinImg.y < stage.height) {
                        penguinImg.y += 2;
                    }
                    break;

                default:
                    return false;
            }
        };
        
        stage.addEventListener('click', function (event) {
            var x = event.clientX - stage.getBoundingClientRect().left;
            var y = event.clientY - stage.getBoundingClientRect().top;
            penguinImg.x = x;
            penguinImg.y = y;
        });


        function update() {
            ctx.clearRect(0, 0, 600, 600);
            drawImageBox(giraffe,giraffeImg.x,giraffeImg.y,giraffeImg.r,giraffeImg.r);
            drawImageBox(penguin,penguinImg.x,penguinImg.y,penguinImg.r,penguinImg.r);
            document.querySelector('.hitTestBox').innerHTML = "否";
            document.querySelector('.hitTestPixel').innerHTML = "否";
            var rect = getInRect(giraffeImg.x,giraffeImg.y,giraffeImg.x+giraffeImg.r,giraffeImg.y+giraffeImg.r,penguinImg.x,penguinImg.y,penguinImg.x+penguinImg.r,penguinImg.y+penguinImg.r)

            //如果沒有相交則退出
            if(rect[0]>=rect[2]||rect[1]>=rect[3]) {

            } else{
                document.querySelector('.hitTestBox').innerHTML = "是";
                giraffeImg.data = null;
                penguinImg.data = null;
                //獲取精靈在相交矩形畫素資料
                drawImageBC(giraffe,giraffeImg.x,giraffeImg.y,giraffeImg.r,giraffeImg.r);
                giraffeImg.data = backBuf.getImageData(rect[0],rect[1],rect[2],rect[3]).data;
                drawImageBC(penguin,penguinImg.x,penguinImg.y,penguinImg.r,penguinImg.r);
                penguinImg.data = backBuf.getImageData(rect[0],rect[1],rect[2],rect[3]).data;
                for(var i=3;i<giraffeImg.data.length;i+=4)
                {
                    if(giraffeImg.data[i]>0&&penguinImg.data[i]>0){
                        document.querySelector('.hitTestPixel').innerHTML = "是";
                    }
                }

            }
            requestAnimationFrame(update);
        }

        update();
    };
</script>
</html>

線上預覽地址:https://github.com/krapnikkk/JS-gameMathematics