搭建贪吃蛇游戏详解

一、需要实现的功能

实现一个贪吃蛇游戏,也是想借助这样一个简单的功能,然后来帮助大家了解我们JavaScript在前端中的作用。

二、需求功能细化

贪吃蛇小游戏的玩法和规则:

游戏开始时,玩家控制一条小蛇在游戏区域内移动,通过吃食物来增加分数。小蛇的移动方向由玩家控制,可以使用键盘上的方向键来控制小蛇的移动方向。当小蛇吃到食物时,它会变长,并且玩家的分数会增加。如果小蛇撞到了游戏区域的边界或者自己的身体,游戏结束。

游戏区域内会随机生成食物,玩家需要控制小蛇吃到食物来增加分数。小蛇的身体会随着吃到的食物变长,玩家需要注意控制小蛇的移动方向,避免撞到自己的身体。游戏难度会逐渐增加,小蛇的移动速度会变快,玩家需要更加灵活地控制小蛇的移动方向。玩家可以通过吃到特殊的食物来获得额外的分数或者特殊能力,例如加速或者减速小蛇的移动速度。游戏结束后,玩家可以查看自己的得分,并且可以选择重新开始游戏或者退出游戏。

三、H5页面设计

我们创建一个命名为 snake.html 的文件通过编辑器(我用的EditPlus)打开。使用<!DOCTYPE>声明HTML文档类型,确保浏览器以正确的方式渲染网页内容。同时,设置UTF-8编码,以确保浏览器能够正确地解析和显示中文字符。

以下是计算器的DOM结构了。

<body>
<h1>贪吃蛇游戏</h1>
<div class="gamBox">
  <div class="screen">
    <div class="snake">
      <div class="snake-head">
        <span>[snake-head]</span>
      </div>
      <div class="snake-body">
      </div>
    </div>
    <div class="food">
      <span>[food]</span>
    </div>
  </div>
  <div class="integral">
    <div>等级(grade)<span class="grade"></span></div>
    <div>分数(score)<span class="score"></span></div>
  </div>
</div>
</body>

(1)<div class="gamBox">:这是一个div元素,用于包裹整个游戏界面。

(2)<div class="screen">:这是一个div元素,用于显示游戏区域。

(3)<div class="snake">:这是一个div元素,用于显示贪吃蛇。

(4)<div class="snake-head">:这是一个div元素,用于显示贪吃蛇的头部。

(5)<span>�[snake-head]</span>:这是一个span元素,用于显示贪吃蛇头部的表情。

(6)<div class="snake-body">:这是一个div元素,用于显示贪吃蛇的身体。

(7)<div class="food">:这是一个div元素,用于显示食物。

(8)<span>�[food]</span>:这是一个span元素,用于显示食物的图标。

(9)<div class="integral">:这是一个div元素,用于显示游戏的积分信息。

(10)<div>等级(grade)<span class="grade"></span></div>:这是一个div元素,用于显示游戏的等级信息。

(11)<div>分数(score)<span class="score"></span></div>:这是一个div元素,用于显示游戏的分数信息。

四、CSS样式设置

上面html已经准备好了,下面我们简单的来配置一下样式吧。

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
p {
  text-align: center;
  font-size: 23px;
  font-weight: 600;
}
.gamBox {
  width: 500px;
  height: 500px;
  border: 10px solid #393c1b;
  margin: 20px auto;
  background-color: #b6b327;
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
}
.integral {
  width: 398px;
  height: 25px;
  display: flex;
  justify-content: space-between;
  font-size: 16px;
  font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
  "Lucida Sans", Arial, sans-serif;
  font-weight: 700;
}
.screen {
  width: 400px;
  height: 400px;
  border: 1px solid #000;
  position: relative;
}
.snake .snake-head {
  width: 20px;
  height: 20px;
  border: 1px solid #4d7d2b;
  background-color: #000;
  position: absolute;
  top: 0;
  left: 0;
}
.snake span {
  font-size: 17px;
  position: absolute;
  left: -2.7px;
  top: -3px;
}
.snake-body>div {
  position: absolute;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  border: 1px solid #4d7d2b;
  background-color: #000;
}
.food {
  width: 20px;
  height: 20px;
  font-size: 8px;
  text-align: left;
  position: absolute;
  top: 10px;
  left: 0;
}
.food span {
  font-size: 17px;
  position: absolute;
  left: -1.7px;
  top: -2px;
}
</style>

(1)*:设置所有元素的样式。

(2)margin: 0;和padding: 0;:将所有元素的外边距和内边距设置为0。

(3)box-sizing: border-box;:将元素的盒模型设置为border-box。

(4)p:设置段落元素的样式。

(5)text-align: center;:将段落元素的文本居中对齐。

(6)font-size: 23px;和font-weight: 600;:设置段落元素的字体大小和字体粗细。

(7).gamBox:设置游戏界面的样式,包括宽度、高度、边框、背景颜色、圆角、布局等。

(8).integral:设置游戏积分信息的样式,包括宽度、高度、布局、字体大小、字体粗细等。

(9).screen:设置游戏区域的样式,包括宽度、高度、边框、位置等。

(10).snake:设置贪吃蛇的样式,包括头部和身体的样式。

(11).food:设置食物的样式,包括宽度、高度、字体大小、位置等。

五、代码逻辑,游戏实现

<script>
  /***
   * 公用变量
   * @foundationNumber 基础倍数:移动、食物随机位置量的倍数
   * @maxGrade 最大的等级
   */
  const foundationNumber = 20
  const maxGrade = 10
  /***
   * 食物相关
   */
  const randomNumber = (min, max) => {
    return Math.floor(Math.random() * (max - min)) + min
  }
  let foodSeat = {
    top: 20,
    left: 20
  }
  const changeFoodSeat = () => {
    foodSeat.top = randomNumber(0, 20) * foundationNumber
    foodSeat.left = randomNumber(0, 20) * foundationNumber
    document.querySelector('.food').style.cssText = 'left:' + foodSeat.left + 'px;top:' + foodSeat.top + 'px'
  }
  /***
   * 等级、分数相关
   */
  let register = {
    score: 0,
    grade: 1
  }
  const changeScore = () => {
    register.score++
    if (register.grade < 10) {
      register.grade = Math.ceil(register.score / 10)
    }
    document.querySelector('.grade').innerText = register.grade
    document.querySelector('.score').innerText = register.score
  }
  /***
   * 蛇相关
   */
  let direction = ''//移动方向
  let snakeLength = []
  let snakeSeat = {
    top: 0,
    left: 0
  }
  const handleWatchEnter = e => {
    let previousTop = snakeSeat.top
    let previousLeft = snakeSeat.left
    //通过便利每个身体部分来进行移动
    snakeLength.forEach((ele, index) => {
      let temporaryTop = ele.top
      let temporaryLeft = ele.left
      ele.top = previousTop
      ele.left = previousLeft
      previousTop = temporaryTop
      previousLeft = temporaryLeft
      document.querySelectorAll('.snake-body>div')[index].style.cssText = 'left:' + ele.left + 'px;top:' + ele.top + 'px'
    });
    switch (direction) {
      case 'ArrowUp':
        snakeSeat.top -= 20
        break;
      case 'ArrowLeft':
        snakeSeat.left -= 20
        break;
      case 'ArrowRight':
        snakeSeat.left += 20
        break;
      case 'ArrowDown':
        snakeSeat.top += 20
        break;
    }
    if (snakeSeat.top == foodSeat.top && snakeSeat.left == foodSeat.left) {
      changeScore()
      changeFoodSeat()
      snakeLength.push({
        top: previousTop,
        left: previousLeft
      })
      var div = document.createElement('div');
      div.style.left = previousLeft + 'px';
      div.style.top = previousTop + 'px';
      div.class = 'bodyItem'
      document.querySelector('.snake-body').appendChild(div)
    }
    if (snakeSeat.top < 0 || snakeSeat.left < 0 || snakeSeat.left > 380 || snakeSeat.top > 380) {
      alert('撞墙身亡')
      snakeSeat.top = 0
      snakeSeat.left = 0
      direction = ''
      snakeLength = []
      init()
      return
    }
    let bodySeats = snakeLength.map(item => JSON.stringify(item))
    if ((bodySeats.indexOf(JSON.stringify({ top: snakeSeat.top, left: snakeSeat.left })) != -1)) {
      alert('把自己撞死了')
      snakeSeat.top = 0
      snakeSeat.left = 0
      snakeLength = []
      direction = ''
      init()
      return
    }
    document.querySelector('.snake-head').style.cssText = 'left:' + snakeSeat.left + 'px;top:' + snakeSeat.top + 'px'
    setTimeout(() => {
      handleWatchEnter()
    }, 400 - (register.grade - 1) * 15);
  }
  //函数节流
  const throttle = (fn, wait) => {
    var timer = null;
    return function () {
      var _this = this;
      var args = arguments;
      if (!timer) {
        timer = setTimeout(function () {
          fn.apply(_this, args);
          timer = null;
        }, wait);
      }
    }
  }
  const init = () => {
    document.querySelector('.snake-head').style.cssText = 'left:' + 0 + 'px;top:' + 0 + 'px'
    document.querySelector('.snake-body').innerHTML = ""
    document.querySelector('.grade').innerText = register.grade
    document.querySelector('.score').innerText = register.score
    changeFoodSeat()
    handleWatchEnter()
  }
  document.addEventListener('keydown', (e) => {
    if ((e.code == 'ArrowUp' && direction != 'ArrowDown') || (e.code == 'ArrowLeft' && direction != 'ArrowRight') || (e.code == 'ArrowRight' && direction != 'ArrowLeft') || (e.code == 'ArrowDown' && direction != 'ArrowUp')) {
      direction = e.code
    }
  });
  init()
</script>

现在解释一下关键部分的代码。

(1)基本设置

foundationNumber:基础倍数,用于移动和食物随机位置计算。

maxGrade:最大等级。

(2)函数的作用

randomNumber(min, max):生成指定范围内的随机整数。

changeFoodSeat():改变食物的位置。

changeScore():改变分数和等级。

handleWatchEnter(e):处理按键事件,控制蛇的移动和吃食物。

throttle(fn, wait):函数节流,用于控制蛇的移动速度。

init():初始化游戏,设置初始状态和界面。

(3)变量的作用

direction:蛇的移动方向。

snakeLength:蛇身体的长度数组。

snakeSeat:蛇头的位置。

foodSeat:食物的位置。

register:存储分数和等级的对象。

在页面加载完成后,监听键盘按下事件,根据按键改变移动方向。然后调用 init 函数初始化游戏。在 init 函数中,设置蛇头位置,清空蛇身体,显示等级和分数,改变食物位置,并调用 handleWatchEnter 函数开始处理蛇的移动。在 handleWatchEnter 函数中,根据移动方向更新蛇身体的位置,判断是否吃到食物并处理相关逻辑,检测是否碰到墙壁或自身,更新蛇头位置,最后通过递归调用自身实现连续移动。整体代码实现了贪吃蛇游戏的逻辑,包括蛇的移动、食物的生成和吃食物的判定,以及游戏结束的条件判断。

六、完整源代码

<!DOCTYPE html>
<html lang="zh-CN">
<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>贪吃蛇游戏</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    h1 {
      font-size: 19px;
      text-align: center;
    }
    p {
      text-align: center;
      font-size: 23px;
      font-weight: 600;
    }
    .gamBox {
      width: 500px;
      height: 500px;
      border: 10px solid #393c1b;
      margin: 20px auto;
      background-color: #b6b327;
      border-radius: 20px;
      display: flex;
      flex-direction: column;
      justify-content: space-around;
      align-items: center;
    }
    .integral {
      width: 398px;
      height: 25px;
      display: flex;
      justify-content: space-between;
      font-size: 16px;
      font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
      "Lucida Sans", Arial, sans-serif;
      font-weight: 700;
    }
    .screen {
      width: 400px;
      height: 400px;
      border: 1px solid #000;
      position: relative;
    }
    .snake .snake-head {
      width: 20px;
      height: 20px;
      border: 1px solid #4d7d2b;
      background-color: #000;
      position: absolute;
      top: 0;
      left: 0;
    }
    .snake span {
      font-size: 17px;
      position: absolute;
      left: -2.7px;
      top: -3px;
    }
    .snake-body>div {
      position: absolute;
      top: 0;
      left: 0;
      width: 20px;
      height: 20px;
      border: 1px solid #4d7d2b;
      background-color: #000;
    }
    .food {
      width: 20px;
      height: 20px;
      font-size: 8px;
      text-align: left;
      position: absolute;
      top: 10px;
      left: 0;
    }
    .food span {
      font-size: 17px;
      position: absolute;
      left: -1.7px;
      top: -2px;
    }
  </style>
</head>
<body>
<h1>贪吃蛇游戏</h1>
<div class="gamBox">
  <div class="screen">
    <div class="snake">
      <div class="snake-head">
        <span>[snake-head]</span>
      </div>
      <div class="snake-body">
      </div>
    </div>
    <div class="food">
      <span>[food]</span>
    </div>
  </div>
  <div class="integral">
    <div>等级(grade)<span class="grade"></span></div>
    <div>分数(score)<span class="score"></span></div>
  </div>
</div>
</body>
<script>
  /***
   * 公用变量
   * @foundationNumber 基础倍数:移动、食物随机位置量的倍数
   * @maxGrade 最大的等级
   */
  const foundationNumber = 20
  const maxGrade = 10
  /***
   * 食物相关
   */
  const randomNumber = (min, max) => {
    return Math.floor(Math.random() * (max - min)) + min
  }
  let foodSeat = {
    top: 20,
    left: 20
  }
  const changeFoodSeat = () => {
    foodSeat.top = randomNumber(0, 20) * foundationNumber
    foodSeat.left = randomNumber(0, 20) * foundationNumber
    document.querySelector('.food').style.cssText = 'left:' + foodSeat.left + 'px;top:' + foodSeat.top + 'px'
  }
  /***
   * 等级、分数相关
   */
  let register = {
    score: 0,
    grade: 1
  }
  const changeScore = () => {
    register.score++
    if (register.grade < 10) {
      register.grade = Math.ceil(register.score / 10)
    }
    document.querySelector('.grade').innerText = register.grade
    document.querySelector('.score').innerText = register.score
  }
  /***
   * 蛇相关
   */
  let direction = ''//移动方向
  let snakeLength = []
  let snakeSeat = {
    top: 0,
    left: 0
  }
  const handleWatchEnter = e => {
    let previousTop = snakeSeat.top
    let previousLeft = snakeSeat.left
    //通过便利每个身体部分来进行移动
    snakeLength.forEach((ele, index) => {
      let temporaryTop = ele.top
      let temporaryLeft = ele.left
      ele.top = previousTop
      ele.left = previousLeft
      previousTop = temporaryTop
      previousLeft = temporaryLeft
      document.querySelectorAll('.snake-body>div')[index].style.cssText = 'left:' + ele.left + 'px;top:' + ele.top + 'px'
    });
    switch (direction) {
      case 'ArrowUp':
        snakeSeat.top -= 20
        break;
      case 'ArrowLeft':
        snakeSeat.left -= 20
        break;
      case 'ArrowRight':
        snakeSeat.left += 20
        break;
      case 'ArrowDown':
        snakeSeat.top += 20
        break;
    }
    if (snakeSeat.top == foodSeat.top && snakeSeat.left == foodSeat.left) {
      changeScore()
      changeFoodSeat()
      snakeLength.push({
        top: previousTop,
        left: previousLeft
      })
      var div = document.createElement('div');
      div.style.left = previousLeft + 'px';
      div.style.top = previousTop + 'px';
      div.class = 'bodyItem'
      document.querySelector('.snake-body').appendChild(div)
    }
    if (snakeSeat.top < 0 || snakeSeat.left < 0 || snakeSeat.left > 380 || snakeSeat.top > 380) {
      alert('撞墙身亡')
      snakeSeat.top = 0
      snakeSeat.left = 0
      direction = ''
      snakeLength = []
      init()
      return
    }
    let bodySeats = snakeLength.map(item => JSON.stringify(item))
    if ((bodySeats.indexOf(JSON.stringify({ top: snakeSeat.top, left: snakeSeat.left })) != -1)) {
      alert('把自己撞死了')
      snakeSeat.top = 0
      snakeSeat.left = 0
      snakeLength = []
      direction = ''
      init()
      return
    }
    document.querySelector('.snake-head').style.cssText = 'left:' + snakeSeat.left + 'px;top:' + snakeSeat.top + 'px'
    setTimeout(() => {
      handleWatchEnter()
    }, 400 - (register.grade - 1) * 15);
  }
  //函数节流
  const throttle = (fn, wait) => {
    var timer = null;
    return function () {
      var _this = this;
      var args = arguments;
      if (!timer) {
        timer = setTimeout(function () {
          fn.apply(_this, args);
          timer = null;
        }, wait);
      }
    }
  }
  const init = () => {
    document.querySelector('.snake-head').style.cssText = 'left:' + 0 + 'px;top:' + 0 + 'px'
    document.querySelector('.snake-body').innerHTML = ""
    document.querySelector('.grade').innerText = register.grade
    document.querySelector('.score').innerText = register.score
    changeFoodSeat()
    handleWatchEnter()
  }
  document.addEventListener('keydown', (e) => {
    if ((e.code == 'ArrowUp' && direction != 'ArrowDown') || (e.code == 'ArrowLeft' && direction != 'ArrowRight') || (e.code == 'ArrowRight' && direction != 'ArrowLeft') || (e.code == 'ArrowDown' && direction != 'ArrowUp')) {
      direction = e.code
    }
  });
  init()
</script>
</html>

以上是贪吃蛇游戏的完整代码,可以直接复制过去,或者点击贪吃蛇游戏demo链接访问。

版权声明:本文为老张的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://www.webppp.com/view/game_snake.html

推荐文章