设计 直接步入正题,由于游戏本身游玩以及操作比较简单,因此这次的游戏界面会有点简陋,实在是不知道该加啥东西了😿
游戏主体部分是宽为650px,高为450px,而小蛇的每一格身体都是25 * 25的,因此整个游戏界面有18行,26列。
这一次我会尽量把每一个功能的代码都记录下来,一是因为这个贪吃蛇代码肯定比俄罗斯方块要少,二是因为,不写代码的话,我这篇博客已经结束了,这也太短了吧喂!
代码 HTML代码如下,布局都是采用position:absolute的方式,因为部件比较少,这样代码也不会很繁琐,而且很简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <title > 贪吃蛇</title > <script src ="./snake.js" > </script > <style > div { border : #000000 3.5px solid; } .container { height : 570px ; width : 707px ; position : relative; margin : 0px auto; margin-top : 75px ; text-align : center; } .gameDiv { height : 450px ; width : 650px ; position : absolute; left : 25px ; top : 70px ; } p { position : absolute; font-weight : 600 ; right : 50px ; } .length { top : -5px ; } .score { top : 20px ; } h2 { position : absolute; font-weight : 600 ; left : 305px ; font-size : 30px ; top : -10px ; } .deflory { position : absolute; bottom : 10px ; right : 5px ; height : 30px ; width : 150px ; } .snakeCube { position : absolute; } </style > </head > <body onload ="init()" > <div class ="container" > <h2 > 贪吃蛇</h2 > <p class ="length" > 长度: </p > <p class ="score" > 分数: </p > <div class ="gameDiv" > </div > <img src ="img/Deflory.png" class ="deflory" /> </div > </body > </html >
1. 移动贪吃蛇 (1) 键盘监听
首先需要让小蛇动起来,因此就会需要键盘监听事件。
凡事都不能一口吃成一个大胖子,不要急着写移动的功能,先看看能否监听到键盘。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 var ROWS = 18 var COLS = 26 var SNAKE_SIZE = 25 function init ( ) { onKeyDown(); } function onKeyDown ( ) { document .onkeydown = function (event ) { switch (event.keyCode){ case 38 : case 87 :{ console .log("上" ); break ; } case 37 : case 65 :{ console .log("左" ); break ; } case 40 : case 83 :{ console .log("下" ); break ; } case 39 : case 68 :{ console .log("右" ); break ; } } } }
稍微按了几下,浏览器的输出也正常,接下来就开始写移动的函数了。
但在此之前,还有一件事。
(2) 创建小蛇
我们没有东西去移动啊!所以要写一个小蛇初始化的函数。
此处snakeCube类我只写了个position:absolute,不然每次写挺麻烦的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function createSnake () { game_div = document.getElementsByClassName("gameDiv" )[0 ]; snakeHead = document.createElement("img" ); snakeHead.setAttribute("src" ,"./img/right.png" ) snakeHead.className = "snakeCube" ; game_div.appendChild(snakeHead); for (var i=0 ;i<3 ;i++){ snakeBody = document.createElement("img" ); snakeBody.setAttribute("src" ,"./img/body.png" ) snakeBody.className = "snakeCube" ; game_div.appendChild(snakeBody); } }
如果单单只是创建元素,而不给它“定位”,也就是设定top和left值,他显示是会叠在一起的。
(3) 定位
综上所述,还有一件事,再写一个定位的函数。不要忘了在createSnake的末尾加上这个定位函数。
1 2 3 4 5 6 7 8 9 10 var snakeLocation = [[200 , 125 ],[175 , 125 ],[150 , 125 ],[125 , 125 ]]function snakeLocate ( ) { var snakes = document .getElementsByClassName("snakeCube" ); var len = snakes.length; for (var i=0 ;i<len;i++){ snakes[i].style.left = snakeLocation[i][0 ] + "px" ; snakes[i].style.top = snakeLocation[i][1 ] + "px" ; } }
(4) 移动
好了,历经千辛万苦,终于可以准备让这个小蛇动起来了。
先来解释下这里的逻辑吧,想想贪吃蛇是怎么移动的呢?
是不是头先动,然后紧接着第一节身体移动到原先头的位置,第二节身体移动到原先第一节身体的位置。因此我们只需要把snakeLocation数组从后往前覆盖就行了(不能从先往后噢,想想就明白了)。
此外这里x,y都是单位长度,因此还要乘以蛇的大小。
1 2 3 4 5 6 7 8 9 10 function move (x, y ) { for (var i=snakeLen-1 ;i>0 ;i--){ snakeLocation[i][0 ] = snakeLocation[i-1 ][0 ]; snakeLocation[i][1 ] = snakeLocation[i-1 ][1 ]; } snakeLocation[0 ][0 ] = snakeLocation[0 ][0 ] + SNAKE_SIZE * x; snakeLocation[0 ][1 ] = snakeLocation[0 ][1 ] + SNAKE_SIZE * y; snakeLocate(); }
好像略微有些惊悚嘛,不过没关系,修改一下移动时头部的方向就行。
只需要在移动前修改方向变量,定位时判断方向即可,这里代码太多就放出来一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 var snakeHeadDirection = "right" ;function onKeyDown ( ) { document .onkeydown = function (event ) { switch (event.keyCode){ case 38 : case 87 :{ snakeHeadDirection = "up" ; move(0 , -1 ); console .log("上" ); break ; } } } } function snakeLocate ( ) { var snakes = document .getElementsByClassName("snakeCube" ); for (var i=0 ;i<snakeLen;i++){ snakes[i].style.left = snakeLocation[i][0 ] + "px" ; snakes[i].style.top = snakeLocation[i][1 ] + "px" ; } switch (snakeHeadDirection){ case "up" :{ snakes[0 ].setAttribute("src" ,"./img/up.png" ) break ; } } }
2. 碰撞判定 (1) 自身碰撞判定
贪吃蛇碰到自己身体就应该结束游戏了。
自身碰撞判定无非就是判断头和身体有没有碰撞,一个for-loop就行了。
当然,我们还需要一个flag,来判断当前是否已经结束,默认是false。
函数实现起来比较轻松,注意每次move都需判断,而且得在“定位”之前判断。
1 2 3 4 5 6 7 8 9 10 function isTouchItself ( ) { var left = snakeLocation[0 ][0 ]; var top = snakeLocation[0 ][1 ]; for (var i=1 ;i<snakeLen;i++){ if (left == snakeLocation[i][0 ] && top == snakeLocation[i][1 ]){ gameover = true ; } } }
然后还得修改一下键盘监听事件,只有gameover = false时才监听移动事件。
这里我多添加了两个方法,一会儿实现。
一个是gameover = true时的restart();一个是gameover = false时的pause()。
前者用于重新开始,后者用于游戏暂停
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 function onKeyDown ( ) { document .onkeydown = function (event ) { if (!gameover){ switch (event.keyCode){ case 38 : case 87 :{ snakeHeadDirection = "up" ; move(0 , -1 ); console .log("上" ); break ; } case 37 : case 65 :{ snakeHeadDirection = "left" ; move(-1 , 0 ); console .log("左" ); break ; } case 40 : case 83 :{ snakeHeadDirection = "down" ; move(0 , 1 ); console .log("下" ); break ; } case 39 : case 68 :{ snakeHeadDirection = "right" ; move(1 , 0 ); console .log("右" ); break ; } case 32 :{ pause(); break ; } } }else { switch (event.keyCode){ case 32 :{ restart(); break ; } } } } }
(2) 边框碰撞判定
贪吃蛇是不能穿越这个游戏的边框的,至于碰到边框是会死,还是从另一边出来可以自由发挥。
我这里实现的是从另一头出来的方式。
类似自身碰撞判定,也是要在“定位”之前判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function crashBoundary ( ) { var left = snakeLocation[0 ][0 ]; var top = snakeLocation[0 ][1 ]; if (left < 0 ){ snakeLocation[0 ][0 ] = 625 ; }else if (left > 625 ){ snakeLocation[0 ][0 ] = 0 ; }else if (top < 0 ){ snakeLocation[0 ][1 ] = 425 ; }else if (top > 425 ){ snakeLocation[0 ][1 ] = 0 ; } }
3.游戏结束 (1) 计时器
首先,我们的小蛇不能自己动,需要设置一个计时器。
因为需要暂停和继续游戏,所以我们在方法中判断,如果当前有计时器就取消它,没有则设置一个。
记得在初始化的时候调用一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var timer = null ;function pause ( ) { if (timer == null ){ timer = setInterval (function ( ) { switch (snakeHeadDirection){ case "up" :{ move(0 , -1 ); break ; } case "down" :{ move(0 , 1 ); break ; } case "left" :{ move(-1 , 0 ); break ; } case "right" :{ move(1 , 0 ); break ; } } }, 200 ); }else { clearInterval (timer); timer = null ; } }
暂停的时候,屏幕上空荡荡的也不大好,给人有一种没有暂停的错觉。所以加一个div,美化下暂停的界面。
1 2 3 4 5 6 7 8 9 .paused { position : absolute; display : none; top : 210px ; z-index : 9 ; color : #ffffff ; left : 205px ; line-height : 60px ; }
1 2 3 <h1 class ="paused" > 按下 SPACE 继续游戏 </h1 >
在修改下上面pause方法,暂停的时候显示提示,继续时隐去提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function pause ( ) { if (timer == null ){ timer = setInterval (function ( ) { switch (snakeHeadDirection){ case "up" :{ move(0 , -1 ); break ; } case "down" :{ move(0 , 1 ); break ; } case "left" :{ move(-1 , 0 ); break ; } case "right" :{ move(1 , 0 ); break ; } } }, 200 ); var end = document .getElementsByClassName("paused" )[0 ] end.style.display = "none" ; }else { clearInterval (timer); timer = null ; var end = document .getElementsByClassName("paused" )[0 ] end.style.display = "block" ; } }
(2) 游戏结束
在此之前,我又写了个div,可以先看看效果。同暂停类似,只需要将它的display值默认设为none,待到游戏结束时在改成block即可。
处于美观,我将游戏背景颜色改成了深灰色。
1 2 3 4 5 6 7 8 9 .gameover { display : none; position : absolute; top : 190px ; z-index : 9 ; color : #ffffff ; left : 205px ; line-height : 60px ; }
1 2 3 4 5 <h1 class ="gameover" > 游戏结束 <br > 按下 <font color ="#ee3000" > SPACE</font > 重新开始 </h1 >
1 2 3 4 5 function final ( ) { var end = document .getElementsByClassName("gameover" )[0 ] end.style.display = "none" ; pause(); }
(3) 重新开始
重新开始需要把所有的变量都重置,然后删除游戏中的贪吃蛇,再把游戏结束的提示隐去,然后和init()一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function restart ( ) { snakeLocation = [[200 , 125 ],[175 , 125 ],[150 , 125 ],[125 , 125 ]] snakeLen = 4 ; snakeHeadDirection = "right" ; gameover = false ; timer = null ; var gameDiv = document .getElementsByClassName("gameDiv" )[0 ]; var children = gameDiv.children; var childrenLen = children.length; for (var i=childrenLen-1 ;i>=0 ;i--){ gameDiv.removeChild(children[i]); } var end = document .getElementsByClassName("gameover" )[0 ] end.style.display = "none" ; createSnake(); pause(); }
4. 分数系统 最后就是小蛇蛇吃的食物啦。
(1) 食物是在屏幕中随机出现,而且吃完一个就会出现下一个。
创建食物同创建小蛇类似,只是多了个random函数,要确保食物随机在游戏界面中。
1 2 3 4 5 6 7 8 9 10 11 12 13 function createFood ( ) { foodLeft = Math .floor(Math .random() * COLS); foodTop = Math .floor(Math .random() * ROWS); var gameDiv = document .getElementsByClassName("gameDiv" )[0 ]; var food = document .createElement("img" ); food.className = "foodCube" ; food.setAttribute("src" ,"./img/food.png" ); food.style.top = foodTop * SNAKE_SIZE + "px" ; food.style.left = foodLeft * SNAKE_SIZE + "px" ; gameDiv.appendChild(food); updateData(); }
(2) 吃一个长度+1,分数+10,且小蛇的身体+1。
蛇头碰到食物即吃掉,删除该img,然后生成新的食物;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function eatFood ( ) { var left = snakeLocation[0 ][0 ]; var top = snakeLocation[0 ][1 ]; if (foodLeft == left && foodTop == top){ snakeLen++; score += 10 ; var food = document .getElementsByClassName("foodCube" )[0 ]; var gameDiv = document .getElementsByClassName("gameDiv" )[0 ]; gameDiv.removeChild(food); createFood(); grow(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function grow ( ) { var gameDiv = document .getElementsByClassName("gameDiv" )[0 ]; var body = document .createElement("img" ); body.className = "snakeCube" ; body.setAttribute("src" ,"./img/body.png" ); var lastTop = snakeLocation[snakeLen-2 ][1 ]; var lastLeft = snakeLocation[snakeLen-2 ][0 ]; var bodyTop = 0 ; var bodyLeft = 0 ; switch (snakeHeadDirection){ case "up" :{ bodyTop = lastTop + 1 ; bodyLeft = lastLeft; break ; } case "down" :{ bodyTop = lastTop - 1 ; bodyLeft = lastLeft; break ; } case "left" :{ bodyTop = lastTop; bodyLeft = lastLeft + 1 ; break ; } case "right" :{ bodyTop = lastTop; bodyLeft = lastLeft - 1 ; break ; } } body.style.top = bodyTop * SNAKE_SIZE + "px" ; body.style.left = bodyLeft * SNAKE_SIZE + "px" ; snakeLocation[snakeLen-1 ] = [bodyLeft, bodyTop]; gameDiv.appendChild(body); }
之后更新界面分数
1 2 3 4 5 6 7 function updateData ( ) { var scoreH1 = document .getElementsByClassName("score" )[0 ]; var lengthH1 = document .getElementsByClassName("length" )[0 ]; scoreH1.innerHTML = "分数:" + score; lengthH1.innerHTML = "长度:" + snakeLen; }
(3) 分数越高,小蛇移动速度越快。
三句代码即可,记得添加到适当的位置,这里就不给出来了。
1 2 3 var speed = 1 ;speed += score / 10 * 0.05 ; setInterval (function ( ) {}, 100 / speed);
5. 游戏性 至此,贪吃蛇的代码已全部写完,之后可以按自己的想法添加一些游戏的规则,例如可以添加一个红色果实的设定,效果是绿色果实的三倍,出现概率10%。我就不实现了,现在已经凌晨了,累了。
游戏运行画面如下。
以后写小游戏就不写这么详细了,这也太累了😭