“低配”貪喫蛇

前兩天我們用canvas製作了一個簡單的五子棋小遊戲,今天我再用canvas製作一個貪喫蛇小遊戲。
試玩點這裏:貪喫蛇
老規矩,先看看界面效果:
在這裏插入圖片描述
屏幕大致分成兩個板塊:上面的菜單板塊和下面的遊戲板塊。
菜單板塊是一些基本的html+css代碼以及少量的js代碼組成,我這裏不多講解,直接給出代碼,看着代碼也應該很容易理解。
html代碼:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GreedSnake</title>
    <link rel="stylesheet" href="snake.css">
</head>
<body>

    <header>
        <h1>貪喫蛇</h1>
        <a href="" id="newgamebutton">New Game</a>
        <a href="javascript:void(0)" id="pausegamebutton">Pause Game</a>
        <a href="javascript:void(0)" id="gamehelpbutton">Game Help</a>
        <p>score:<span id="score">0</span></p>
        </head>
        <canvas width="600" height="600" id="canvas"></canvas>
        <script src="snake.js"></script>
</body>
</html>

css代碼:

header {
    display: block;
    margin: 0 auto;
    width: 100%;
    text-align: center
}

header h1 {
    font-family: Arial;
    font-size: 40px;
    font-weight: bold;
}

header a {
    display: block;
    margin: 20px auto;
    width: 100px;
    padding: 10px;
    font-family: Arial;
    color: white;
    border-radius: 10px;
    text-decoration: none;
}

#newgamebutton {
    background-color: #f14343;
}

header #newgamebutton:hover {
    background: #c90707;
}

#pausegamebutton {
    background-color: rgb(57, 190, 243);
}

header #pausegamebutton:hover {
    background: rgb(8, 168, 231);
}

#gamehelpbutton {
    background-color: rgb(221, 224, 26);
}

header #gamehelpbutton:hover {
    background: rgb(198, 201, 40);
}

header p {
    font-family: Arial;
    font-size: 25px;
    margin: 20px auto;
}

canvas {
    display: block;
    margin: 30px auto;
    background-color: #33cc99;
}

部分的js代碼:

pause.addEventListener('click', function (e) {
    alert("暫停遊戲!");
});
help.addEventListener('click', function (e) {
    alert("此遊戲支持電腦鍵盤操作並有得分系統\n" +
        "wsad分別控制蛇上下左右移動\n" +
        "蛇的移速是動態的與蛇的長度成正比\n" +
        "地圖總共有三種功能的食物,具體功能有待你們自己去探索\n" +
        "Have a Great Game!");
});

進入最重要的遊戲版本界面。
遊戲製作可以先粗分爲三部分:繪製地圖,繪製食物與蛇,制定遊戲規則。
繪製地圖:
這裏的操作和五子棋棋盤的操作大同小異,也都是固定格式,我直接給出代碼。

for (var i = 1; i < 20; i++) {
        //+0.5是爲了繪製出1px寬度的線條
        tools.moveTo(30 * i + 0.5, 0);
        tools.lineTo(30 * i + 0.5, 600);
        tools.moveTo(0, 30 * i + 0.5);
        tools.lineTo(600, 30 * i + 0.5);
    }
    tools.strokeStyle = 'white';
    tools.stroke();

繪製食物:
簡單來看,就是繪製一個矩形小方塊,再賦予其隨機算法即可,但是在遊戲裏我設定了三種不同的食物,所以我這裏暫值給出簡單的繪製矩形的代碼和隨機算法的代碼,具體情況留到制定遊戲規則板塊。

tools.fillStyle = 'rgb(252, 101, 101)';
tools.fillRect(xRed, yRed, 30, 30);
var xRed = Math.floor(Math.random() * 20) * 30;
var yRed = Math.floor(Math.random() * 20) * 30;

繪製蛇:
簡單來看,在地圖裏是蛇,但是實際上是用數組來保存,所以我們真實操作的也是這個數組。

for (var i = 0; i < snake.length; i++) {
        if (i == 0) {
            tools.fillStyle = '#ff0033';
        } else {
            tools.fillStyle = '#333399';
        }
        tools.fillRect(snake[i].x * 30, snake[i].y * 30, 30, 30);
    }

制定遊戲規則:
由於這個板塊基本都牽連到全局,所以此部分我就無法單獨給出其代碼。這裏我只講述每個核心部分的實現思路,並在文章最後給出完整的js代碼供大家參考(代碼裏都有詳細的註釋)。
這個板塊主要又可細分爲以下幾個部分:
蛇的移動,蛇喫食物,蛇的移動方向,遊戲結束判定。(基本功能)
動態移速,得分系統,減蛇長食物。(擴展功能)
蛇的移動:
蛇的移動其實是一種動畫效果,而動畫的原理就是兩步:擦除+重繪。
說白了就是刪除掉舊狀態的畫布,然後繪製出新狀態的畫布,而這裏就需要用到js定時器。
蛇喫食物:
其實蛇喫食物也是人視覺的一個錯覺,由於蛇的底層是一個數組,我們可以設置蛇的每一次運動都有出棧和“入棧”的操作來保持蛇長度的平衡,而在蛇遇到食物時取消出棧的操作,這些就能使得蛇的長度+1。
蛇的移動方向:
通過綁定鍵盤監聽事件來實現,注意特別判定“蛇換頭”的bug。
遊戲結束判定:
就兩種情況:蛇碰到自己的身體以及邊界。(這裏都可以通過座標來處理)
動態移速:
通過修改定時器的時間間隔,使得其與蛇的長度成正比。
得分系統:
定義一個全局變量score,在每次蛇喫到食物後score給予相應的積累改變,並將結果呈現在頁面。
減蛇長食物:
這個功能的設計想法是當蛇長達到一定長度後能夠通過這個食物減短蛇一定的長度(減低移速)來平衡遊戲難度,而當蛇的長度減小到一定長度限制後,此類食物會消失。
我採取的實現思路稍稍有些複雜,這裏就不給出,可以直接參考代碼。如果有更好實現思路的大佬,歡迎評論區留言。
完整的js代碼:

var canvas = document.getElementById("canvas");
var scoreId = document.getElementById("score");
var pause = document.getElementById("pausegamebutton");
var help = document.getElementById("gamehelpbutton");
var tools = canvas.getContext("2d");
var xRed = Math.floor(Math.random() * 20) * 30;
var yRed = Math.floor(Math.random() * 20) * 30;
var xGold = Math.floor(Math.random() * 20) * 30;
var yGold = Math.floor(Math.random() * 20) * 30;
var xBlue = 0;
var yBlue = 0;
var isEatRed = false;
var isEatGold = false;
var isEatBlue = false;
var startEatBlue = false;
var startFlag = true;
var endFlag = true;
var endEatBlue = false;
var liveBlue = false;
var snake = [
    { x: 3, y: 0 },
    { x: 2, y: 0 },
    { x: 1, y: 0 }
];
var score = 0;
var directionX = 1;
var directionY = 0;
//監聽鍵盤事件
document.addEventListener('keydown', function (e) {
    //設定蛇的運行方向不能180度調頭
    if (e.keyCode == 87 && (directionX != 0 || directionY != 1)) {
        directionX = 0;
        directionY = -1;
    } else if (e.keyCode == 83 && (directionX != 0 || directionY != -1)) {
        directionX = 0;
        directionY = 1;
    } else if (e.keyCode == 65 && (directionX != 1 || directionY != 0)) {
        directionX = -1;
        directionY = 0;
    } else if (e.keyCode == 68 && (directionX != -1 || directionY != 0)) {
        directionX = 1;
        directionY = 0;
    }
});
pause.addEventListener('click', function (e) {
    alert("暫停遊戲!");
});
help.addEventListener('click', function (e) {
    alert("此遊戲支持電腦鍵盤操作並有得分系統\n" +
        "wsad分別控制蛇上下左右移動\n" +
        "蛇的移速是動態的與蛇的長度成正比\n" +
        "地圖總共有三種功能的食物,具體功能有待你們自己去探索\n" +
        "Have a Great Game!");
});
//設定遊戲默認定時器(蛇的運行速度)
var game = setInterval(start, 1000 / 3);
//下面三個函數都是產生一個新的合法食物
function newEatRed(snake) {
    var xRed = Math.floor(Math.random() * 20) * 30;
    var yRed = Math.floor(Math.random() * 20) * 30;
    while (true) {
        var flag = false;
        for (var i = 0; i < snake.length; i++) {
            if (snake[i].x * 30 == xRed && snake[i].y * 30 == yRed) {
                flag = true;
                break;
            }
        }
        if (flag) {
            xRed = Math.floor(Math.random() * 20) * 30;
            yRed = Math.floor(Math.random() * 20) * 30;
        } else {
            break;
        }
    }
    return {
        x: xRed,
        y: yRed
    }
}
function newEatGold(snake, xRed, yRed) {
    var xGold = Math.floor(Math.random() * 20) * 30;
    var yGold = Math.floor(Math.random() * 20) * 30;
    while (true) {
        var flag = false;
        for (var i = 0; i < snake.length; i++) {
            if (snake[i].x * 30 == xRed && snake[i].y * 30 == yRed) {
                flag = true;
                break;
            }
        }
        if (xGold == xRed && yGold == yRed) {
            flag = true;
        }
        if (flag) {
            xGold = Math.floor(Math.random() * 20) * 30;
            yGold = Math.floor(Math.random() * 20) * 30;
        } else {
            break;
        }
    }
    return {
        x: xGold,
        y: yGold
    }
}
function newEatBlue(snake, xRed, yRed, xGold, yGold) {
    var xBlue = Math.floor(Math.random() * 20) * 30;
    var yBlue = Math.floor(Math.random() * 20) * 30;
    while (true) {
        var flag = false;
        for (var i = 0; i < snake.length; i++) {
            if (snake[i].x * 30 == xRed && snake[i].y * 30 == yRed) {
                flag = true;
                break;
            }
        }
        if ((xBlue == xRed && yBlue == yRed) || (xBlue == xGold && yBlue == yGold)) {
            flag = true;
        }
        if (flag) {
            xBlue = Math.floor(Math.random() * 20) * 30;
            xBlue = Math.floor(Math.random() * 20) * 30;
        } else {
            break;
        }
    }
    return {
        x: xBlue,
        y: yBlue
    }
}
//判斷兩種遊戲結束條件
function judge(snake, newHead) {
    if (newHead.x < 0 || newHead.x * 30 >= 600 || newHead.y * 30 < 0 || newHead.y * 30 >= 600) {
        alert("遊戲結束!");
        clearInterval(game);
        return true;
    }
    for (var i = 1; i < snake.length; i++) {
        if (newHead.x == snake[i].x && newHead.y == snake[i].y) {
            alert("遊戲結束!");
            clearInterval(game);
            return true;
        }
    }
    return false;
}
//綁定定時器的函數(核心函數)
function start() {
    //擦除舊的canvas
    tools.clearRect(0, 0, 600, 600);
    var oldHead = snake[0];
    var newHead = {
        x: oldHead.x + directionX,
        y: oldHead.y + directionY
    }
    //判斷結束條件
    var isFailed =  judge(snake, newHead);
    if(isFailed) {
        return;
    }
    //蛇喫食物
    if (snake[0].x * 30 == xRed && snake[0].y * 30 == yRed) {
        score++;
        scoreId.innerText = score;
        isEatRed = true;
    } else if (snake[0].x * 30 == xGold && snake[0].y * 30 == yGold) {
        score = score + 2;
        scoreId.innerText = score;
        isEatGold = true;
    } else {
        isEatRed = false;
        isEatGold = false;
        snake.pop();
    }
    snake.unshift(newHead);
    if (snake[0].x * 30 == xBlue && snake[0].y * 30 == yBlue) {
        isEatBlue = true;
        snake.pop();
    } else {
        isEatBlue = false;
    }
    //蛇的動態移速(無限遞歸)
    clearInterval(game);
    game = setInterval(start, 1000 / (3 + (snake.length - 1) / 5));
    //下面兩個判斷藍色食物代碼必須放在蛇喫食物的下面,否則會出現藍色食物出現與消失慢拍的bug
    //出現藍色食物
    if (snake.length > 25 && startFlag) {
        startEatBlue = true;
        startFlag = false;
        endEatBlue = false;
        endFlag = true;
    }
    //取消藍色食物
    if (snake.length < 16 && endFlag) {
        endEatBlue = true;
        endFlag = false;
        startEatBlue = false;
        startFlag = true;
    }
    //產生紅色食物與金色食物
    if (isEatRed) {
        var arrRed = newEatRed(snake);
        xRed = arrRed.x;
        yRed = arrRed.y;
    }
    tools.fillStyle = 'rgb(252, 101, 101)';
    tools.fillRect(xRed, yRed, 30, 30);
    if (isEatGold) {
        var arrGold = newEatGold(snake, xRed, yRed);
        xGold = arrGold.x;
        yGold = arrGold.y;
    }
    tools.fillStyle = 'gold';
    tools.fillRect(xGold, yGold, 30, 30);
    if (endEatBlue) {
        if (liveBlue) {
            liveBlue = false;
        }
    }
    //第一次產生藍色食物
    if (startEatBlue) {
        if (!liveBlue) {
            var arrBlue = newEatBlue(snake, xRed, yRed, xGold, yGold);
            xBlue = arrBlue.x;
            yBlue = arrBlue.y;
            liveBlue = true;
        }
        tools.fillStyle = 'rgb(89, 195, 245)';
        tools.fillRect(xBlue, yBlue, 30, 30);
    }
    //正常產生藍色食物
    if (startEatBlue && isEatBlue) {
        var arrBlue = newEatBlue(snake, xRed, yRed, xGold, yGold);
        xBlue = arrBlue.x;
        yBlue = arrBlue.y;
        tools.fillStyle = 'rgb(89, 195, 245)';
        tools.fillRect(xBlue, yBlue, 30, 30);
    }
    //繪製蛇
    for (var i = 0; i < snake.length; i++) {
        if (i == 0) {
            tools.fillStyle = '#ff0033';
        } else {
            tools.fillStyle = '#333399';
        }
        tools.fillRect(snake[i].x * 30, snake[i].y * 30, 30, 30);
    }
    //繪製地圖
    for (var i = 1; i < 20; i++) {
        //+0.5是爲了繪製出1px寬度的線條
        tools.moveTo(30 * i + 0.5, 0);
        tools.lineTo(30 * i + 0.5, 600);
        tools.moveTo(0, 30 * i + 0.5);
        tools.lineTo(600, 30 * i + 0.5);
    }
    tools.strokeStyle = 'white';
    tools.stroke();
}

最後補充一點:start函數裏面的代碼執行順序是很有講究的,不可隨意調換!
如果大家有興趣,可以繼續擴展遊戲玩法,比如再設定一條蛇使其變成一個雙人遊戲等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章