溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JS學習筆記之貪吃蛇小游戲demo實例詳解

發(fā)布時間:2020-09-29 12:39:55 來源:腳本之家 閱讀:141 作者:倪曉磊 欄目:web開發(fā)

本文實例講述了JS學習筆記之貪吃蛇小游戲demo實例。分享給大家供大家參考,具體如下:

最近跟著視頻教程打了一個貪吃蛇,

來記錄一下實現(xiàn)思路,

先上代碼
靜態(tài)頁

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>貪吃蛇</title>
</head>
<style>
*{
  margin: 0;
  padding: 0;
}
  .map{
    width:800px;
    height: 600px;
    background-color: #ccc;
    position:relative;
  }

</style>
<body>
<!-- 畫出地圖,設置樣式 -->
 <div class="map">

 </div>
</body>
<script src="food.js"></script>
<script src="snake.js"></script>
<script src="game.js"></script>
</html>

food.js

//食物就是一個對象,寬高橫縱坐標,先定義構造函數(shù),然后創(chuàng)建對象
(function (){
  var elements=[];//用來保存每個小方塊食物
  function Food(x,y,width,height,color){
    //橫縱坐標
    this.x=x||0;
    this.y=y||0;
    this.width=width||20;
    this.height=height||20;
    //背景顏色
    this.color=color||"green";
  }
  //為原型添加初始化的方法(作用:在頁面上取顯示這個食物)
  //因為食物要在地圖上顯示,所以,需要地圖這個參數(shù)
  Food.prototype.init=function(map){
    //先刪除這個食物
    //外部無法訪問的函數(shù)
    remove()
    var div=document.createElement("div");
    //把這個div加到map中
    map.appendChild(div);
    //設置div的樣式
    div.style.width=this.width+"px";
    div.style.height=this.height+"px";
    div.style.backgroundColor=this.color;
    // div.style.left=this.x+"px";

    //先脫離文檔流
    div.style.position="absolute";
    //隨機橫縱坐標
    this.x=parseInt(Math.random()*(map.offsetWidth/this.width))*this.width;
    this.y=parseInt(Math.random()*(map.offsetHeight/this.height))*this.height;
    div.style.left=this.x+"px";
    div.style.top=this.y+"px";
    // Food.prototype.init=function(map){

    // }
    //把div加入到數(shù)組elements中
    elements.push(div);
  }

  function remove(){
    //elements數(shù)組中有這個食物
    for(var i=0;i<elements.length;i++){
      var ele=elements[i]
      //找到這個子元素的父級元素,然后刪除這個子元素
      ele.parentNode.removeChild(ele);
      //再次把elements中的這個子元素也要刪除
      elements.splice(i,1)
    }
  }
  //把Food暴露給Window,外部可以使用
  window.Food=Food;
}());

snake.js

//蛇
(function(){
  var elements=[];//存放小蛇的每個身體部分
  //蛇的構造函數(shù)
  function Snake(width,height,direction){
    //小蛇的每個部分的寬
    this.width=width||20;
    this.height=height||20;
    //身體
    this.body=[
      {x:3,y:2,color:"red"},
      {x:2,y:2,color:"orange"},
      {x:1,y:2,color:"orange"}
    ];

    this.direction=direction||"right";
  }
//蛇的初始化
  Snake.prototype.init=function(map){
    remove()
    //循環(huán)遍歷創(chuàng)建div
    for(var i=0;i<this.body.length;i++){
      var obj=this.body[i];
      //創(chuàng)建div
      var div=document.createElement("div");
      //把div加入到map地圖中
      map.appendChild(div);
      //設置div的樣式;
      div.style.position="absolute";
      div.style.width=this.width+"px";
      div.style.height=this.height+"px";
      div.style.left=obj.x*this.width+"px";
      div.style.top=obj.y*this.height+"px";
      div.style.backgroundColor=obj.color;

      //把div加入到elements數(shù)組中--目的是刪除
      elements.push(div)
    }
  }
  //蛇的移動
  Snake.prototype.move=function(food,map){
    //改變蛇身體位置
    var i=this.body.length-1; //2
    for(;i>0;i--){
      this.body[i].x=this.body[i-1].x;
      this.body[i].y=this.body[i-1].y;
    }
    //判斷方向---改變小蛇的頭的坐標位置
    switch (this.direction){
      case "right":
        this.body[0].x+=1;
        break;
      case "left":
        this.body[0].x-=1;
        break;
      case "top":
        this.body[0].y-=1;
        break;
      case "bottom":
        this.body[0].y+=1;
        break;
    }

    //判斷有沒有吃到食物
    //小蛇的頭的坐標和食物位置
    var headX=this.body[0].x*this.width;
    var headY=this.body[0].y*this.height;
    //食物的橫縱坐標
    var foodX=food.x;
    var foodY=food.y;
    if(headX==foodX&&headY==foodY){
      //獲取蛇的最后尾巴
      var last=this.body[this.body.length-1];
      //把最后的蛇尾復制一份
      this.body.push({
        x:last.x,
        y:last.y,
        color:last.color
      })
      //重新初始化食物
      food.init(map);
    }



  }

  //刪除小蛇的私有函數(shù)
  function remove(){
    //獲取數(shù)組
    var i=elements.length-1;
    for(;i>=0;i--){
      //先從當前的子元素中找到該子元素的父級元素,然后再弄死這個子元素
      var ele=elements[i];
      //從map地圖上刪除這個子元素div
      ele.parentNode.removeChild(ele);
      elements.splice(i,1);
    }
  }
  window.Snake=Snake;
}());

//蛇
(function(){
  var elements=[];//存放小蛇的每個身體部分
  //蛇的構造函數(shù)
  function Snake(width,height,direction){
    //小蛇的每個部分的寬
    this.width=width||20;
    this.height=height||20;
    //身體
    this.body=[
      {x:3,y:2,color:"red"},
      {x:2,y:2,color:"orange"},
      {x:1,y:2,color:"orange"}
    ];

    this.direction=direction||"right";
  }
//蛇的初始化
  Snake.prototype.init=function(map){
    remove()
    //循環(huán)遍歷創(chuàng)建div
    for(var i=0;i<this.body.length;i++){
      var obj=this.body[i];
      //創(chuàng)建div
      var div=document.createElement("div");
      //把div加入到map地圖中
      map.appendChild(div);
      //設置div的樣式;
      div.style.position="absolute";
      div.style.width=this.width+"px";
      div.style.height=this.height+"px";
      div.style.left=obj.x*this.width+"px";
      div.style.top=obj.y*this.height+"px";
      div.style.backgroundColor=obj.color;

      //把div加入到elements數(shù)組中--目的是刪除
      elements.push(div)
    }
  }
  //蛇的移動
  Snake.prototype.move=function(food,map){
    //改變蛇身體位置
    var i=this.body.length-1; //2
    for(;i>0;i--){
      this.body[i].x=this.body[i-1].x;
      this.body[i].y=this.body[i-1].y;
    }
    //判斷方向---改變小蛇的頭的坐標位置
    switch (this.direction){
      case "right":
        this.body[0].x+=1;
        break;
      case "left":
        this.body[0].x-=1;
        break;
      case "top":
        this.body[0].y-=1;
        break;
      case "bottom":
        this.body[0].y+=1;
        break;
    }

    //判斷有沒有吃到食物
    //小蛇的頭的坐標和食物位置
    var headX=this.body[0].x*this.width;
    var headY=this.body[0].y*this.height;
    //食物的橫縱坐標
    var foodX=food.x;
    var foodY=food.y;
    if(headX==foodX&&headY==foodY){
      //獲取蛇的最后尾巴
      var last=this.body[this.body.length-1];
      //把最后的蛇尾復制一份
      this.body.push({
        x:last.x,
        y:last.y,
        color:last.color
      })
      //重新初始化食物
      food.init(map);
    }



  }

  //刪除小蛇的私有函數(shù)
  function remove(){
    //獲取數(shù)組
    var i=elements.length-1;
    for(;i>=0;i--){
      //先從當前的子元素中找到該子元素的父級元素,然后再弄死這個子元素
      var ele=elements[i];
      //從map地圖上刪除這個子元素div
      ele.parentNode.removeChild(ele);
      elements.splice(i,1);
    }
  }
  window.Snake=Snake;
}());

game.js

//游戲
(function(){
   var that=null;
  //游戲的構造函數(shù)
  function Game(map){
    this.food=new Food();
    this.snake=new Snake();
    this.map=map;//地圖
    that=this;
  }
  Game.prototype.init=function(){
    //初始化游戲
    //食物初始化
    this.food.init(this.map);
    this.snake.init(this.map);

    this.runSnake(this.food,this.map)
    this.bindKey();
  }

  Game.prototype.runSnake=function(food,map){
    //自動的去移動
    var timeId=setInterval(function(){
      //此時的this是window
      //蛇的移動
      this.snake.move(food,map);
      //初始化蛇
      this.snake.init(map);
      //橫坐標最大值
      var maxX=map.offsetWidth/this.snake.width;
      //獲取縱坐標的最大值
      var maxY=map.offsetHeight/this.snake.height;
      //蛇頭的坐標
      var headX=this.snake.body[0].x;
      var headY=this.snake.body[0].y;
      //判斷橫坐標
      if(headX<0||headX>=maxX){
        clearInterval(timeId)
        alert("游戲結束")
      }
      //判斷縱坐標
      if(headY<0||headY>maxY){
        clearInterval(timeId)
        alert("游戲結束")
      }
      console.log(headX)
    }.bind(that),150)
  }

  Game.prototype.bindKey=function(){
    //獲取用戶的按鍵,改變小蛇的方向
    document.addEventListener("keydown",function(e){
      //獲取案件的值
      switch(e.keyCode){
        case 37:
          this.snake.direction="left";
          break;
        case 38:
          this.snake.direction="top";
          break;
        case 39:
          this.snake.direction="right";
          break;
        case 40:
          this.snake.direction="bottom";
          break;
      }
    }.bind(that),false)
  }


  window.Game=Game;

}());

//初始化游戲?qū)ο?var gm=new Game(document.querySelector(".map"));
gm.init()

這里加一個小插曲,關于匿名函數(shù)自調(diào)用的三種寫法

第一種

JS學習筆記之貪吃蛇小游戲demo實例詳解

第二種

JS學習筆記之貪吃蛇小游戲demo實例詳解

第三種

JS學習筆記之貪吃蛇小游戲demo實例詳解

注意!注意! 注意! 匿名函數(shù)的最后不要忘記加封號;  因為如果忘了加,系統(tǒng)很容易與后面的代碼混淆 造成各種很奇葩的報錯;

這里我推薦第三種寫法,比較清晰明了

好,代碼貼完了,我們來分析一下實現(xiàn)思路

首先 第一步

建立一個畫布

設置畫布的寬度為800px   高度為600px  因為小蛇 需要在畫布內(nèi)任意移動,需要脫離標準文檔流,所以需要設置絕對定位, 因此我給畫布添加了position:relative;  ,再給背景添加一個顏色 ,灰色#ccc

好,畫布創(chuàng)建好了,我們可以開始編寫邏輯代碼了

Food.js 代碼分析

首先我們需要創(chuàng)建一個貪吃蛇 吃的“食物”,因此我們需要創(chuàng)建一個食物的對象,這里我在food.js中創(chuàng)建了一個自定義構造函數(shù)

定義了 “食物Food”的 x值、y值、寬度、高度、顏色

JS學習筆記之貪吃蛇小游戲demo實例詳解

這里我利用 || 運算設置了默認值,如果 || 左邊為false 則會自動取右邊的值,所以當實例化對象時若未傳參時 自動取 “||” 運算符右邊的值

然后在"Food"的原型上定義了一個 init 初始化方法

首先創(chuàng)建一個div ,并將此對象保存在 div變量中

然后 在地圖中 添加上這個 div  再逐步給這個 div元素 加上他的寬度、高度、背景顏色、并且設置絕對定位

那我們怎么定位呢?

這里我們可以把整個地圖看成是一個坐標系,把地圖的寬度除以 “食物”的寬度 來切分這個地圖 ,x=1則相當于1個“食物寬度”的單位長,x=3 則相當于3個“食物寬度的單位長”

高度同理

這里我取了隨機數(shù)  乘以  地圖被切分的總份數(shù)   這樣就會的到 隨機的 X和Y   然后乘以 寬度和高度 就的到了不會超出地圖的隨機坐標 ,舉例 :  因為Math.random(0,5)  是不包括5的

JS學習筆記之貪吃蛇小游戲demo實例詳解

因為“食物”是會被貪吃蛇 “吃”掉的

所以我們必須創(chuàng)建一個方法來“消滅”這個“食物”,因此我定義了一個 remove 函數(shù)

并且 上方創(chuàng)建了一個 數(shù)組elements用于存放 創(chuàng)建出來的 "食物" 這個div元素 的對象,方便用來刪除,每次初始化“食物” 時,將對象追加入elements 數(shù)組 

JS學習筆記之貪吃蛇小游戲demo實例詳解

我們遍歷 elements 數(shù)組, 通過數(shù)組中每個div對象 先找到其父級,然后通過removeChild 方法將其自身刪除JS學習筆記之貪吃蛇小游戲demo實例詳解

因為有保存在elements 數(shù)組中,那我們想要刪除“食物就很方便了”,每次初始化之前我們秩序要調(diào)用一次 remove函數(shù)就是實現(xiàn)了“消滅”食物,然后再生成新的食物

因為此處的所有函數(shù)都寫在了一個 自調(diào)用的匿名函數(shù)中,所以內(nèi)部的Food 對象,在外部是訪問不到的,

那怎么辦呢?

這里我調(diào)用了window 對象,將配置好的Food 對象暴露給window ,這樣,我們再其他的地方有需要時也可以實例化 Food了 

Snake.js 代碼分析

其實 “貪吃蛇”身體的實現(xiàn)和 “食物”的實現(xiàn)原理大體相同 ,首先我也同樣建立了一個elements數(shù)組,用于存放之后小蛇移動時所產(chǎn)生的舊的“身體”,用于刪除,因為都是局部變量,所以雖然兩個數(shù)組名字相同,但不會沖突

這里我也給小蛇設置了寬、高,寬高我設定為默認和食物相同,并且還設置了方向direction 這用來控制小蛇的移動方向,這里我默認給了“right”向右移動,

并且,因為當游戲開始時,小蛇必當有一個初始的長度,我給了它一個腦袋 二節(jié)身體,腦袋設置成了紅色,方便識別

所以我之后如果需要讓小蛇增加長度,體現(xiàn)越吃越長的感覺 ,只需要在 body這個數(shù)組中追加對象就可以了

JS學習筆記之貪吃蛇小游戲demo實例詳解


好,小蛇的基本屬性配置完了,我們下一步就是要初始化小蛇 

同樣的,上方也提到了,我創(chuàng)建過一個elements數(shù)組,用于存放“小蛇”的舊身體,所以在初始化之前,我們需要調(diào)用remove函數(shù),遍歷elements數(shù)組,和刪除“食物”一樣的方法,將舊的“蛇身”都給刪除了

JS學習筆記之貪吃蛇小游戲demo實例詳解

執(zhí)行完刪除之后呢,我們就可以專心初始化了,

蛇身這么長,那我們該怎么知道蛇身的每一節(jié)到底該在哪里呢

這時就用到了我們上方定義的  body 這個數(shù)組了,它存放了小蛇的身體的所有部分

我們只需要遍歷它,根據(jù)其中每個對象的屬性都進行創(chuàng)建新的div對象,同意設置其寬、高、left、top,并且,將創(chuàng)建好的對象又存入elements數(shù)組中,方便下一次刪除

JS學習筆記之貪吃蛇小游戲demo實例詳解

定義完初始化的方法后,我們就得考慮小蛇的移動該怎么實現(xiàn)了,

既然是貪吃蛇游戲,我們肯定需要與玩家互動,讓玩家來操控小蛇的走向,

對了,我們自定義構造函數(shù)的時候不是設置過一個direction 屬性嗎,我們就得利用起來,依據(jù)此來判斷小蛇的走向

至于更改方向,我們放在之后的代碼中實現(xiàn)

這里我們定義了一個move 函數(shù) ,并傳入兩個參數(shù) food "食物對象" 和 map“地圖對象”  

那為什么要傳呢,

雖然我們這個demo里面只有一條小蛇,但這樣的寫法保留了同時開啟多個游戲的可能性

  首先我們獲取 body 數(shù)組的長度 存入i 中,然后倒序倒序倒序遍歷 i  ,根據(jù) i 作為索引, 從蛇尾巴開始向蛇腦袋遍歷,大家想象一下,貪吃蛇的蛇身是不是都是按部就班的沿著腦袋走過的路徑走的? 你給它繞個直角、或者正方形它總是老老實實的走完,所以我們每次移動,只需要控制蛇腦袋移動,讓蛇身體讓它他們挨個獲取他們前面那一節(jié)身體的坐標就可以了

所以,這里我們倒序遍歷,將第 i 節(jié)身體賦值前一節(jié)身體的 x 屬性和 y 屬性

蛇身的重新賦值做完了,我們判斷一下蛇頭的移動方向,因為是固定的4個方向,所以這里使用switch較為方便,

根據(jù) 上、下、左、右不一樣的情況對 頭部的x和y增加或減少

既然是貪吃蛇,我們食物也創(chuàng)建好了,需要實現(xiàn)貪吃蛇吃食物這個過程

首先我們分別計算出 蛇腦袋和食物的X和Y

然后,我們判斷一下,

當蛇腦袋的x,y 和食物的x,y都相等的時候,

我們延長一節(jié)蛇身,這里我復制了一份最后一節(jié)蛇身體 然后追加入body數(shù)組,注意!

因為對象是引用類型,所以必須這樣拆開賦值

最后,再調(diào)用一次食物的初始化,產(chǎn)生新的食物

JS學習筆記之貪吃蛇小游戲demo實例詳解

同樣的,這里我也將,Snake 對象暴露給window,供下方的Game.js中的代碼調(diào)用

JS學習筆記之貪吃蛇小游戲demo實例詳解

Game.js 代碼分析

在Game.js中,開頭就定義了that 

用來保存this 的指向,供后面使用

我們分別實例化一個 “食物”對象和一個“貪吃蛇”對象

傳入地圖對象,并賦值

JS學習筆記之貪吃蛇小游戲demo實例詳解

屬性設置好了

那既然是游戲那我們是不是應該設定點游戲規(guī)則,

當我們的小蛇到達地圖邊界時,小蛇就會一頭撞死了,游戲結束,

并且我們也沒有實現(xiàn)小蛇的移動,讓我們來接著實現(xiàn)吧

這里我定義了一個runSnake函數(shù),傳入 food 和map 對象

首先,定義一個計時器,存入timeId這個變量中

調(diào)用一個蛇的 move(移動) 和 init (初始化函數(shù))

在小蛇成功移動之后,我們再判斷一下,小蛇是否已經(jīng)走到邊界了,

計算出,地圖寬度最多能被蛇頭的寬度分為幾份,高度同理

取出蛇頭自身的x和y

判斷 如果蛇頭x<0 說明越過左邊界,超過maxX則說明超過右邊界,

y同理

如果越過邊界,則清除定時器,執(zhí)行一個彈框

注意,我在這個定時器中的方法后加個一個bind 并傳入了開始定義的 that ,也就是提前保存的this 指向,如果不加,這里的代碼多處用到了this ,因為setInterVal 的指向為window 所以會導致代碼出現(xiàn)錯誤,無法找到這些方法和屬性

JS學習筆記之貪吃蛇小游戲demo實例詳解

接下來我們再來實現(xiàn)一下如何用鍵盤控制小蛇的移動

根據(jù)keycode 來更改 snake對象的 direction ,

同樣的,此處的this 指向也不正確,指向的是 觸發(fā)該事件的對象,這是無法調(diào)用snake對象的,所以我們必須改變它,在bind中傳入(that)

然后將Game 對象暴露給 window

JS學習筆記之貪吃蛇小游戲demo實例詳解

接著定義初始化游戲的函數(shù)

分別調(diào)用food對象的初始化函數(shù)、小蛇的初始化函數(shù),調(diào)用runSnake函數(shù)開啟定時器讓小蛇跑起來

最后綁定上keydown 事件

JS學習筆記之貪吃蛇小游戲demo實例詳解

最后的最后

實例化一個Game對象

調(diào)用gm 的init  貪吃蛇小demo就實現(xiàn)了

JS學習筆記之貪吃蛇小游戲demo實例詳解

效果展示

JS學習筆記之貪吃蛇小游戲demo實例詳解

更多關于JavaScript相關內(nèi)容感興趣的讀者可查看本站專題:《JavaScript數(shù)學運算用法總結》、《JavaScript數(shù)據(jù)結構與算法技巧總結》、《JavaScript數(shù)組操作技巧總結》、《JavaScript排序算法總結》、《JavaScript遍歷算法與技巧總結》、《JavaScript查找算法技巧總結》及《JavaScript錯誤與調(diào)試技巧總結》

希望本文所述對大家JavaScript程序設計有所幫助。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI