前言
我的博客
看到掘金上有這樣一種效果,感覺很好看,就是那種毛玻璃效果,於是想試試寫一個登錄頁面並且實現遮罩,但是寫成瞭開始遊戲,可是光一個開始遊戲也沒意思,幹脆寫一個小遊戲吧,直接試試貪吃蛇。
如何實現
開始遊戲
這是我HTML
中body
部分的代碼,main
是主體,也就是遊戲場地。
beginBox
是開始遊戲的界面,我再這個盒子裡面實現瞭毛玻璃遮罩,還不錯。
然後下面那個盒子就是蛇瞭。
如果你也想試試毛玻璃遮罩效果,可以看看我的css
。
直接看js
代碼吧。
首先,我們先定義好全局變量,做好準備。
// 蛇的速度,即計時器的間隔時間var SnakeTime = 200;// 蛇的身體var map = document.getElementById('map');
速度是計時器控制的。
接下來,我們創建一個方法,Snake()
,這是蛇整個的構造方法。
我再這個方法裡面寫瞭蛇的一些東西。
我的蛇初始是3個10*10的正方形拼成的。
// 設置蛇的寬、高、默認走的方向 this.width = 10; this.height = 10; this.direction = 'right';
所以方法裡面,我首先確定瞭寬高,以及使用direction
屬性確定方向。
然後,我們這個蛇的三個點,需要按照規律排好,我這裡使用瞭一個數組。
this.body = [ { x: 2, y: 0 }, // 蛇頭,第一個點 { x: 1, y: 0 }, // 蛇脖子,第二個點 { x: 0, y: 0 } // 蛇尾,第三個點];
這還隻是蛇的初始化狀態哈!蛇還沒創建。
然後我們來創建蛇。
定義一個方法。這個方法在snake
方法裡面。
// 顯示蛇 this.display = function () { // 創建蛇 for (var i = 0; i < this.body.length; i++) { if (this.body[i].x != null) { // 當吃到食物時,x==null,不能新建,不然會在0,0處新建一個 var s = document.createElement('div'); // 將節點保存到狀態中,以便於後面刪除 this.body[i].flag = s; // 設置寬高 s.style.width = this.width + 'px'; s.style.height = this.height + 'px'; //設置顏色 s.style.backgroundColor = 'yellow'; // 設置位置 s.style.position = 'absolute'; s.style.left = this.body[i].x * this.width + 'px'; s.style.top = this.body[i].y * this.height + 'px'; // 添加進去 map.appendChild(s); } } //設置蛇頭的顏色 this.body[0].flag.style.backgroundColor = 'orange'; };
在這個方法裡面,s
就是一個div,而body
數組的長度是3,我們循環3此,依次追加,就拼成瞭,頭、身、尾。
但是,此時蛇,是出來瞭,但是不能動啊….
所以在定義一個方法,也是在snake
方法裡面。
this.run = function () { // 後一個元素到前一個元素的位置 for (var i = this.body.length - 1; 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 "left": this.body[0].x -= 1; break; case "right": this.body[0].x += 1; break; case "up": this.body[0].y -= 1; break; case "down": this.body[0].y += 1; break; } // 判斷是否出界,根據蛇頭判斷 if (this.body[0].x 150 || this.body[0].y 60) { clearInterval(timer); // 清除定時器 alert("出界啦,遊戲結束!"); document.getElementById('beginBox').style.display = 'block'; // 刪除舊的 for (var i = 0; i < this.body.length; i++) { if (this.body[i].flag != null) { // 如果剛吃完就死掉,會加一個值為null的 map.removeChild(this.body[i].flag); } } this.body = [ // 回到初始狀態, { x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 } ]; this.direction = 'right'; this.display(); // 顯示初始狀態 return false; // 結束 } // 判斷蛇頭吃到食物,xy坐標重合, if (this.body[0].x == food.x && this.body[0].y == food.y) { // 蛇加一節,因為根據最後節點定,下面display時,會自動賦值的 this.body.push({ x: null, y: null, flag: null }); // 獲取蛇的長度 var len = this.body.length; // 根據蛇的長度,設置定時器頻率SnakeTime SnakeTime = SnakeTime - (len - 3) * 5; // SnakeTime最低不能小於40 if (SnakeTime < 40) { SnakeTime = 40; } refresh(); // 清除食物,重新生成食物 map.removeChild(food.flag); food.display(); } // 吃到自己死亡,從第五個開始與頭判斷,因為前四個永遠撞不到 for (var i = 4; i < this.body.length; i++) { if (this.body[0].x == this.body[i].x && this.body[0].y == this.body[i].y) { clearInterval(timer); // 清除定時器, alert("你咬到瞭自己,遊戲結束!"); // 顯示id為beginBox的毛玻璃遮罩盒子 document.getElementById('beginBox').style.display = 'block'; // 刪除舊的 for (var i = 0; i < this.body.length; i++) { if (this.body[i].flag != null) { // 如果剛吃完就死掉,會加一個值為null的 map.removeChild(this.body[i].flag); } } this.body = [ // 回到初始狀態, { x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 } ]; this.direction = 'right'; this.display(); // 顯示初始狀態 return false; // 結束 } } // 先刪掉初始的蛇,在顯示新蛇 for (var i = 0; i < this.body.length; i++) { if (this.body[i].flag != null) { // 當吃到食物時,flag是等於null,且不能刪除 map.removeChild(this.body[i].flag); } } // 重新顯示蛇 this.display(); }}
這段代碼有點多哈。我們拆開看。
// 後一個元素到前一個元素的位置for (var i = this.body.length - 1; i > 0; i--) { this.body[i].x = this.body[i - 1].x; this.body[i].y = this.body[i - 1].y;}
首先,蛇是一節節動的,所以我們使用循環,讓他後一個替代前一個的位置。
然後,根據direction
屬性來判斷方向。
// 根據方向處理蛇頭 switch (this.direction) { case "left": this.body[0].x -= 1; break; case "right": this.body[0].x += 1; break; case "up": this.body[0].y -= 1; break; case "down": this.body[0].y += 1; break; }
然後,我們就要定義出界後遊戲結束瞭,這個就不多說瞭。
// 判斷是否出界,根據蛇頭判斷 if (this.body[0].x 150 || this.body[0].y 60) { clearInterval(timer); // 清除定時器 alert("出界啦,遊戲結束!"); document.getElementById('beginBox').style.display = 'block'; // 刪除舊的 for (var i = 0; i < this.body.length; i++) { if (this.body[i].flag != null) { // 如果剛吃完就死掉,會加一個值為null的 map.removeChild(this.body[i].flag); } } this.body = [ // 回到初始狀態, { x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 } ]; this.direction = 'right'; this.display(); // 顯示初始狀態 return false; // 結束 }
這裡的x和y都是整體的px
/蛇的盒子高寬,也就是除10,1 = 10px
。
然後,就是吃食物瞭。
當蛇頭與食物相遇,我們就認為它吃瞭食物,於是我們通過位置來判斷吃東西。
// 判斷蛇頭吃到食物,xy坐標重合, if (this.body[0].x == food.x && this.body[0].y == food.y) { // 蛇加一節,因為根據最後節點定,下面display時,會自動賦值的 this.body.push({ x: null, y: null, flag: null }); // 獲取蛇的長度 var len = this.body.length; // 根據蛇的長度,設置定時器頻率SnakeTime SnakeTime = SnakeTime - (len - 3) * 5; // SnakeTime最低不能小於40 if (SnakeTime < 40) { SnakeTime = 40; } refresh(); // 清除食物,重新生成食物 map.removeChild(food.flag); food.display(); }
說明一下:這個
flag
是當時創建食物時留下的一個對象。
![]()
創建食物方法我寫在瞭後面,一步步看吧。
而下面這部分代碼:
// 獲取蛇的長度 var len = this.body.length; // 根據蛇的長度,設置定時器頻率SnakeTime SnakeTime = SnakeTime - (len - 3) * 5; // SnakeTime最低不能小於40 if (SnakeTime < 40) { SnakeTime = 40; }
是為瞭可以動態的實現蛇吃到食物後,速度加快。
這裡,我有一個
refresh();
這個後面再看。
然後就是咬到自己,遊戲結束,這個不多說。
現在就到瞭構造食物瞭。
// 構造食物function Food() { this.width = 10; this.height = 10; this.display = function () { // 創建一個div(一節蛇身) var f = document.createElement('div'); this.flag = f; f.style.width = this.width + 'px'; f.style.height = this.height + 'px'; f.style.background = 'red'; f.style.position = 'absolute'; this.x = Math.floor(Math.random() * 80); this.y = Math.floor(Math.random() * 40); f.style.left = this.x * this.width + 'px'; f.style.top = this.y * this.height + 'px'; map.appendChild(f); }}
實際上,這個“食物”就是創建瞭蛇的一節身體。
後面也可以看見,有一個追加到蛇身。
map.appendChild(f);
看到這,你可能還疑惑,不應該啊,這也無法分辨出明確的蛇和食物啊,也就是說,很抽象啊。
因為我最後面,還有一個創建對象過程。
var snake = new Snake();var food = new Food();// 初始化顯示snake.display(); food.display();
將方法作為瞭一個對象。
而我們為瞭控制蛇的方向,我們需要使用鍵盤事件來改變蛇的屬性。
// 給body加按鍵事件,上下左右document.body.onkeydown = function (e) { // 有事件對象就用事件對象,沒有就自己創建一個,兼容低版本瀏覽器 var ev = e || window.event; switch (ev.keyCode) { case 38: if (snake.direction != 'down') { // 不允許返回,向上的時候不能向下 snake.direction = "up"; } break; case 40: if (snake.direction != "up") { snake.direction = "down"; } break; case 37: if (snake.direction != "right") { snake.direction = "left"; } break; case 39: if (snake.direction != "left") { snake.direction = "right"; } break; // 兼容WASD鍵 case 87: if (snake.direction != "down") { snake.direction = "up"; } break; case 83: if (snake.direction != "up") { snake.direction = "down"; } break; case 65: if (snake.direction != "right") { snake.direction = "left"; } break; case 68: if (snake.direction != "left") { snake.direction = "right"; } break; } };
當然,我這裡做瞭兼容,WASD
和上下左右鍵都通用控制。
最後就是點擊開始遊戲的事件瞭。
// 獲取開始按鈕var btn = document.getElementById('begin');// 點擊開始遊戲事件btn.onclick = function () { // 開始按鈕毛玻璃幕佈 var parent = this.parentNode; // 隱藏開始按鈕 parent.style.display = 'none'; // 獲取定時器時間 let time = SnakeTime; timer = setInterval(function () { snake.run(); }, time);}
我們這裡面是使用瞭setInterval
來實現不斷的前進走動。
timer = setInterval(function () { snake.run(); }, time);
但是啊,因為這個計時器他是不刷新的,也就是說啟動時,
time = 200
,然後你改變time
的值。此時
time
值確實變瞭,但是,這個setInterval
它隻認定第一次的設置,它不會動態改變。
那怎麼辦呢?首先,分析,他要什麼時候做出time
值的刷新,肯定是吃到食物的時候對吧。
於是,我們寫一個刷新函數。
// 定義刷新定時器方法function refresh() { // 停止定時器 clearInterval(timer); // 刷新定時器 timer = setInterval(function () { snake.run(); console.log(SnakeTime); }, SnakeTime);}
然後,你們就知道我上面說的refresh()
方法是什麼瞭吧?就是用於動態刷新setInterval
的。
這樣,這個貪吃蛇就寫好瞭。
效果
開始頁面
遊戲界面
完整源碼
Github
:JanYork/Snake
Gitee
:janyork/Snake