本文阅读时间10分钟,要求你有一定的html5+css3+es6基础。打开编辑器跟着我一起把代码敲一遍,效果更佳。
首先我们要有一个蛇的类,它要移动自己的身体,吃到果子;移动的时候撞了墙,或者咬到了自己的身体,会死;吃到了果子,身长会增加。
所以这个蛇的类,要有一个Move的方法,Move里包含Eat,还有控制Move方向的Controller。
那么怎么让它Move呢?是通过不断往蛇前进的方向添加DOM,并在尾巴处删除DOM?还是通过不断地改变蛇头和蛇尾的背景色来实现移动的效果?我选择了后者。
把蛇的活动区域(300px * 500px)打上格子,每个格子是一个div(20px * 20px)。每个div的ID按照自己所在的位置编号:比如第一排的第一个,编号为”grid-1-1“。
1 <div class="game-container"> 2 <h1>SUPER SNAKE</h1> 3 <p class="first-one"> 4 <span>CURRENT SCORE:</span> 5 <strong class="score"></strong> 6 </p> 7 <p> 8 <span>HISTORY SCORE:</span> 9 <strong class="history-score"></strong> 10 </p> 11 <div class="statement"></div> 12 <section class="snake-area"></section> 13 </div>
const initializeGrids = (height = 500, width = 300) => { for (let i = 1; i < height / 20 + 1; i++) { for (let j = 1; j < width / 20 + 1; j++) { const grid = document.createElement('div'); grid.setAttribute('id', `grid-${i}-${j}`); grid.classList.add('grid'); const con = document.querySelector('.snake-area').appendChild(grid); } } const showSnake = new Snake(height, width); const showScore = new Score(); }; initializeGrids();
这样我们就能通过编号来记录并控制蛇运动的路线了。我们给蛇设置的初始身长为3个格子的长度(背景色为粉色);初始位置是第一排从左边起的前三个(把这三个位置保存在数组里:['grid-1-1', 'grid-1-2', 'grid-1-3']);初始的移动方向是从左到右。
为了实现从左到右的移动,每隔500ms,给蛇头右边的第一个div添加粉色背景,并去掉蛇尾的背景色。同时移除蛇身体数组的第一个元素,并添加新的蛇头位置。
1 class Snake { 2 3 constructor(height, width) { 4 5 this.y_edge = height / 20; 6 7 this.x_edge = width / 20; 8 9 this.body = ['grid-1-1', 'grid-1-2', 'grid-1-3']; 10 11 this.interval = null; 12 13 this.wrapper = document.querySelector('.statement') 14 15 this.addColor(); 16 17 this.move(); 18 19 } 20 21 addColor() { 22 23 this.body.forEach(item => { 24 25 document.querySelector(`#${item}`).classList.add('snake'); 26 27 }) 28 29 }; 30 31 removeColor() { 32 33 const uncolored = this.body.shift() 34 35 document.querySelector(`#${uncolored}`).classList.remove('snake'); 36 37 }; 38 39 move(to = 'right') { 40 41 this.interval = setInterval(() => { 42 43 const headId = this.body.slice(-1)[0], 44 45 headY = headId.split('-')[1], 46 47 headX = headId.split('-')[2], 48 49 directions = { 50 51 right: [`${headY}`, +headX + 1, this.x_edge], 52 53 left: [`${headY}`, +headX - 1, this.x_edge], 54 55 down: [+headY + 1, `${headX}`, this.y_edge], 56 57 up: [+headY - 1, `${headX}`, this.y_edge] 58 59 }; 60 61 this.body.push(`grid-${directions[to][0]}-${directions[to][1]}`); 62 63 this.removeColor(); 64 65 this.addColor(); 66 67 }; 68 69 }, 500); 70 71 }; 72 73 };
取出蛇头的id,并用headY和headX分别保存蛇头在Y轴和X轴上的位置;y_edge和x_edge分别为蛇活动区域的下边界和有边界;directions主要保存蛇的位移和位置,为了方便后面使用,把y_edge和x_edge也放在了里面。
接下来我们来写controller方法,即通过控制键盘的上下左右键,来实现蛇前进方向的转变:
controller() { document.addEventListener('keyup', e => { if (this.moveTo !== e.keyCode && this.moveTo !== e.keyCode + 2 && this.moveTo !== e.keyCode - 2 && !this.stopGame) { this.moveTo = e.keyCode; clearInterval(this.interval); switch (e.keyCode) { case 40: this.move('down'); break; case 38: this.move('up'); break; case 37: this.move('left'); break; case 39: this.move('right'); break; default: break; } } }, false);
绑定keyup事件。this.moveTo用来保存当前按键的值,避免重复按键或按下方向相反的键。this.stopGame后面会讲到。
keyup事件触发之后,清除当前interval(停止蛇在当前方向上的运动),改变方向后(this.moveTo取得了新的值)继续移动。
蛇的移动到这里告一段落。下面讲吃果子且身体长度增加的部分。
果子的出现位置是随机的,且每次蛇吃完以后需要重新生成。于是我们写一个Food的类:
class Food { constructor() { if (!document.querySelector('.food')) { this.x = Math.floor(13 * Math.random() + 2); this.y = Math.floor(23 * Math.random() + 2); document.querySelector(`#grid-${this.y}-${this.x}`).classList.add('food'); return { X: this.x, Y: this.y }; } } };
再在蛇的类里添加一个吃的方法:
1 eat(x, y) { 2 3 if (x == this.food.X && y == this.food.Y) { 4 5 document.querySelector(`#grid-${y}-${x}`).classList.remove('food'); 6 7 this.shouldRemove = false; 8 9 this.showScore.addScore(); 10 11 this.food = new Food(); 12 13 } 14 15 };
到这里为止核心的部分就完成了。最后我们再添加一个得分的类。
1 class Score { 2 3 constructor() { 4 5 try { 6 7 localStorage.getItem('historyScore') ? 8 9 this.historyScore = localStorage.getItem('historyScore') : 10 11 this.historyScore = 0; 12 13 document.querySelector('.history-score').innerHTML = this.historyScore; 14 15 } catch { 16 17 alert('Your localStorage is not functioning, open this page in another browser!') 18 19 } 20 21 this.score = 0; 22 23 document.querySelector('.score').innerHTML = this.score; 24 25 }; 26 27 addScore() { 28 29 this.score++; 30 31 document.querySelector('.score').innerHTML = this.score; 32 33 }; 34 35 updateHistory() { 36 37 if (this.score > this.historyScore) { 38 39 try { 40 41 localStorage.setItem('historyScore', this.score); 42 43 document.querySelector('.history-score').innerHTML = this.score; 44 45 } catch { 46 47 alert('Your localStorage isn't functioning, open this page in another browser!') 48 49 } 50 51 } 52 53 } 54 55 };
完整的代码可点此阅读:https://gist.github.com/PengHoliday/65029f78f385b5884c1b9dfd2162c611