当前位置:   article > 正文

(新手向js项目)小白必看的原生js实现贪吃蛇游戏_原生js项目

原生js项目

1. 实现游戏页面布局

index.html

  1. <html lang="en">
  2. <head>
  3. <meta charset="UTF-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>贪吃蛇</title>
  7. <link rel="stylesheet" href="../css/index.css">
  8. </head>
  9. <body>
  10. <!-- 整个游戏容器 -->
  11. <div class="container">
  12. <!-- 开始按钮 -->
  13. <button class="startBtn"></button>
  14. <!-- 暂停按钮 -->
  15. <button class="pauseBtn"></button>
  16. </div>
  17. <script src="../js/config.js"></script>
  18. <script src="../js/index.js"></script>
  19. </body>
  20. </html>

包括游戏整体的容器container,开始游戏按钮startBtn,中途暂停按钮pauseBtn

index.css

 绘制高和宽为600px的正方形

 插入开始游戏和继续游戏的按钮,需要水平垂直居中

  1. /*父元素设置为*/
  2. display: flex;
  3. /*水平居中*/
  4. justify-content: center;
  5. /*垂直居中*/
  6. align-items: center;
  1. *{
  2. /* 去除内外边距 */
  3. margin: 0;
  4. padding: 0;
  5. }
  6. /* 整体游戏容器 */
  7. .container{
  8. display: flex;
  9. /* 蛇要移动,是绝对定位 */
  10. position: relative;
  11. width: 600px;
  12. height: 600px;
  13. background-color: #fdfdfd;
  14. border: 20px solid #9bd0e7;
  15. margin: 20px auto;
  16. justify-content: center;
  17. align-items: center;
  18. }
  19. .container button{
  20. border: none;
  21. outline: none;
  22. }
  23. .startBtn{
  24. width: 200px;
  25. height: 120px;
  26. background: url(../img/start.jpg.jpg) center/contain no-repeat;
  27. display: block;
  28. }
  29. .pauseBtn{
  30. width: 200px;
  31. height: 70px;
  32. background: url(../img/play.webp.jpg) center/contain no-repeat;
  33. display: none;
  34. }

其中的图片是自己用画板绘制的,可在附录中找到 

2. 游戏逻辑

 2.1 主方法

index.js

  1. function main() {
  2. //1.初始化游戏
  3. initGame();
  4. //2.绑定事件
  5. bindEvent();
  6. }
  7. main();
  2.1.1 初始化游戏

 config.js 创建变量

  1. var gridData = []; //存储地图对象
  2. //整个网格的行与列
  3. var tr = 30;
  4. var td = 30; //因为地图600px, 即每个格子20pixel
'
运行

  index.js

  1. /**
  2. * 初始化游戏方法
  3. */
  4. function initGame() {
  5. //1.初始化地图
  6. for (var i = 0; i < tr; i++) {
  7. for (var j = 0; j < td; j++) {
  8. gridData.push({ //从左往右从上至下存放坐标{x,y} = {j,i}
  9. x: j,
  10. y: i
  11. });//遍历每一个格子,存放数据
  12. }
  13. }
  14. //console.log(gridData);
  15. //2.绘制蛇
  16. drawSnake(snake);
  17. //3.绘制食物
  18. drawFood();
  19. }
'
运行
  2.1.1.1 绘制snake

 config.js

  1. //蛇的身体大小
  2. var snakeBody = 20;
  3. //蛇相关配置
  4. var snake = {
  5. //蛇的初始位置
  6. snakePos : [
  7. //初始有三段身体,有一个头
  8. {x:0,y:0,domContent:"",flag:'body'},
  9. {x:1,y:0,domContent:"",flag:'body'},
  10. {x:2,y:0,domContent:"",flag:'body'},
  11. {x:3,y:0,domContent:"",flag:'head'},
  12. ]
  13. }
'
运行

 index.js

  1. /**
  2. * 绘制蛇的方法
  3. * @param {*} snake
  4. */
  5. function drawSnake(snake) {
  6. //遍历snakePos
  7. for (var i = 0; i < snake.snakePos.length; i++) {
  8. //判断是否创建
  9. if (!snake.snakePos[i].domContent) {
  10. //进入此if表示第一次创建蛇
  11. snake.snakePos[i].domContent = document.createElement("div");
  12. //靠父元素计算移动位置
  13. snake.snakePos[i].domContent.style.position = "absolute"; //postion: absolute
  14. snake.snakePos[i].domContent.style.width = snakeBody + "px"; //20px
  15. snake.snakePos[i].domContent.style.height = snakeBody + "px";
  16. snake.snakePos[i].domContent.style.left = snake.snakePos[i].x * snakeBody + "px";//距离父元素左边距
  17. snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody + "px";//距离父元素上边距
  18. if (snake.snakePos[i].flag === 'head') {
  19. //当前是蛇头
  20. snake.snakePos[i].domContent.style.background = "linear-gradient(to right, #32cd32, #006400)";
  21. snake.snakePos[i].domContent.style.borderRadius = '50%';
  22. } else {
  23. //当前是蛇身
  24. snake.snakePos[i].domContent.style.background = "#ADFF2F";
  25. snake.snakePos[i].domContent.style.borderRadius = '50%';
  26. }
  27. }
  28. //需要将创建的dom元素添加到container容器中
  29. document.querySelector(".container").append(snake.snakePos[i].domContent);
  30. }
  31. }
'
运行

蛇头和蛇身都用填充背景色来完成绘制,添加圆角'borderRadius'使得其展示为圆形 

2.1.1.2 绘制food

config.js

  1. //食物相关配置
  2. var food = {
  3. x: 0,
  4. y: 0,
  5. domContent: ""
  6. }

index.js

  1. /**
  2. * 绘制食物的方法
  3. */
  4. function drawFood() {
  5. //1.食物坐标随机
  6. //2.食物不能生成在container之外,border上,snakeBody,snakeHead)
  7. while (true) {
  8. //构造死循环,直到生成符合要求的食物坐标才能退出循环
  9. var isRepeat = false; //默认生成食物坐标符合要求
  10. food.x = Math.floor(Math.random() * tr);
  11. food.y = Math.floor(Math.random() * tr);
  12. //查看坐标是否符合要求(遍历蛇)
  13. for (let i = 0; i < snake.snakePos.length; i++) {
  14. if (snake.snakePos[i].x === food.x && snake.snakePos[i].y === food.y) {
  15. //当前食物与蛇重合,不符合要求
  16. isRepeat = true;
  17. break;//跳出当前for循环
  18. }
  19. }
  20. if (!isRepeat) {
  21. //当isRepeat为false的时候
  22. //跳出while
  23. break;
  24. }
  25. }
  26. //跳出while循环,得到food.x, food.y
  27. if (!food.domContent) {
  28. food.domContent = document.createElement("div");
  29. food.domContent.style.position = "absolute";
  30. food.domContent.style.width = snakeBody + "px";
  31. food.domContent.style.height = snakeBody + "px";
  32. food.domContent.style.background = "#A52A2A";
  33. food.domContent.style.borderRadius = '50%';
  34. document.querySelector('.container').append(food.domContent);
  35. }
  36. food.domContent.style.left = food.x * snakeBody + "px";
  37. food.domContent.style.top = food.y * snakeBody + "px";
  38. }
'
运行

生成food随机,可以使用Math.random() * tr + 0来完成,共有30种情况(30个格子,包含坐标0,可省略)(这种算法能保证food不会生成在border上)

for循环遍历snake,保证food的坐标和snake坐标不同,即没有生成在snake上,即合法 

2.1.2 键盘事件绑定

当用户在键盘上按下上下左右的时候,snake需要有方向上的更改(包括整体转向和蛇头角度转向)

index.js

  1. /**
  2. * 绑定事件
  3. */
  4. function bindEvent() {
  5. //1.用户键盘点击上下左右,蛇移动
  6. document.onkeydown = function (e) {
  7. if ((e.key === 'ArrowRight' || e.key.toLowerCase() === 'd') && snake.direction.flag !== 'left') {
  8. //1.右,右侧新增元素,遍历移动到最右侧,原最左侧的元素删除
  9. //x+1
  10. snake.direction = directionNum.right;
  11. }
  12. if ((e.key === 'ArrowUp' || e.key.toLowerCase() === 'w') && snake.direction.flag !== 'bottom') {
  13. //2.上
  14. //y-1
  15. //往下走的时候不能按上键
  16. snake.direction = directionNum.top;
  17. }
  18. if ((e.key === 'ArrowDown' || e.key.toLowerCase() === 's') && snake.direction.flag !== 'top') {
  19. //3.下
  20. //y+1
  21. snake.direction = directionNum.bottom;
  22. }
  23. if ((e.key === 'ArrowLeft' || e.key.toLowerCase() === 'a') && snake.direction.flag !== 'right') {
  24. //4.左
  25. //x-1
  26. snake.direction = directionNum.left;
  27. }
  28. }
  29. }
'
运行

需要注意的是,默认如下情况:

当用户更改的方向不能和原来的方向相反,否则会碰到自己的身体。 

2.2 蛇移动 snakeMove()

逻辑:

定义一个新蛇头(newHead),移动后增加一个新的dom元素放入新头,旧头(oldHead)更改为body

如果没有吃到food,蛇长度不会增加,则原最后一段body的dom元素被删除

如果吃到了food,蛇长度增加,原最后一段body的dom元素不删除

因此,还需要增加一个碰撞检测

另外:如果吃到了food,food元素display更改为none,且重新绘制一次(drawFood();)

2.2.1 snake移动方向

config.js

给snake对象增加默认的移动方向为right

控制蛇上下左右移动,坐标会相应地增加/减少

  1. //新蛇头和旧蛇头之间的关系
  2. //用directionNum和旧蛇头坐标做计算
  3. var directionNum = {
  4. left: {x: -1, y: 0, flag: 'left'},
  5. right: {x: 1, y: 0, flag: 'right'},
  6. top: {x: 0, y: -1, flag: 'top'},
  7. bottom: {x: 0, y: 1, flag: 'bottom'},
  8. }
  9. //蛇相关配置
  10. var snake = {
  11. //蛇一开始移动的方向->右
  12. direction:directionNum.right,
  13. //蛇的初始位置
  14. snakePos : [
  15. //初始有三段身体,有一个头
  16. {x:0,y:0,domContent:"",flag:'body'},
  17. {x:1,y:0,domContent:"",flag:'body'},
  18. {x:2,y:0,domContent:"",flag:'body'},
  19. {x:3,y:0,domContent:"",flag:'head'},
  20. ]
  21. }
'
运行

newHead的坐标 = oldHead的坐标 + config中定义的不同方向会导致的坐标变化

因此,总体index.js如下

  1. /**
  2. * 蛇移动方法
  3. */
  4. function snakeMove() {
  5. var oldHead = snake.snakePos[snake.snakePos.length - 1];
  6. //根据方向计算出新蛇头的坐标
  7. var newHead = {
  8. domContent: "",
  9. x: oldHead.x + snake.direction.x,
  10. y: oldHead.y + snake.direction.y,
  11. flag: 'head'
  12. }
  13. //碰撞检测
  14. //food, body, 墙壁
  15. var collideCheckResult = isCollide(newHead);
  16. if (collideCheckResult.isCollide) {
  17. //进入此if,碰到墙或者body
  18. if (window.confirm(`
  19. 游戏结束,您当前的得分为${score}分,是否要重新开始游戏?
  20. `)) {
  21. //用户点确定,重新开始游戏
  22. //清空所有div
  23. document.querySelector('.container').innerHTML = `
  24. <!-- 开始按钮 -->
  25. <button class="startBtn" style="display: none"></button>
  26. <!-- 暂停按钮 -->
  27. <button class="pauseBtn" style="display: none"></button>
  28. `;
  29. score = 0;
  30. snake = {
  31. // 蛇一开始移动的方向 -> 右
  32. direction: directionNum.right,
  33. //蛇的初始位置
  34. snakePos: [
  35. //初始有三段身体,有一个头
  36. { x: 0, y: 0, domContent: "", flag: 'body' },
  37. { x: 1, y: 0, domContent: "", flag: 'body' },
  38. { x: 2, y: 0, domContent: "", flag: 'body' },
  39. { x: 3, y: 0, domContent: "", flag: 'head' },
  40. ]
  41. }
  42. food = {
  43. x: 0,
  44. y: 0,
  45. domContent: ""
  46. }
  47. initGame();
  48. } else {
  49. //结束游戏
  50. document.onkeydown = null; //删除绑定的事件
  51. clearInterval(timeStop); //蛇停止移动,否则结束游戏之后仍然一直撞墙,会持续游戏结束
  52. }
  53. return;
  54. }
  55. //将旧的head改为body
  56. oldHead.flag = 'body';
  57. oldHead.domContent.style.background = "#ADFF2F";
  58. oldHead.domContent.style.borderRadius = '50%';
  59. snake.snakePos.push(newHead);
  60. //判断是否吃到food
  61. if (collideCheckResult.isEat) {
  62. //进入此if,吃到food
  63. //生成新food
  64. drawFood();
  65. } else {
  66. //没吃到food,移除最后一个元素,保持原长度
  67. document.querySelector(".container").removeChild(snake.snakePos[0].domContent);
  68. snake.snakePos.shift(); //删除第一个元素,即最后一个body
  69. }
  70. //重新绘制snake
  71. drawSnake(snake);
  72. }
'
运行

如果控制蛇转向,蛇头的角度也需要转,index.js -> drawSnake()新增

  1. if (snake.snakePos[i].flag === 'head') {
  2. //当前是蛇头
  3. snake.snakePos[i].domContent.style.background = "linear-gradient(to right, #32cd32, #006400)";
  4. snake.snakePos[i].domContent.style.borderRadius = '50%';
  5. //根据方向进行旋转
  6. switch (snake.direction.flag) {
  7. case 'top': {
  8. snake.snakePos[i].domContent.style.transform = `
  9. rotate(-90deg)
  10. `; //倒着转90度
  11. }
  12. case 'bottom': {
  13. snake.snakePos[i].domContent.style.transform = `
  14. rotate(90deg)
  15. `; //转90度
  16. }
  17. case 'left': {
  18. snake.snakePos[i].domContent.style.transform = `
  19. rotate(180deg)
  20. `;
  21. }
  22. case 'right': {
  23. snake.snakePos[i].domContent.style.transform = `
  24. rotate(0deg)
  25. `;
  26. }
  27. break;
  28. }
  29. } else {
  30. //当前是蛇身
  31. snake.snakePos[i].domContent.style.background = "#ADFF2F";
  32. snake.snakePos[i].domContent.style.borderRadius = '50%';
  33. }

 2.2.2 碰撞检测

碰撞检测需要检查两个逻辑:

1.snake是否碰到了food(isEat)

2.snake是否碰到了墙壁或者自己的body(isCollide)

index.js

  1. /**
  2. * 碰撞检测
  3. * @param {*} newHead
  4. */
  5. function isCollide(newHead) {
  6. var collideCheckInfo = {
  7. isCollide: false, //是否碰撞墙壁、body
  8. isEat: false //是否吃到food
  9. }
  10. //1.检测是否碰到墙壁
  11. if (newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr) {
  12. collideCheckInfo.isCollide = true;
  13. return collideCheckInfo;
  14. }
  15. //2.检测是否碰到body
  16. for (let i = 0; i < snake.snakePos.length; i++) {
  17. if (newHead.x === snake.snakePos[i].x && newHead.y === snake.snakePos[i].y) {
  18. collideCheckInfo.isCollide = true;
  19. return collideCheckInfo;
  20. }
  21. }
  22. //3.检测是否吃到food
  23. if (newHead.x === food.x && newHead.y === food.y) {
  24. collideCheckInfo.isEat = true;
  25. score++; //分数自增
  26. }
  27. return collideCheckInfo;
  28. }
'
运行
2.3 开始游戏和继续游戏

用户点击container,游戏暂停(蛇移动停止),出现继续游戏(pauseBtn)按钮

点击游戏继续(pauseBtn),蛇继续移动

index.js -> bindEvent函数中新增

  1. //2.计数器自动调用蛇移动的方法
  2. startGame();
  3. document.querySelector('.container').onclick = function (e) {
  4. if (e.target.className === "container") {
  5. //3.点击整个容器container,暂停游戏
  6. //暂停操作
  7. document.querySelector('.pauseBtn').style.display = "block";
  8. //蛇运动暂停
  9. clearInterval(timeStop);
  10. } else {
  11. //4.点击'continue'按钮,游戏继续
  12. //游戏继续操作 className === "pauseBtn"
  13. document.querySelector('.pauseBtn').style.display = "none";
  14. //蛇运动继续
  15. startGame();
  16. }
  17. }

游戏开始时,需要点击开始游戏(startBtn),游戏才运行

修改main函数

  1. /**
  2. * 游戏的主方法
  3. */
  4. function main() {
  5. //用户点击开始游戏之后才开始后续的流程
  6. document.querySelector('.startBtn').onclick = function (e) {
  7. e.stopPropagation();
  8. document.querySelector('.startBtn').style.display = "none";
  9. //1.初始化游戏
  10. initGame();
  11. //2.绑定事件
  12. bindEvent();
  13. }
  14. }
  15. main();
2.3.1 游戏开始/继续 (startGame)

config.js

  1. //停止计时器
  2. var timeStop = null;
  3. var time = 500; //0.5s
'
运行

index.js

  1. /**
  2. * 计时器自动调用snakeMove()
  3. */
  4. function startGame() {
  5. timeStop = setInterval(function () {
  6. snakeMove();
  7. }, time)
  8. }
'
运行
2.4 计分

config.js

var score = 0;'
运行

每次吃到食物,score自增

当snake死亡(碰到墙壁或body)时,弹出对话框,显示分数${score}

此时可以选择重新开始游戏或取消

1. 如果选择重新开始游戏,重新增加button且都设置为'display: none',分数score、snake和food重置,再次调用initGame()初始化游戏

2. 如果选择取消重玩,删除绑定的事件(则按下取消后用户再按键盘或点击屏幕都没有反应),计时器停止计时(否则取消后snake仍然自动移动,会持续碰墙或body,持续发生游戏结束的弹窗)

实现:

snakeMove()中新增

  1. if (collideCheckResult.isCollide) {
  2. //进入此if,碰到墙或者body
  3. if (window.confirm(`
  4. 游戏结束,您当前的得分为${score}分,是否要重新开始游戏?
  5. `)) {
  6. //用户点确定,重新开始游戏
  7. //清空所有div
  8. document.querySelector('.container').innerHTML = `
  9. <!-- 开始按钮 -->
  10. <button class="startBtn" style="display: none"></button>
  11. <!-- 暂停按钮 -->
  12. <button class="pauseBtn" style="display: none"></button>
  13. `;
  14. score = 0;
  15. snake = {
  16. // 蛇一开始移动的方向 -> 右
  17. direction: directionNum.right,
  18. //蛇的初始位置
  19. snakePos: [
  20. //初始有三段身体,有一个头
  21. { x: 0, y: 0, domContent: "", flag: 'body' },
  22. { x: 1, y: 0, domContent: "", flag: 'body' },
  23. { x: 2, y: 0, domContent: "", flag: 'body' },
  24. { x: 3, y: 0, domContent: "", flag: 'head' },
  25. ]
  26. }
  27. food = {
  28. x: 0,
  29. y: 0,
  30. domContent: ""
  31. }
  32. initGame();
  33. } else {
  34. //结束游戏
  35. document.onkeydown = null; //删除绑定的事件
  36. clearInterval(timeStop); //蛇停止移动,否则结束游戏之后仍然一直撞墙,会持续游戏结束
  37. }
  38. return;
  39. }

3. 附录

start.jpg
start.jpg
play.jpg
play.jpg

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/1001456
推荐阅读
相关标签
  

闽ICP备14008679号