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

【H5/JS】遊戲常用演算法-碰撞檢測-地圖格子演算法

這種演算法經常用於RPG(早期的《最終幻想》、《DQ》、《仙劍奇俠傳》)、SLG(《炎龍騎士團》、《超級機器人大戰》)、PUZ(《俄羅斯方塊》、《寶石謎陣》)型別的遊戲。這類遊戲中,通常情況下整個地圖都是由一些地圖塊元素組成,在製作的時候首先給製作出地圖所需要的最基本的元素進行編號,然後把這些編號的地圖塊組合起來就可以根據需要形成任意大小的地圖。

早期的RPG型別或者SLG型別的遊戲可以明顯地看出遊戲中的地圖是由一些小的地圖塊格子而成,採用這種方式組成地圖的好處是節約記憶體的使用,並且不需要太多的地圖元素就可以任意組合成足夠大的地圖,簡單靈活,缺陷就是最後製作出的地圖不太美觀。但實際上為了便於遊戲中的碰撞檢測,比如人物碰到NPC或者是碰到不可跨越的障礙等情況,在遊戲中實際上還是儲存了一張看不見的邏輯層。這個層的大小和地圖等大,並且也進行了格子劃分,主要目的就是為了碰撞檢測,通常我們在遊戲中對這個碰撞邏輯層使用一個數組描述資訊,使用 1 表示不可以通過,0表示可以通過,假設人物和NPC在遊戲中行走,這種情況下我們就很容易檢測地圖中人物是否碰到了NPC或者障礙物。

var mapArr = [
            1, 0, 0, 1,
            0, 0, 0, 1,
            0, 1, 0, 0,
            1, 0, 0, 1
        ];

程式碼如下:

<!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>
<canvas id="stage"></canvas>
</body>
<script>
    window.onload = function () {
        var stage = document.querySelector('#stage'),
            ctx = stage.getContext('2d');
        stage.width = 400;
        stage.height = 400;

        var mapArr = [
            1, 0, 0, 1,
            0, 0, 0, 1,
            0, 1, 0, 0,
            1, 0, 0, 1
        ],rectIdx = 5;

        //柵格線條
        function drawGrid(context, color, stepx, stepy) {
            context.strokeStyle = color;
            context.lineWidth = 0.5;

            for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) {
                context.beginPath();
                context.moveTo(i, 0);
                context.lineTo(i, context.canvas.height);
                context.stroke();
            }
            for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) {
                context.beginPath();
                context.moveTo(0, i);
                context.lineTo(context.canvas.width, i);
                context.stroke();
            }
        }

        function createRect(x, y, r, c) {
            ctx.beginPath();
            ctx.fillStyle = c;
            ctx.rect(x, y, r, r);
            ctx.fill();
        }

        document.onkeydown = function (event) {
            var e = event || window.event || arguments.callee.caller.arguments[0];
            //根據地圖陣列碰撞將測
            switch (e.keyCode){
                case 37:
                    console.log("Left");
                    if (rectIdx - 1 >= 0 && (rectIdx - 1) % 4 !== 3 && !mapArr[rectIdx - 1]) {
                        rectIdx -= 1;
                    }
                    break;
                case 38:
                    console.log("Top");
                    if (rectIdx - 4 >= 0 && !mapArr[rectIdx - 4]) {
                        rectIdx -= 4;
                    }
                    break;
                case 39:
                    console.log("Right");
                    if ((rectIdx + 1) % 4 !== 0 && !mapArr[rectIdx + 1]) {
                        rectIdx += 1;
                    }
                    break;
                case 40:
                    console.log("Bottom");
                    if (rectIdx + 4 < mapArr.length && !mapArr[rectIdx + 4]) {
                        rectIdx += 4;
                    }
                    break;

                default:
                    return false;
        }
        };

        function update() {
            ctx.clearRect(0, 0, 400, 400);
            drawGrid(ctx, 'lightgray', 100, 100);
            var rect = {
                x: rectIdx % 4 * 100,
                y: rectIdx % 4 === 0 ? rectIdx / 4 * 100 : Math.floor(rectIdx / 4) * 100,
                r: 100,
                c: "blue"
            };
            createRect(rect.x, rect.y, rect.r, rect.c);
            //根據地圖陣列建立色塊
            for (var i = 0, len = mapArr.length; i < len; i++) {
                if (mapArr[i]) {
                    createRect(i % 4 * 100, i % 4 === 0 ? i / 4 * 100 : Math.floor(i / 4) * 100, 100, "red");
                }
            }

            requestAnimationFrame(update);
        }

        update();
    };
</script>
</html>
線上預覽地址:https://github.com/krapnikkk/JS-gameMathematics

採用這種方式判斷邏輯極其簡單,效率也比較高,但不太精確,如果A物體的大小比格子小很多,則物體行動的時候可能看起來離B物體有些距離就無法行走了,所以做這種型別遊戲最好保證格子足夠小或者保證人物大小和格子相差不大