您现在的位置是:首页 >技术教程 >前端技术搭建贪吃蛇小游戏(内含源码)网站首页技术教程

前端技术搭建贪吃蛇小游戏(内含源码)

几何心凉 2024-07-01 12:01:01
简介前端技术搭建贪吃蛇小游戏(内含源码)


✨ 写在前面

上周我们实通过前端基础实现了井字游戏,今天还是继续按照我们原定的节奏来带领大家完成一个贪吃蛇游戏,功能也比较简单简单,也是想借助这样一个简单的功能,然后来帮助大家了解我们JavaScript在前端中的作用, 在前面的文章当中我们也提及到我们在本系列的专栏是循序渐进从简单到复杂的过程,后续会带领大家用前端实现翻卡片、扫雷等有趣的小游戏,纯前端语言实现,都会陆续带给大家。欢迎大家订阅我们这份前端小游戏的专栏。订阅链接:https://blog.csdn.net/jhxl_/category_12261013.html


✨ 功能介绍

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

在这里插入图片描述

游戏区域内会随机生成食物,玩家需要控制小蛇吃到食物来增加分数。小蛇的身体会随着吃到的食物变长,玩家需要注意控制小蛇的移动方向,避免撞到自己的身体。游戏难度会逐渐增加,小蛇的移动速度会变快,玩家需要更加灵活地控制小蛇的移动方向。玩家可以通过吃到特殊的食物来获得额外的分数或者特殊能力,例如加速或者减速小蛇的移动速度。游戏结束后,玩家可以查看自己的得分,并且可以选择重新开始游戏或者退出游戏。希望这些规则能够帮助您了解贪吃蛇小游戏的玩法和规则。如果您有任何其他问题,可以随时联系我。


✨ 页面搭建

创建文件

首先呢我们创建我们的HTML文件,这里我就直接命名为 贪吃蛇.html 了,大家可以随意命名, 文件创建生成后我们通过编辑器打开,这里我用的是VScode, 然后初始化我们的代码结构,那在这里告诉大家一个快捷键,就是我们敲上我们英文的一个 ! 我们敲击回车直接就会给我们生成基础版本的前端代码结构。

在这里插入图片描述

文档声明和编码设置: 在HTML文档的头部,使用<!DOCTYPE>声明HTML文档类型,确保浏览器以正确的方式渲染网页内容。同时,设置UTF-8编码,以确保浏览器能够正确地解析和显示中文字符。下面我就开始搭建我们的DOM结构了!

DOM结构搭建

这段代码是贪吃蛇小游戏的DOM结构代码,它包含了游戏的各个元素和界面布局。具体来说:

<p>贪吃蛇</p>:这是一个段落元素,用于显示游戏的标题。<div class="gamBox">:这是一个div元素,用于包裹整个游戏界面。<div class="screen">:这是一个div元素,用于显示游戏区域。<div class="snake">:这是一个div元素,用于显示贪吃蛇。<div class="snake-head">:这是一个div元素,用于显示贪吃蛇的头部。<span>?</span>:这是一个span元素,用于显示贪吃蛇头部的表情。<div class="snake-body">:这是一个div元素,用于显示贪吃蛇的身体。<div class="food">:这是一个div元素,用于显示食物。<span>?</span>:这是一个span元素,用于显示食物的图标。<div class="integral">:这是一个div元素,用于显示游戏的积分信息。<div>等级(grade)<span class="grade"></span></div>:这是一个div元素,用于显示游戏的等级信息。<div>分数(score)<span class="score"></span></div>:这是一个div元素,用于显示游戏的分数信息。

<body>

  <p>贪吃蛇</p>
  <div class="gamBox">

    <div class="screen">
      <div class="snake">
        <div class="snake-head">

          <span>?</span>

        </div>
        <div class="snake-body">
        </div>
      </div>

      <div class="food">
        <span>?</span>
      </div>
    </div>
    <div class="integral">
      <div>等级(grade)<span class="grade"></span></div>
      <div>分数(score)<span class="score"></span></div>
    </div>
  </div>

</body>

在这里插入图片描述


✨ 样式设置

我们看到了上面的的DOM已经搭建好了,但是页面什么都看不出来,下面我们简单的来配置一下样式吧,其实我们本专栏也是想带领大家掌握一些逻辑所以样式方面我们就一切从简;

*:设置所有元素的样式。margin: 0;和padding: 0;:将所有元素的外边距和内边距设置为0。box-sizing: border-box;:将元素的盒模型设置为border-box。p:设置段落元素的样式。text-align: center;:将段落元素的文本居中对齐。font-size: 23px;和font-weight: 600;:设置段落元素的字体大小和字体粗细。.gamBox:设置游戏界面的样式,包括宽度、高度、边框、背景颜色、圆角、布局等。.integral:设置游戏积分信息的样式,包括宽度、高度、布局、字体大小、字体粗细等。.screen:设置游戏区域的样式,包括宽度、高度、边框、位置等。.snake:设置贪吃蛇的样式,包括头部和身体的样式。.food:设置食物的样式,包括宽度、高度、字体大小、位置等。

<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>

在这里插入图片描述

✨ 逻辑部分

上面我们搭建了基本的样式,下面呢我们就通过js代码,实现我们游戏的功能吧,下面是对代码的简化解释:

  • foundationNumber:基础倍数,用于移动和食物随机位置计算。
  • maxGrade:最大等级。

以下是函数的作用:

  • randomNumber(min, max):生成指定范围内的随机整数。
  • changeFoodSeat():改变食物的位置。
  • changeScore():改变分数和等级。
  • handleWatchEnter(e):处理按键事件,控制蛇的移动和吃食物。
  • throttle(fn, wait):函数节流,用于控制蛇的移动速度。
  • init():初始化游戏,设置初始状态和界面。

以下是变量的作用:

  • direction:蛇的移动方向。
  • snakeLength:蛇身体的长度数组。
  • snakeSeat:蛇头的位置。
  • foodSeat:食物的位置。
  • register:存储分数和等级的对象。

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

<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>

完整代码


<!-- 

  Author profile:

  欢迎您朋友,感谢你的认可,我是几何心凉
  CSDN博客专家、内容合伙人、新星计划导师,
  Vue技能树构建者、阿里云社区专家博主,
  前端领域优质创作者致力于新技术的推广与优秀技术的普及。

  可提供简历、就业指导服务

  CSDN博客:https://blog.csdn.net/JHXL_
  公众号:#几何心凉的核心圈
  
 -->

<!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>凉哥的简易版贪吃蛇</title>
  <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>
</head>

<body>

  <p>贪吃蛇</p>
  <div class="gamBox">

    <div class="screen">
      <div class="snake">
        <div class="snake-head">

          <span>?</span>

        </div>
        <div class="snake-body">
        </div>
      </div>

      <div class="food">
        <span>?</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>

本期推荐 点击查看详情

在这里插入图片描述

原创不易,还希望各位大佬支持一下 extcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下

? 点赞,你的认可是我创作的动力! extcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! extcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! extcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。