記錄16 貪吃蛇第二部分,實現,結束

構建Block構造方法

我們將定義一個Block 構造方法,它會創建對象來表示不可見的游戲網格中的單個的塊。每個塊都有 col(column 的縮寫)和 row 屬性, 它們將存儲特定的塊在網格上的位置。下圖展示了這個帶有數目固定的一 些列和行的網格。盡管這個網格并不會真的出現在屏幕上,游戲設計成讓蘋果和貪吃蛇段總是能夠和網格中的塊對齊。


Paste_Image.png

我們可以通過該構造方法創建一個塊實例,比如下面創建了一個第5列第5行中的塊

var sampleBlock = new Block(5,5);

上面只是創建了實例,但是沒有將實例繪制出來,要在游戲面板上繪制出該實例還必須使用drawSquare或drawCircle方法。

添加equal方法

在游戲中,需要知道兩個塊是否位于同一位置。例如,如果蘋果和貪吃蛇的頭部位于同一位置,這意味著,貪吃蛇會吃掉蘋果。另一方面,如果貪吃蛇的頭部和尾部位于同一位置,那么,貪吃蛇碰到了自己。
為了使得比較塊的位置更為容易,我們給Block 構造方法原型添加了 equal 方法。當在一個塊對象上調用equal并傳遞另一個對象作為一個參數, 如果兩個對象位于相同的位置,它將返回true(否則的話,返回false)。代碼如下:

Block.prototype.equal = function (otherBlock){
            return this.col === otherBlock.col && this.row === otherBlock.row;
}

我們將使用 equal 方法來檢查貪吃蛇是否吃到蘋果或者碰到自己。

創建貪吃蛇

我們把貪吃蛇的位置存儲為一個名為segments 的數組,其中包含了一系列的塊對象。為了移動貪吃蛇,我們在segments 數組的 開頭添加一個新的塊,并且從數組的尾部刪除該塊。Segments 數組的第一個元素將表示貪吃蛇的頭部。

  • Snake構造方法,貪吃蛇一開始長度為3個網格
    direction 屬性存儲了貪吃蛇的當前位置
    nextDirection 屬性,它存儲了貪吃蛇在下一個動畫步驟將要移動的方向,除非按下方向鍵修改該屬性,不然貪吃蛇的前進方向不會改變。構造方法將這兩個屬性都設置為 "right",因此游戲一開始的時候,貪吃蛇向右移動。
var Snake = function (){
    this.segments = [
        new Block(7,5),
        new Block(6,5),
        new Block(5,5)
    ];
        this.direction = "right";
    this.nextDirection = "right";
};
組成貪吃蛇最初的塊

繪制貪吃蛇

為了繪制貪吃蛇,我們直接遍歷其segments 數組中的每一個塊,在每個 塊上調用在前面所創建的drawSquare 方法。這將會為貪吃蛇的每一段都繪制 一個方塊。

Snake.prototype.draw = function(){
    for(var i=0;i < this.segments.length; i++){
        this.segments[i].drawSquare("Blue"); //等價于Block實例.drawSquare("Blue");
    }
};

移動貪吃蛇

貪吃蛇移動看起來好像很復雜,身體的每個塊都得移動,有的塊可能方向可能會變。其實很簡單,我們不用去移動貪吃蛇每個網格組成部分,只要往頭部添加一個網格,把最尾部網格刪除,其他網格不動就可以了。
我們將創建一個 move 方法,沿著貪吃蛇的當前方向將其移動一個塊。為 了移動貪吃蛇,我們添加了一個新的頭部段(在segments 數組的開頭添加了一個新的block 對象),然后,從segments 數組 刪除尾部段。

move方法還將調用一個checkCollision方法, 來查看新的頭部是否與貪吃蛇其他的部分或者墻 發生碰撞,以及新的頭部是否吃到了蘋果。如果新的頭部與身體或墻發生碰撞,調用gameOver 函數來結束游戲。如果貪吃蛇吃到了蘋果,我們增加分數,并且將蘋果移動到 新的位置。

將 this.direction 設置為和this. nextDirection 相等,這會將貪吃蛇的移動方向更 新為與近按下的箭頭鍵一致(當我們介紹keydown 事件處理程序的時候, 將會更詳細地看到這是如何工作的)。

對于動畫中的每一步,貪吃蛇的 direction 屬性都會更新一次,因為每個動畫步驟都會調用一次move 方法。另一方面,當玩家在任何時候按下 一個箭頭鍵,nextDirection屬性都會更新(因此,如果玩家按鍵真的很快, 這個屬性理論上可能會在每個動畫步驟中更新多次)。通過保持這兩個 屬性各自分離,我們確保了如果玩家在動畫的兩個步驟之間非常快速地 按下兩個箭頭鍵,貪吃蛇不會回過頭來碰到自己。

當 this.nextDirection 為 "down" 的時候創建 newHead

檢查碰撞并添加頭部

只有在checkCollision 返回true 的時候,才會遇到return關鍵字,因此,如果貪吃蛇沒有和任何物體發生碰撞,將會執行剩余的代碼。只要貪吃蛇沒有和某個物體碰撞,就會在貪吃蛇的前面添加新的頭部, 通過使用unshift 把 newHead 添加到segments 數組的開始,從而做到這一點。
貪吃蛇移動只要往頭部添加一個網格,把最尾部網格刪除,其他網格不動就可以了。由于貪吃蛇每個網格是保存在segments屬性數組里面的,于是我們只要每次移動往該數組第一個網格添加一個頭部元素(unshift添加一個元素),如果是吃到蘋果那么就不用改變尾部,如果不是就通過pop把最后一個網格元素去掉。下次根據新的數組繪制出新的貪吃蛇。

數組unshift的用法:用unshift 把元素"Monkey" 和 "Polar Bear" 添加到數組的起始位 置,每一次原有的值都會向后順延 一個索引位置。每次調用unshift,也會返回數 組新的長度,就像 push 一樣。


使用equal 方法來比較newHead 和 apple.position。如果這兩 個塊位于相同的位置,equal 方法將會返回true,這意味著,貪吃蛇吃掉了蘋果。

//創建一個新的網格加到當前貪吃蛇前進方向的頭部,刪除尾部網格
Snake.prototype.move = function(){
    var head = this.segments[0];
    var newHead;
    this.direction = this.nextDirection;
    if(this.direction === "right"){
        newHead = new Block(head.col+1,head.row);
    }else if(this.direction === "down"){
        newHead = new Block(head.col,head.row+1);
    }else if(this.direction === "left"){
        newHead = new Block(head.col-1,head.row);
    }else if(this.direction === "up"){
        newHead = new Block(head.col,head.row-1);
    }

    if (this.checkCollision(newHead)){
        gameOver();
        return;
    }

    this.segments.unshift(newHead);

    if(newHead.equal(apple.position)){
        score++;
        apple.move();
    }else{
        this.segments.pop();
    }
}

添加 checkCollision 方法

每次為貪吃蛇的頭部設置一個新的位置的時候,都必須檢查碰撞。碰撞檢測在游戲機制中是一個很常見的步驟,往往也是游戲編程中較為復雜 的一個方面。好在,在貪吃蛇游戲中,碰撞檢測相對簡單。
注意:該游戲中(0,0)這樣的位置不是活動位置邊界,已經是由灰色邊框網格填充了。游戲中四個邊界都有10像素寬的邊界,所以貪吃蛇頭部網格的新位置head.col === 0就“Game Over”了。

添加 keydown 事件處理程序

var directions = {
    37: "left",
    38: "up",
    39: "right",
    40: "down"
};

$("body").keydown(function(event){
    var newDirection = directions[event.keyCode];
    if(newDirection !== undefined){
        snake.setDirection(newDirection);
    }
});

游戲入口程序

var intervalId = setInterval(function(){
    ctx.clearRect(0,0,width,height);
    drawScore();
    snake.move();
    snake.draw();
    apple.draw();
    drawBorder();
},100);

移動蘋果

由于邊框的存在,蘋果出現的范文只能(1~38)*10,0和39都是邊框。
調用 Math.? oor(Math.random() * 38),它給出了從0到37的一個隨機數,然后,給結果加1以得到1到38之間的一個數字。

添加 setDirection 方法

setDirection 還防止玩家調頭以導致貪吃蛇立即碰到自己。例如,如果貪吃蛇向右移動,然后它突然向左轉而不向上或向下移 以改變路徑,那么它會和自己碰撞。這種現象叫作非法調頭,因為我們不想讓玩家這么做。也就是如果當前方向是向右,那么按鍵是向左直接return,不更新當前方向。


基于當前方向的有效的新方向

完整可運行代碼

<!DOCTYPE html>
<html>
<head>
    <title>Learn</title>
</head>
<body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script type="text/javascript" src="jquery-3.1.1.js"></script>
    <script type="text/javascript">
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;

        //游戲面板看成是40個10*10的網格組成的
        var blockSize = 10;//每個網格的大小
        var widthInBlocks = width / blockSize;
        var heightInBlocks = height / blockSize;

        //每次貪吃蛇吃掉一個蘋果的時候,分數加1
        var score = 0; 

        //創建一個drawBorder函數來繪制圍繞畫布的灰色邊框,1個塊(10 像素)那么寬。 
        var drawBorder = function(){
            ctx.fillStyle = "Gray";
            //繪制4條邊框
            ctx.fillRect(0,0,width,blockSize);
            ctx.fillRect(0,height-blockSize,width,blockSize);
            ctx.fillRect(0,0,blockSize,height);
            ctx.fillRect(width-blockSize,0,blockSize,height);
        }

        //編寫記分函數
        var drawScore = function(){
            ctx.font = "20px Courier";
            ctx.fillStyle = "Black";
            ctx.textAlign = "left";
            ctx.textBaseline = "top"; //文本左對齊
            ctx.fillText("Score:"+score,blockSize,blockSize);
        }

        //結束游戲
        var gameOver = function(){
            clearInterval(intervalId);
            ctx.font = "60px Courier";
            ctx.fillStyle = "Black";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText("Game Over",width/2,height/2);
        }

        //繪制圓的方法
        var circle = function(x,y,radius,fillCircle){
            ctx.beginPath();
            ctx.arc(x,y,radius,0,Math.PI*2,false);
            if(fillCircle){
                ctx.fill();
            }else{
                ctx.stroke();
            }
        };

        //Block構造方法
        var Block = function(col,row){
            this.col = col;
            this.row = row;
        };

        //繪制方塊
        Block.prototype.drawSquare = function(color){
            var x = this.col * blockSize;
            var y = this.row * blockSize;
            ctx.fillStyle = color;
            ctx.fillRect(x,y,blockSize,blockSize);
        }

        //繪制圓封裝成Block的原型方法
        Block.prototype.drawCircle = function(color){
            var centerX = this.col * blockSize + blockSize/2;
            var centerY = this.row * blockSize + blockSize/2;
            ctx.fillStyle = color;
            circle(centerX,centerY,blockSize/2,true);
        }

        //檢查Block是否再同一個位置已經是其他的Block了(如貪吃蛇前進,后面的塊頂替前面塊的位置,都是在同一個位置卻是不同的塊了)
        Block.prototype.equal = function (otherBlock){
            return this.col === otherBlock.col && this.row === otherBlock.row;
        }

        //貪吃蛇構造方法
        var Snake = function (){
            this.segments = [
                new Block(7,5),
                new Block(6,5),
                new Block(5,5)
            ];

            this.direction = "right";
            this.nextDirection = "right";
        };

        //繪制貪吃蛇身體的每一個網格部分
        Snake.prototype.draw = function(){
            for(var i=0;i < this.segments.length; i++){
                this.segments[i].drawSquare("Blue");
            }
        };

        //創建一個新的網格加到當前貪吃蛇前進方向的頭部
        Snake.prototype.move = function(){
            var head = this.segments[0];
            var newHead;
            this.direction = this.nextDirection;
            if(this.direction === "right"){
                newHead = new Block(head.col+1,head.row);
            }else if(this.direction === "down"){
                newHead = new Block(head.col,head.row+1);
            }else if(this.direction === "left"){
                newHead = new Block(head.col-1,head.row);
            }else if(this.direction === "up"){
                newHead = new Block(head.col,head.row-1);
            }

            if (this.checkCollision(newHead)){
                gameOver();
                return;
            }

            this.segments.unshift(newHead);

            if(newHead.equal(apple.position)){
                score++;
                apple.move();
            }else{
                this.segments.pop();
            }
        }

        //檢查蛇的頭部是否碰到墻體或者吃到自己的尾巴
        Snake.prototype.checkCollision = function(head){
            var leftCollision = (head.col === 0);
            var topCollision = (head.row === 0);
            var rightCollision = (head.col === widthInBlocks-1);
            var bottomCollision = (head.row === heightInBlocks-1);
            
            var wallCollision = leftCollision || topCollision ||
                rightCollision || bottomCollision;

            var selfCollision = false;

            for(var i=0;i<this.segments.length;i++){
                if(head.equal(this.segments[i])){
                    selfCollision = true;
                }
            }
            return wallCollision || selfCollision;              
        }

        //根據按下的鍵盤決定貪吃蛇下次的移動方向
        Snake.prototype.setDirection = function(newDirection){
            if(this.direction === "up" && newDirection === "down" ){
                return;
            }else if(this.direction === "right" && newDirection === "left"){
                return;
            }else if(this.direction === "down" && newDirection === "up"){
                return;
            }else if(this.direction === "left" && newDirection === "right"){
                return;
            }
            this.nextDirection = newDirection;
        }

        //蘋果構造器
        var Apple = function(){
            this.position = new Block(10,10);
        };

        //添加原型方法,在蘋果的坐標屬性的位置上繪制一個蘋果
        Apple.prototype.draw = function(){
            this.position.drawCircle("LimeGreen");
        };

        //移動蘋果到一個新的隨機位置
        Apple.prototype.move = function(){
            var randomCol = Math.floor(Math.random()*(widthInBlocks-2))+1;
            var randomRow = Math.floor(Math.random()*(heightInBlocks-2))+1;
            this.position = new Block(randomCol,randomRow);
        };

        //游戲開始時,創建貪吃蛇和蘋果
        var snake = new Snake();
        var apple = new Apple();

        //游戲動畫,加載script便簽就得到間歇調用的執行。其他代碼比如函數定義也是執行了,只不過都是函數定義
        var intervalId = setInterval(function(){
            ctx.clearRect(0,0,width,height);
            drawScore();
            snake.move();
            snake.draw();
            apple.draw();
            drawBorder();
        },100);

        //keyCode用更形象的字符串來表示方向
        var directions = {
            37: "left",
            38: "up",
            39: "right",
            40: "down"
        };

        $("body").keydown(function(event){
            var newDirection = directions[event.keyCode];
            if(newDirection !== undefined){
                snake.setDirection(newDirection);
            }
        });
    </script>
</body>
</html>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 今天實現一款經典小游戲的實例,貪吃蛇想必大家都有接觸過,當然今天實現的細節沒有那么全面,只能算是簡易版本的小游戲,...
    牛水聿閱讀 1,186評論 0 3
  • 我們將自己構建經典的街機游戲貪吃蛇。 在貪吃蛇中,玩家控制一條蛇向上、向下、向左或向右移動。隨著蛇在游戲區域內移動...
    suhuanzhen閱讀 547評論 0 1
  • 關于我 我是IsCoding,11年開始做 Android 開發 已經做了7年在創業公司負責過技術,拿到過融資。想...
    IsCoding閱讀 1,966評論 0 3
  • 翻譯自“Collection View Programming Guide for iOS” 0 關于iOS集合視...
    lakerszhy閱讀 3,901評論 1 22
  • 做個簡單練習,題目關于點菜。要求用戶注冊登錄之后,來到點菜頁面,點菜頁面數據從后臺數據庫獲取后頁面渲染出來。用戶選...
    2010jing閱讀 723評論 4 4