JS學(xué)習(xí)筆記之貪吃蛇小游戲demo實(shí)例詳解
本文實(shí)例講述了JS學(xué)習(xí)筆記之貪吃蛇小游戲demo實(shí)例。分享給大家供大家參考,具體如下:
最近跟著視頻教程打了一個(gè)貪吃蛇,
來記錄一下實(shí)現(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> <!-- 畫出地圖,設(shè)置樣式 --> <div class="map"> </div> </body> <script src="food.js"></script> <script src="snake.js"></script> <script src="game.js"></script> </html>
food.js
//食物就是一個(gè)對(duì)象,寬高橫縱坐標(biāo),先定義構(gòu)造函數(shù),然后創(chuàng)建對(duì)象 (function (){ var elements=[];//用來保存每個(gè)小方塊食物 function Food(x,y,width,height,color){ //橫縱坐標(biāo) this.x=x||0; this.y=y||0; this.width=width||20; this.height=height||20; //背景顏色 this.color=color||"green"; } //為原型添加初始化的方法(作用:在頁面上取顯示這個(gè)食物) //因?yàn)槭澄镆诘貓D上顯示,所以,需要地圖這個(gè)參數(shù) Food.prototype.init=function(map){ //先刪除這個(gè)食物 //外部無法訪問的函數(shù) remove() var div=document.createElement("div"); //把這個(gè)div加到map中 map.appendChild(div); //設(shè)置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"; //隨機(jī)橫縱坐標(biāo) 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ù)組中有這個(gè)食物 for(var i=0;i<elements.length;i++){ var ele=elements[i] //找到這個(gè)子元素的父級(jí)元素,然后刪除這個(gè)子元素 ele.parentNode.removeChild(ele); //再次把elements中的這個(gè)子元素也要?jiǎng)h除 elements.splice(i,1) } } //把Food暴露給Window,外部可以使用 window.Food=Food; }());
snake.js
//蛇 (function(){ var elements=[];//存放小蛇的每個(gè)身體部分 //蛇的構(gòu)造函數(shù) function Snake(width,height,direction){ //小蛇的每個(gè)部分的寬 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); //設(shè)置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) } } //蛇的移動(dòng) 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; } //判斷方向---改變小蛇的頭的坐標(biāo)位置 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; } //判斷有沒有吃到食物 //小蛇的頭的坐標(biāo)和食物位置 var headX=this.body[0].x*this.width; var headY=this.body[0].y*this.height; //食物的橫縱坐標(biāo) var foodX=food.x; var foodY=food.y; if(headX==foodX&&headY==foodY){ //獲取蛇的最后尾巴 var last=this.body[this.body.length-1]; //把最后的蛇尾復(fù)制一份 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--){ //先從當(dāng)前的子元素中找到該子元素的父級(jí)元素,然后再弄死這個(gè)子元素 var ele=elements[i]; //從map地圖上刪除這個(gè)子元素div ele.parentNode.removeChild(ele); elements.splice(i,1); } } window.Snake=Snake; }()); //蛇 (function(){ var elements=[];//存放小蛇的每個(gè)身體部分 //蛇的構(gòu)造函數(shù) function Snake(width,height,direction){ //小蛇的每個(gè)部分的寬 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); //設(shè)置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) } } //蛇的移動(dòng) 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; } //判斷方向---改變小蛇的頭的坐標(biāo)位置 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; } //判斷有沒有吃到食物 //小蛇的頭的坐標(biāo)和食物位置 var headX=this.body[0].x*this.width; var headY=this.body[0].y*this.height; //食物的橫縱坐標(biāo) var foodX=food.x; var foodY=food.y; if(headX==foodX&&headY==foodY){ //獲取蛇的最后尾巴 var last=this.body[this.body.length-1]; //把最后的蛇尾復(fù)制一份 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--){ //先從當(dāng)前的子元素中找到該子元素的父級(jí)元素,然后再弄死這個(gè)子元素 var ele=elements[i]; //從map地圖上刪除這個(gè)子元素div ele.parentNode.removeChild(ele); elements.splice(i,1); } } window.Snake=Snake; }());
game.js
//游戲 (function(){ var that=null; //游戲的構(gòu)造函數(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){ //自動(dòng)的去移動(dòng) var timeId=setInterval(function(){ //此時(shí)的this是window //蛇的移動(dòng) this.snake.move(food,map); //初始化蛇 this.snake.init(map); //橫坐標(biāo)最大值 var maxX=map.offsetWidth/this.snake.width; //獲取縱坐標(biāo)的最大值 var maxY=map.offsetHeight/this.snake.height; //蛇頭的坐標(biāo) var headX=this.snake.body[0].x; var headY=this.snake.body[0].y; //判斷橫坐標(biāo) if(headX<0||headX>=maxX){ clearInterval(timeId) alert("游戲結(jié)束") } //判斷縱坐標(biāo) if(headY<0||headY>maxY){ clearInterval(timeId) alert("游戲結(jié)束") } 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()
這里加一個(gè)小插曲,關(guān)于匿名函數(shù)自調(diào)用的三種寫法
第一種
第二種
第三種
注意!注意! 注意! 匿名函數(shù)的最后不要忘記加封號(hào); 因?yàn)槿绻思?系統(tǒng)很容易與后面的代碼混淆 造成各種很奇葩的報(bào)錯(cuò);
這里我推薦第三種寫法,比較清晰明了
好,代碼貼完了,我們來分析一下實(shí)現(xiàn)思路
首先 第一步
建立一個(gè)畫布
設(shè)置畫布的寬度為800px 高度為600px 因?yàn)樾∩?需要在畫布內(nèi)任意移動(dòng),需要脫離標(biāo)準(zhǔn)文檔流,所以需要設(shè)置絕對(duì)定位, 因此我給畫布添加了position:relative; ,再給背景添加一個(gè)顏色 ,灰色#ccc
好,畫布創(chuàng)建好了,我們可以開始編寫邏輯代碼了
Food.js 代碼分析
首先我們需要?jiǎng)?chuàng)建一個(gè)貪吃蛇 吃的“食物”,因此我們需要?jiǎng)?chuàng)建一個(gè)食物的對(duì)象,這里我在food.js中創(chuàng)建了一個(gè)自定義構(gòu)造函數(shù)
定義了 “食物Food”的 x值、y值、寬度、高度、顏色
這里我利用 || 運(yùn)算設(shè)置了默認(rèn)值,如果 || 左邊為false 則會(huì)自動(dòng)取右邊的值,所以當(dāng)實(shí)例化對(duì)象時(shí)若未傳參時(shí) 自動(dòng)取 “||” 運(yùn)算符右邊的值
然后在"Food"的原型上定義了一個(gè) init 初始化方法
首先創(chuàng)建一個(gè)div ,并將此對(duì)象保存在 div變量中
然后 在地圖中 添加上這個(gè) div 再逐步給這個(gè) div元素 加上他的寬度、高度、背景顏色、并且設(shè)置絕對(duì)定位
那我們?cè)趺炊ㄎ荒兀?/p>
這里我們可以把整個(gè)地圖看成是一個(gè)坐標(biāo)系,把地圖的寬度除以 “食物”的寬度 來切分這個(gè)地圖 ,x=1則相當(dāng)于1個(gè)“食物寬度”的單位長,x=3 則相當(dāng)于3個(gè)“食物寬度的單位長”
高度同理
這里我取了隨機(jī)數(shù) 乘以 地圖被切分的總份數(shù) 這樣就會(huì)的到 隨機(jī)的 X和Y 然后乘以 寬度和高度 就的到了不會(huì)超出地圖的隨機(jī)坐標(biāo) ,舉例 : 因?yàn)镸ath.random(0,5) 是不包括5的
因?yàn)椤笆澄铩笔菚?huì)被貪吃蛇 “吃”掉的
所以我們必須創(chuàng)建一個(gè)方法來“消滅”這個(gè)“食物”,因此我定義了一個(gè) remove 函數(shù)
并且 上方創(chuàng)建了一個(gè) 數(shù)組elements用于存放 創(chuàng)建出來的 "食物" 這個(gè)div元素 的對(duì)象,方便用來刪除,每次初始化“食物” 時(shí),將對(duì)象追加入elements 數(shù)組
我們遍歷 elements 數(shù)組, 通過數(shù)組中每個(gè)div對(duì)象 先找到其父級(jí),然后通過removeChild 方法將其自身刪除
因?yàn)橛斜4嬖趀lements 數(shù)組中,那我們想要?jiǎng)h除“食物就很方便了”,每次初始化之前我們秩序要調(diào)用一次 remove函數(shù)就是實(shí)現(xiàn)了“消滅”食物,然后再生成新的食物
因?yàn)榇颂幍乃泻瘮?shù)都寫在了一個(gè) 自調(diào)用的匿名函數(shù)中,所以內(nèi)部的Food 對(duì)象,在外部是訪問不到的,
那怎么辦呢?
這里我調(diào)用了window 對(duì)象,將配置好的Food 對(duì)象暴露給window ,這樣,我們?cè)倨渌牡胤接行枰獣r(shí)也可以實(shí)例化 Food了
Snake.js 代碼分析
其實(shí) “貪吃蛇”身體的實(shí)現(xiàn)和 “食物”的實(shí)現(xiàn)原理大體相同 ,首先我也同樣建立了一個(gè)elements數(shù)組,用于存放之后小蛇移動(dòng)時(shí)所產(chǎn)生的舊的“身體”,用于刪除,因?yàn)槎际蔷植孔兞?,所以雖然兩個(gè)數(shù)組名字相同,但不會(huì)沖突
這里我也給小蛇設(shè)置了寬、高,寬高我設(shè)定為默認(rèn)和食物相同,并且還設(shè)置了方向direction 這用來控制小蛇的移動(dòng)方向,這里我默認(rèn)給了“right”向右移動(dòng),
并且,因?yàn)楫?dāng)游戲開始時(shí),小蛇必當(dāng)有一個(gè)初始的長度,我給了它一個(gè)腦袋 二節(jié)身體,腦袋設(shè)置成了紅色,方便識(shí)別
所以我之后如果需要讓小蛇增加長度,體現(xiàn)越吃越長的感覺 ,只需要在 body這個(gè)數(shù)組中追加對(duì)象就可以了
好,小蛇的基本屬性配置完了,我們下一步就是要初始化小蛇
同樣的,上方也提到了,我創(chuàng)建過一個(gè)elements數(shù)組,用于存放“小蛇”的舊身體,所以在初始化之前,我們需要調(diào)用remove函數(shù),遍歷elements數(shù)組,和刪除“食物”一樣的方法,將舊的“蛇身”都給刪除了
執(zhí)行完刪除之后呢,我們就可以專心初始化了,
蛇身這么長,那我們?cè)撛趺粗郎呱淼拿恳还?jié)到底該在哪里呢
這時(shí)就用到了我們上方定義的 body 這個(gè)數(shù)組了,它存放了小蛇的身體的所有部分
我們只需要遍歷它,根據(jù)其中每個(gè)對(duì)象的屬性都進(jìn)行創(chuàng)建新的div對(duì)象,同意設(shè)置其寬、高、left、top,并且,將創(chuàng)建好的對(duì)象又存入elements數(shù)組中,方便下一次刪除
定義完初始化的方法后,我們就得考慮小蛇的移動(dòng)該怎么實(shí)現(xiàn)了,
既然是貪吃蛇游戲,我們肯定需要與玩家互動(dòng),讓玩家來操控小蛇的走向,
對(duì)了,我們自定義構(gòu)造函數(shù)的時(shí)候不是設(shè)置過一個(gè)direction 屬性嗎,我們就得利用起來,依據(jù)此來判斷小蛇的走向
至于更改方向,我們放在之后的代碼中實(shí)現(xiàn)
這里我們定義了一個(gè)move 函數(shù) ,并傳入兩個(gè)參數(shù) food "食物對(duì)象" 和 map“地圖對(duì)象”
那為什么要傳呢,
雖然我們這個(gè)demo里面只有一條小蛇,但這樣的寫法保留了同時(shí)開啟多個(gè)游戲的可能性
首先我們獲取 body 數(shù)組的長度 存入i 中,然后倒序倒序倒序遍歷 i ,根據(jù) i 作為索引, 從蛇尾巴開始向蛇腦袋遍歷,大家想象一下,貪吃蛇的蛇身是不是都是按部就班的沿著腦袋走過的路徑走的? 你給它繞個(gè)直角、或者正方形它總是老老實(shí)實(shí)的走完,所以我們每次移動(dòng),只需要控制蛇腦袋移動(dòng),讓蛇身體讓它他們挨個(gè)獲取他們前面那一節(jié)身體的坐標(biāo)就可以了
所以,這里我們倒序遍歷,將第 i 節(jié)身體賦值前一節(jié)身體的 x 屬性和 y 屬性
蛇身的重新賦值做完了,我們判斷一下蛇頭的移動(dòng)方向,因?yàn)槭枪潭ǖ?個(gè)方向,所以這里使用switch較為方便,
根據(jù) 上、下、左、右不一樣的情況對(duì) 頭部的x和y增加或減少
既然是貪吃蛇,我們食物也創(chuàng)建好了,需要實(shí)現(xiàn)貪吃蛇吃食物這個(gè)過程
首先我們分別計(jì)算出 蛇腦袋和食物的X和Y
然后,我們判斷一下,
當(dāng)蛇腦袋的x,y 和食物的x,y都相等的時(shí)候,
我們延長一節(jié)蛇身,這里我復(fù)制了一份最后一節(jié)蛇身體 然后追加入body數(shù)組,注意!
因?yàn)閷?duì)象是引用類型,所以必須這樣拆開賦值
最后,再調(diào)用一次食物的初始化,產(chǎn)生新的食物
同樣的,這里我也將,Snake 對(duì)象暴露給window,供下方的Game.js中的代碼調(diào)用
Game.js 代碼分析
在Game.js中,開頭就定義了that
用來保存this 的指向,供后面使用
我們分別實(shí)例化一個(gè) “食物”對(duì)象和一個(gè)“貪吃蛇”對(duì)象
傳入地圖對(duì)象,并賦值
、
屬性設(shè)置好了
那既然是游戲那我們是不是應(yīng)該設(shè)定點(diǎn)游戲規(guī)則,
當(dāng)我們的小蛇到達(dá)地圖邊界時(shí),小蛇就會(huì)一頭撞死了,游戲結(jié)束,
并且我們也沒有實(shí)現(xiàn)小蛇的移動(dòng),讓我們來接著實(shí)現(xiàn)吧
這里我定義了一個(gè)runSnake函數(shù),傳入 food 和map 對(duì)象
首先,定義一個(gè)計(jì)時(shí)器,存入timeId這個(gè)變量中
調(diào)用一個(gè)蛇的 move(移動(dòng)) 和 init (初始化函數(shù))
在小蛇成功移動(dòng)之后,我們?cè)倥袛嘁幌?,小蛇是否已?jīng)走到邊界了,
計(jì)算出,地圖寬度最多能被蛇頭的寬度分為幾份,高度同理
取出蛇頭自身的x和y
判斷 如果蛇頭x<0 說明越過左邊界,超過maxX則說明超過右邊界,
y同理
如果越過邊界,則清除定時(shí)器,執(zhí)行一個(gè)彈框
注意,我在這個(gè)定時(shí)器中的方法后加個(gè)一個(gè)bind 并傳入了開始定義的 that ,也就是提前保存的this 指向,如果不加,這里的代碼多處用到了this ,因?yàn)閟etInterVal 的指向?yàn)閣indow 所以會(huì)導(dǎo)致代碼出現(xiàn)錯(cuò)誤,無法找到這些方法和屬性
接下來我們?cè)賮韺?shí)現(xiàn)一下如何用鍵盤控制小蛇的移動(dòng)
根據(jù)keycode 來更改 snake對(duì)象的 direction ,
同樣的,此處的this 指向也不正確,指向的是 觸發(fā)該事件的對(duì)象,這是無法調(diào)用snake對(duì)象的,所以我們必須改變它,在bind中傳入(that)
然后將Game 對(duì)象暴露給 window
接著定義初始化游戲的函數(shù)
分別調(diào)用food對(duì)象的初始化函數(shù)、小蛇的初始化函數(shù),調(diào)用runSnake函數(shù)開啟定時(shí)器讓小蛇跑起來
最后綁定上keydown 事件
最后的最后
實(shí)例化一個(gè)Game對(duì)象
調(diào)用gm 的init 貪吃蛇小demo就實(shí)現(xiàn)了
效果展示
更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript數(shù)組操作技巧總結(jié)》、《JavaScript排序算法總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》、《JavaScript查找算法技巧總結(jié)》及《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
- js實(shí)現(xiàn)貪吃蛇小游戲(容易理解)
- 20行js代碼實(shí)現(xiàn)的貪吃蛇小游戲
- 純js和css完成貪吃蛇小游戲demo
- js實(shí)現(xiàn)貪吃蛇小游戲
- 基于javascript實(shí)現(xiàn)貪吃蛇經(jīng)典小游戲
- 基于javascript實(shí)現(xiàn)貪吃蛇小游戲
- jsp網(wǎng)頁實(shí)現(xiàn)貪吃蛇小游戲
- js猜數(shù)字小游戲的簡單實(shí)現(xiàn)代碼
- JavaScript編寫連連看小游戲
- JavaScript實(shí)現(xiàn)打地鼠小游戲
- js實(shí)現(xiàn)九宮格拼圖小游戲
- 原生javascript制作貪吃蛇小游戲的方法分析
相關(guān)文章
js基礎(chǔ)之DOM中document對(duì)象的常用屬性方法詳解
下面小編就為大家?guī)硪黄猨s基礎(chǔ)之DOM中document對(duì)象的常用屬性方法詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10javascript 獲取模態(tài)窗口的滾動(dòng)位置代碼
模態(tài)窗口的滾動(dòng)位置獲取辦法還有不知道的嗎?下面的方法或許對(duì)大家有所幫助,感興趣的朋友可以了解下,希望對(duì)大家有所幫助2013-08-08微信小程序?qū)崿F(xiàn)自定義加載圖標(biāo)功能
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)自定義加載圖標(biāo)功能,非常不錯(cuò)具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-07-07window.location.href和window.open窗口跳轉(zhuǎn)區(qū)別解析
這篇文章主要為大家介紹了window.location.href和window.open 跳轉(zhuǎn)區(qū)別解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07js實(shí)現(xiàn)旋轉(zhuǎn)大風(fēng)車
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)旋轉(zhuǎn)大風(fēng)車,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02JS中的THIS和WINDOW.EVENT.SRCELEMENT詳解
對(duì)于js初學(xué)著必須理解this和srcElement的應(yīng)用,這也是面試中經(jīng)??嫉降?。下面我們就通過幾個(gè)示例來詳細(xì)了解下2015-05-05Bootstrap3使用typeahead插件實(shí)現(xiàn)自動(dòng)補(bǔ)全功能
這篇文章主要介紹了Bootstrap3使用typeahead插件實(shí)現(xiàn)自動(dòng)補(bǔ)全功能的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07