新的起点
这是我的第一篇博客,记载着我自学前端之后根据自己的一瞬想法而写成的‘东西’。虽然它很粗糙,但是它承载着我对前端的无限想象,是我前端之路的一个重要里程碑。
时刻回望过去,继续憧憬未来。
念念不忘,不求回响。
前言
作为一个前端小白,在b站学习完pink老师讲的动画原理之后,我脑中就有了写一个小游戏的想法。然后发现贪吃蛇好像还挺好实现的,然后就动手开始试着写了一下。
点击试玩⚡
实现过程
用 HTML 和 CSS 构建场景和物件
HTML 部分
我的做法就是让整个页面由div标签构成,场地是个div,目标物是个div,蛇头和蛇身都是由一个个的div构成。我加入了障碍物的设定,就是每过一段时间就会在地图上生成一个障碍物。所有的蛇身会被放入类名为allBody的div中,所有的障碍物会被放入类名为allBarrier的div中。
完整 HTML 代码如下:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Snake</title > <link rel ="stylesheet" href ="./snake.css" > </head > <body > <div class ="wall" > <div class ="remind" > </div > <div class ="ground" > <div class ="target" > </div > <div class ="head" > </div > <div class ="allBody" > </div > <div class ="allBarrier" > </div > </div > </div > <script src ="./snake.js" > </script > </body > </html >
CSS 部分
墙,就是除了整个页面除去内部场地的部分,所以给墙加上绝对定位,给提示和场地加上相对定位。而其他部分在场地内,加上绝对定位。
完整 CSS 代码如下:
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 .wall { position : absolute; left : 0 ; top : 0 ; width : 100 %; height : 100 %; background-color : greenyellow; } .remind { position : relative; left : 0px; top : 0px; width : 50px; height : 50px; background-color : pink; } .ground { position : relative; left : 50px; top : 0 ; width : 0 ; height : 0 ; background-color : white; } .head { position : absolute; left : 0 ; top : 0 ; width : 50px; height : 50px; background-color : blue; } .body { position : absolute; left : 0 ; top : 0 ; width : 50px; height : 50px; background-color : aquamarine; } .barrier { position : absolute; left : 0 ; top : 0 ; width : 50px; height : 50px; background-color : gray; } .target { position : absolute; left : 0 ; top : 0 ; width : 50px; height : 50px; background-color : red; }
用 JavaScript 控制各种功能的实现
声明
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 let wall = document .querySelector ('.wall' );let remind = document .querySelector ('.remind' );let ground = document .querySelector ('.ground' );let target = document .querySelector ('.target' );let head = document .querySelector ('.head' );let allBody = document .querySelector ('.allBody' );let allBarrier = document .querySelector ('.allBarrier' );let newBody = document .createElement ('div' );let newBarrier = document .createElement ('div' );let body = allBody.querySelectorAll ('.body' );let barrier = allBarrier.querySelectorAll ('.barrier' );let headX = randomX ();let headY = randomY ();let bodyX = head.offsetLeft ;let bodyY = head.offsetTop ;let targetX = randomX ();let targetY = randomY ();let barrierX = randomX ();let barrierY = randomY ();let time = 0 ;let times = 0 ;let score = 0 ;let flag = true ;let turnUp = true ;let turnLeft = true ;let turnDown = true ;let turnRight = true ;let single = true ;
随机生成函数得到随机值
随机获取 a~b 之间的一个整数:Math.round(Math.random() * ( b - a ) + a);
我们要生成是从场所内出现并且间隔为50的随机值
1 2 3 4 5 6 7 8 function randomX ( ) { return Math .round (Math .random () * (wall.offsetWidth / 50 - 4 ) + 1 ) * 50 ; }function randomY ( ) { return Math .round (Math .random () * (wall.offsetHeight / 50 - 4 ) + 1 ) * 50 ; }
蛇头的生成和控制
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 function setHead ( ) { headX = randomX (); headY = randomY (); head.style .left = headX + 'px' ; head.style .top = headY + 'px' ; }function go (dir ) { turn (dir); clearInterval (head.timer ); judge (dir); head.timer = setInterval (function ( ) { judge (dir); }, 100 ); }function judge (dir ) { body = allBody.querySelectorAll ('.body' ); barrier = allBarrier.querySelectorAll ('.barrier' ); followHead (); switch (dir) { case 'up' : head.style .top = head.offsetTop - 50 + 'px' ; break ; case 'left' : head.style .left = head.offsetLeft - 50 + 'px' ; break ; case 'down' : head.style .top = head.offsetTop + 50 + 'px' ; break ; case 'right' : head.style .left = head.offsetLeft + 50 + 'px' ; break ; } isGetTarget (dir); isDead (dir); }
障碍物的生成
在满足不会生成到蛇身、目标物和蛇头附近200px位置的情况下生成障碍物
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 function setBarrier ( ) { newBarrier = document .createElement ('div' ); allBarrier.appendChild (newBarrier); newBarrier.classList .add ('barrier' ); barrierX = randomX (); barrierY = randomY (); for (let i = 0 ; i < body.length ; i++) { body = allBody.querySelectorAll ('.body' ); for (let j = 0 ; j < barrier.length ; j++) { barrier = allBarrier.querySelectorAll ('.barrier' ); if (barrierX === body[i].offsetLeft && barrierY === body[i].offsetTop || barrierX === barrier[j].offsetLeft && barrierY === barrier[j].offsetTop || Math .abs ((barrierX - head.offsetLeft )) < 200 || Math .abs ((barrierX - head.offsetLeft )) < 200 || barrierX === target.offsetLeft && barrierY === target.offsetTop ) { barrierX = randomX (); barrierY = randomY (); i = 0 ; j = 0 ; } } } newBarrier.style .left = barrierX + 'px' ; newBarrier.style .top = barrierY + 'px' ; }
目标物的生成及被吃到后的重置
在满足不会生成到蛇身、障碍物位置的情况下生成目标物
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 function setTarget ( ) { targetX = randomX (); targetY = randomY (); for (let i = 0 ; i < body.length ; i++) { body = allBody.querySelectorAll ('.body' ); for (let j = 0 ; j < barrier.length ; j++) { barrier = allBarrier.querySelectorAll ('.barrier' ); if (targetX === body[i].offsetLeft && targetY === body[i].offsetTop || targetX === barrier[j].offsetLeft && targetY === barrier[j].offsetTop ) { targetX = randomX (); targetY = randomY (); i = 0 ; j = 0 ; } } } target.style .left = targetX + 'px' ; target.style .top = targetY + 'px' ; }function isGetTarget (dir ) { if (targetX === head.offsetLeft && targetY === head.offsetTop ) { single = false ; times++; setTarget (); setBody (dir); } }
吃到目标物后蛇身的增长
吃到目标物时根据此时的方向来决定新蛇身的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function setBody (dir ) { newBody = document .createElement ('div' ); allBody.appendChild (newBody); newBody.classList .add ('body' ); if (dir === 'left' ) { newBody.style .left = head.offsetLeft + 50 + 'px' ; newBody.style .top = head.offsetTop + 'px' ; } if (dir === 'right' ) { newBody.style .left = head.offsetLeft - 50 + 'px' ; newBody.style .top = head.offsetTop + 'px' ; } if (dir === 'up' ) { newBody.style .left = head.offsetLeft + 'px' ; newBody.style .top = head.offsetTop + 50 + 'px' ; } if (dir === 'down' ) { newBody.style .left = head.offsetLeft + 'px' ; newBody.style .top = head.offsetTop - 50 + 'px' ; } }
控制蛇身跟着蛇头走
核心代码,通过这种方式来控制蛇身的走动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function followHead ( ) { body = allBody.querySelectorAll ('.body' ); if (body.length !== 0 ) { for (let i = body.length - 1 ; i >= 0 ; i--) { if (i === 0 ) { body[i].style .left = head.offsetLeft + 'px' ; body[i].style .top = head.offsetTop + 'px' ; } else { body[i].style .left = body[i - 1 ].offsetLeft + 'px' ; body[i].style .top = body[i - 1 ].offsetTop + 'px' ; } } } }
判断是否死亡
三种死亡情况:撞墙死、撞身死和撞障碍物死
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function isDead (dir ) { switch (dir) { case 'up' : if (head.offsetTop < 0 ) gameOver (); break ; case 'left' : if (head.offsetLeft < 0 ) gameOver (); break ; case 'down' : if (head.offsetTop > ground.offsetHeight ) gameOver (); break ; case 'right' : if (head.offsetLeft > ground.offsetWidth ) gameOver (); break ; } for (let i = 0 ; i < body.length ; i++) { if (body[i].offsetLeft === head.offsetLeft && body[i].offsetTop === head.offsetTop ) gameOver (); } for (let j = 0 ; j < barrier.length ; j++) { if (barrier[j].offsetLeft === head.offsetLeft && barrier[j].offsetTop === head.offsetTop ) gameOver (); } }
死亡后的结算和重启
这是我的计分公式:score = Math.round(times * 3.5 * (1 + 2 / (time + 1)))
欢迎提出更合理的计分公式
1 2 3 4 5 6 7 8 9 10 function gameOver ( ) { clearInterval (head.timer ); score = Math .round (times * 3.5 * (1 + 2 / (time + 1 ))); if (score > 100 ) score = 100 ; alert ('本次你坚持了' + time + '秒,吃到了' + times + '次红方块, 系统评分为' + score + '分,再来一次吧~' ); setHead (); setTarget (); window .location .reload (); }
如何控制有蛇身时,不能直接往反方向走
排己思想,先将所有方向都设置为false,然后将选定方向的反方向设为false
1 2 3 4 5 6 7 8 9 10 11 12 function turn (dir ) { turnUp = true ; turnLeft = true ; turnDown = true ; turnRight = true ; switch (dir) { case 'up' : turnDown = false ; break ; case 'left' : turnRight = false ; break ; case 'down' : turnUp = false ; break ; case 'right' : turnLeft = false ; break ; } }
其他
document.addEventListener(‘keydown’, function (e) {…})
和remind.addEventListener(‘click’, function () {alert(‘…’);})就不贴出来啦
完整的 JavaScript 的代码可以去 我的 Github 取。
总结
对于一个初学者来说,能自己通过思考,结合之前学过的知识来完成一个小游戏,还是蛮有成就感的事情啊。如果有初学者也想自己动手写一下这个小游戏的话,可以参考我的思路来试着完成。我的代码肯定还有很多需要优化的地方,恳请大佬们提出意见和指导 QWQ