如何用不到200行代码实现经典小游戏贪吃蛇,附源代码及详细实现思路
不多廢話,直接上鏈接
鏈接:https://pan.baidu.com/s/1ZKtVNhzR4fIzNZSGWgFKQw?pwd=zglt?
提取碼:zglt
有需要的好兄弟們可以直接取用,想要了解一下編程思路的朋友們可以繼續往下看,如有任何問題可以在評論區留言。
首先貪吃蛇小游戲主要需要實現一下幾個功能:
(1)小蛇不斷向前移動
(2)小蛇根據鍵盤按鍵改變移動方向
(3)小蛇撞墻或撞到自己后游戲結束
(4)地圖內隨機生成蘋果
(5)小蛇吃到蘋果后增加一格
接下來我們逐條實現
首先在頁面內生成一個div,劃出800*800的區域,并使其居中,將其class名設置為back
back的內容如下:
.back {border: 2px solid black;width: 800px;height: 800px;margin: 0 auto;}生成效果:
?然后在.back中添加兩個屬性 display: flex;和?flex-wrap: wrap;
.back {border: 2px solid black;width: 800px;height: 800px;margin: 0 auto;display: flex;flex-wrap: wrap;}這兩個屬性是彈性盒子的內容,第一個屬性設置為flex值將back聲明為彈性盒子,第二個屬性設置該盒子內容自動換行,設置這兩個屬性的原因我們接下來講。
然后我們聲明一個css樣式名為.box,設置.box的寬高為40,并設置為怪異盒子
.box {width: 40px;height: 40px;box-sizing: border-box;}box-sizing: border-box;用于將元素設置為怪異盒子,怪異盒子的特殊之處在于其總寬高固定,設置邊框不會增加其實際寬高。
左為怪異盒子,右為普通盒子,給予20像素邊框右者實際大小為140*140
這時頁面中并沒有叫.box的元素,接下來我們使用JavaScript代碼給大背景添加小格子。
首先獲取clas名為back的大背景。
var back = document.querySelector(".back");接下來寫一個執行400次的for循環,每次循環都用.creatElement()函數生成一個新的div標簽,并將該標簽的class名設置為box,然后使用.appendChild方法添加進大背景中:
for (let i = 0; i < 400; i++) { //添加地圖格子var box = document.createElement("div");box.className = "box";back.appendChild(box);}由于大背景寬高為800,小格子寬高為40,這樣大背景中一共可以塞下20*20=400個小格子,由于我們給父元素設置為了彈性盒子且自動換行,這時當第一行被小格子塞滿時多余的小格子就自動填充到下一行中(類似于浮動),這樣逐行填充后就生成了一個400格的棋盤。
為了方便演示,我們給小格子添加一個邊框,這是最終效果:
?接下來我們開始畫蛇,首先用querySelectAll()方法獲取大背景中的400個小格子,并命名為box
var box = document.querySelectorAll(".box"); //獲取所有格子然后給格子添加顏色,需要注意的是,由于游戲開始時蛇頭的位置是朝右的,所以蛇頭一格并不是box[0],而是box[2],這里我設置蛇頭為青色,身體為灰色。
//聲明初始蛇樣式box[0].style.background = "grey";box[1].style.background = "grey";box[2].style.background = "cyan";最終效果:
?現在我們有了一條三格長的小蛇,接下來就是如何讓小蛇動起來,這里當然需要用到計時器
var timer = setInterval(move, 200);setInterval()為間隔計時器,內部傳入兩個參數,第一個為調用的函數名,第二個為間隔時間(單位為毫秒),當前該計時器會每隔200毫秒調用一次函數,即讓小蛇一秒走五次,如果覺得太慢也可以把這個值調小,這樣小蛇會移動得更快。
然后我們需要寫讓小蛇動起來的函數move(),不過在此之前,先將小蛇的頭和尾位置以及整體位置存儲一下。
//聲明蛇,并以行和列的形式存儲蛇每一格的位置var snake = [{hang:0,lie: 0}, {hang:0,lie: 1}, {hang:0,lie: 2}];var head = {}; //聲明蛇頭head.hang = snake[2].hang;//獲取當前蛇頭位置head.lie = snake[2].lie;var tail = {}; //聲明蛇尾tail.hang = snake[0].hang;//獲取當前蛇尾位置tail.lie = snake[0].lie;接下來我們來寫函數move(),首先我們要理解一點,想讓小蛇前進一個其實并不需要讓小蛇每一段身體都前進,只需要讓頭部的位置前移一格,讓原本頭部的位置變為身體,再刪除最后一節尾部即可。
?這樣我們就有了基本的實現思路,接下來就是如何用代碼實現。
首先在移動前,我們需要判斷蛇頭是否撞墻,若撞墻則游戲直接結束無需再進行移動,判斷是否撞墻,即判斷新的頭部位置行和列數值是否大于-1且小于20,不過向右移動時只需要判斷右側即可。
function move() {if (head.lie + 1 < 20) { //判斷是否向右撞墻} else {alert("游戲結束"); //撞墻,游戲結束;clearInterval(timer);//清除定時器,讓小蛇停止移動};}由于頭部遇到的情況比較復雜(撞墻,吃蘋果,撞自己等),所以這里我們將頭部移動單獨封裝成一個函數moveHead(),不過在調用該函數前,先設置好新頭部的位置。
function move() {if (head.lie + 1 < 20) { //判斷是否向右撞墻head.列 += 1;//向右移動即頭部列位置+1moveHead();//調用頭部移動函數} else {alert("游戲結束"); //撞墻,游戲結束;clearInterval(timer); //清除定時器,讓小蛇停止移動};}function moveHead() {}//聲明頭部移動函數接下來正式開始移動小蛇,首先獲取到新的頭部位置在數組中的下標,由于地圖每行為20格,那么位置下標=行數值*20+列數值,獲取后將新頭部格子刷成青色。
function moveHead() { //聲明頭部移動函數//獲取頭部位置在數組中的下標,并儲存為positonvar position = head.hang * 20 + head.lie;//利用下標在box數組中定位新的頭位置,并將其刷成青色box[position].style.background = "cyan";}當前效果:
?然后我們需要將原蛇頭位置的青色變為灰色,原蛇頭位置即當前snake數組中的最后一位,即數組長度-1的位置(數組下標從0開始,所以減一)。
function moveHead() { //聲明頭部移動函數//獲取新頭部位置在數組中的下標,并儲存為positonvar position = head.hang * 20 + head.lie;//利用下標在box數組中定位新的頭位置,并將其刷成青色box[position].style.background = "cyan";//獲取原有頭部位置的下標,并儲存為positonposition = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;//將原蛇頭位置刷成灰色box[position].style.background = "grey";}當前效果:
接下來就是刪除蛇尾,蛇尾位置即snak[0]存儲的位置。
function moveHead() { //聲明頭部移動函數//獲取新頭部位置在數組中的下標,并儲存為positonvar position = head.hang * 20 + head.lie;//利用下標在box數組中定位新的頭位置,并將其刷成青色box[position].style.background = "cyan";//獲取原有頭部位置的下標,并儲存為positonposition = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;//將原蛇頭位置刷成青色box[position].style.background = "grey";//獲取蛇尾位置position = snake[0].hang * 20 + snake[0].lie;//刪除蛇尾顏色box[position].style.background = "";}?最終效果:
?這時我們還需要做最后的處理工作,雖然頁面中的snake位置已經變化了,但是snake數組中存儲的位置并沒有改變,所以現在我們要修改snake數組中的內容。
var newhead = { //處理對象淺復制問題"hang": head.hang,"lie": head.lie};snake.push(newhead) //將新的頭位置添加進數組末尾snake.shift() //刪除數組中尾部位置由于這里涉及到對象淺復制問題,所以每次添加都需要聲明一個新對象,感興趣的朋友們可以自行了解一下。
最終效果:
?這樣小蛇的移動就完成了,接下來我們來控制小蛇的移動方向。
首先我們聲明一個變量,設置其數值為ArrowRight,為什么是這個值我們接下來講。
var ahead = "ArrowRight"; //聲明蛇的前進方向然后我們給頁面添加一個監聽事件,監聽鼠標按下,并傳入一個參數e。
var ahead = 39; //聲明蛇的前進方向document.addEventListener("keydown", (e) => {})//添加鍵盤按下的監聽事件并傳入e這里傳入的e可以簡單理解為事件本身,其內部存儲了觸發該事件時的一系列數據,這里我們需要用到其中的一個數據.key,該屬性存儲了觸發事件的按鍵名稱。
打印e.key時分別點擊小鍵盤上下左右四個鍵時返回的名稱:
?這就是我們設置ahead的值為ArrowRight的原因,在JavaScript中不同的按鍵有不同的名稱,我們只需要根據名稱就可以利用分支語句決定小蛇前進的方向,同時我們還可以添加一個判斷語句,防止小蛇出現180度調頭的情況(這里使用了JavaScript的三目運算,感興趣的朋友們可以自行了解)。
document.addEventListener("keydown", (e) => { //添加鍵盤按下的監聽事件并傳入eswitch (e.key) { //添加分支語句,設置移動方向case "ArrowUp": //判斷e.key是否等于該關鍵字ahead != "ArrowDown" ? ahead = e.key : ""; //判斷并給ahead賦新值break; //結束Switch執行,防止下方的代碼影響結果case "ArrowRight":ahead != "ArrowLeft" ? ahead = e.key : ""; //判斷并給ahead賦新值break;case "ArrowDown":ahead != "ArrowUp" ? ahead = e.key : ""; //判斷并給ahead賦新值break;case "ArrowLeft":ahead != "ArrowRight" ? ahead = e.key : ""; //判斷并給ahead賦新值break;}console.log(e.key);})然后再給前面的move()函數也添加一個Switch分支語句,通過ahead的值判斷頭部向哪個方向移動,向上移動時頭部行位置減一,向下移動時頭部行位置加一,向左和向右則為頭部列位置減一和加一。
function move() {switch (ahead) { //通過ahead判斷移動方向case "ArrowUp": //是否向上if (head.hang - 1 > -1) {head.hang -= 1; //向上移動即頭部行位置-1moveHead();} else {alert("游戲結束");clearInterval(timer);};break;case "ArrowDown": //是否向下if (head.hang + 1 < 20) {head.hang += 1; //向下移動即頭部行位置-1moveHead();} else {alert("游戲結束");clearInterval(timer);};break;case "ArrowLeft": //是否向左if (head.lie - 1 > -1) {head.lie -= 1; //向左移動即頭部列位置-1moveHead();} else {alert("游戲結束");clearInterval(timer);};break;case "ArrowRight": //是否向右if (head.lie + 1 < 20) {head.lie += 1; //向右移動即頭部列位置+1moveHead();} else {alert("游戲結束");clearInterval(timer);};break;}}由于目前我們可以通過按鍵來改變ahead的值,而move()函數的移動方向由ahead決定,所以我們就間接實現了通過按鍵控制小蛇的移動方向,效果如下:
?下一步就是聲明蘋果了,首先我們創建函數addApple(),并聲明對象apple存儲蘋果的位置
var apple = {};function addapple() {}接下來我們需要用到一個數學方法Math.random(),該方法的作用是聲明一個0到1之間的隨機浮點數,由于我們的地圖大小為20*20格,所以我們將生成的數字乘20并取整,這樣就得到了一個0到20之間的隨機整數(向下取整所以不含20)。
function addapple() { //隨機生成蘋果函數apple.hang = parseInt(Math.random() * 20); //隨機生成行apple.lie = parseInt(Math.random() * 20); //隨機生成列var position = apple.lie + apple.hang * 20 //通過行和列隨機生成蘋果box[position].style.background = "red"; //在隨機確定的位置刷紅色}這時我們調用函數,蘋果就會出現在地圖中
?不過由于目前蘋果的位置完全隨機,所以有可能會直接生成在小蛇身上,這是我們需要避免的問題,即當隨機生成的蘋果位置有顏色時重新生成,這里我們可以通過使用一個簡單的遞歸來解決該問題。
var apple = {}; //聲明對象存儲蘋果位置function addapple() { //隨機生成蘋果函數apple.hang = parseInt(Math.random() * 20); //隨機生成行apple.lie = parseInt(Math.random() * 20); //隨機生成列var position = apple.lie + apple.hang * 20 //通過行和列隨機生成蘋果if (box[position].style.background != "") { //判斷隨機生成的蘋果位置是否有顏色addapple(); //若有顏色重新調用該函數生成} else {box[position].style.background = "red"; //若無顏色則刷紅}}這時當生成的隨機位置有顏色時該函數就會重新被調用,生成一個新的蘋果位置,若沒有顏色則結束遞歸,并在該位置刷紅色。
然后就是最后一步,當小蛇吃到蘋果時變長一格,同時生成新的蘋果位置,其實這一功能很好實現,我們在移動小蛇時會刪除小蛇的尾部,現在只需要讓小蛇頭部碰到蘋果時尾部不刪除即可增加一格長度,所以我們需要對movehead()函數進行修改:
function moveHead() {//獲取新頭部位置在數組中的下標,并儲存為positonvar position = head.hang * 20 + head.lie;if (box[position].style.background == "red") { //判斷新的頭部位置是否為紅色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若為紅色則復制一份蛇尾,刪除時便會保留蛇尾addapple(); //調用函數,生成新蘋果}box[position].style.background = "cyan";position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;box[position].style.background = "grey";position = snake[0].hang * 20 + snake[0].lie;box[position].style.background = "";var newhead = {"hang": head.hang,"lie": head.lie};snake.push(newhead)snake.shift() //刪除數組中尾部位置}同時這里我們還可以添加一個判斷,若新蛇頭位置為灰色,則說明小蛇撞到了自己,這時可以直接結束游戲:
function moveHead() {//獲取新頭部位置在數組中的下標,并儲存為positonvar position = head.hang * 20 + head.lie;if (box[position].style.background == "red") { //判斷新的頭部位置是否為紅色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若為紅色則復制一份蛇尾,刪除時便會保留蛇尾addapple(); //調用函數,生成新蘋果} else if (box[position].style.background == "grey") {//判斷新的頭部位置是否為灰色alert("游戲結束");//若為灰色則直接結束游戲clearInterval(timer);}box[position].style.background = "cyan";position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;box[position].style.background = "grey";position = snake[0].hang * 20 + snake[0].lie;box[position].style.background = "";var newhead = {"hang": head.hang,"lie": head.lie};snake.push(newhead)snake.shift() //刪除數組中尾部位置}到這里一款可以正常游玩的貪吃蛇小游戲就算基本完工了,刪除注釋的話總代碼量可能都不到150行,雖然有很多細節有待優化;
那么本期的內容就到這里,如有任何問題歡迎在評論區留言,up都會盡量解答。
----------------------------------------------分割線-----------------------------------------------
后續我又對代碼進行了一點優化,首先是添加了分數和規則
規則不難寫,分數的話只需要在全局聲明一個變量number?來存儲分數,每次撞到蘋果后該數值加一即可。
var p = document.querySelector("p");//獲取第一行,分數行 var number = 0; //存儲當前分數function moveHead() { //聲明頭部移動函數.....if (box[position].style.background == "red") { //判斷新的頭部位置是否為紅色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若為紅色則復制一份蛇尾,刪除時便會保留蛇尾addapple(); //調用函數,生成新蘋果number++; //number加一分p.innerHTML = number + "分"; //改變p標簽內容} else if (box[position].style.background == "grey") { //判斷新的頭部位置是否為灰色alert("游戲結束"); //若為灰色則直接結束游戲clearInterval(timer);}.....}然后是暫停功能,只需要在addEventListener監聽事件的Switch分支中添加一個空格分支,單擊空格時彈出一個窗口即可暫停,當瀏覽器顯示窗口時計時器是停止運行的。
document.addEventListener("keydown", (e) => { //添加鍵盤按下的監聽事件并傳入eswitch (e.key) { //添加分支語句,設置移動方向.....case " ":alert("暫停中。。。");}console.log(e.key);});最后我把蛇和蘋果的顏色放到了代碼最上方,這樣可以直接修改所有代碼中的顏色。
總結
以上是生活随笔為你收集整理的如何用不到200行代码实现经典小游戏贪吃蛇,附源代码及详细实现思路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: opengl光照效果的三棱锥+键盘上下左
- 下一篇: 范德堡计算机科学硕士,范德堡大学计算机科