美文网首页
原生js开发【贪吃蛇】小游戏

原生js开发【贪吃蛇】小游戏

作者: 风中凌乱的男子 | 来源:发表于2022-12-10 18:00 被阅读0次
  • 万里归一,情归深处,万变不离其宗,兜兜转转我们回到原生js开发中来,下面我们来使用原生js来开发一个小游戏==>【贪吃蛇】
  • 下面是游戏截图
image.png
    1. 首先先来创建项目,创建三个目录,js、css、image,和一个index.html
image.png
    1. css目录内新建index.css
    1. js目录内新建index.js和config.js
    1. index.html内先引入必要的文件、创建必要的dom
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>贪吃🐍</title>
        <link rel="stylesheet" href="./css/index.css">
    </head>
    <body>
        <div class="container"></div>
        <script src="./js/config.js"></script>
        <script src="./js/index.js"></script>
    </body>
</html>
  • index.css
.container{
    width: 600px;
    height: 600px;
    background: #225675;
    border: 20px solid #7dd9ff;
    margin: 0 auto;
    position: relative;
}
    1. 首先来创建一个游戏的主方法,并且调用
  • index.js
/**
 * 游戏的主方法
 */
function main() {
  
}
main();
    1. 在主方法内,调用一个初始化游戏的方法
/**
 * 游戏的主方法
 */
function main() {
  initGame();
}
main();
    1. 下面创建这个初始化的游戏方法
function initGame() {
  
}
    1. 初始化的游戏方法大概分三步
  • 8.1 第一步:绘制地图
  • 8.2 第二步:绘制蛇
  • 8.3 第三步:绘制食物
  • 绘制地图前,要先在config.js内写一些游戏的相关配置
    1. config.js部分相关配置
// 游戏相关的配置
// 存储地图对象
var gridData = [];
// 整个地图的行与列
var tr = 30;
var td = 30;
// 蛇的身体大小
var snakeBody = 20;
// 蛇相关的配置信息
var snake = {
  snakePos: [
    { x: 0, y: 0, domContent: "", flag: "body" },
    { x: 1, y: 0, domContent: "", flag: "body" },
    { x: 2, y: 0, domContent: "", flag: "body" },
    { x: 3, y: 0, domContent: "", flag: "head" },
  ]
}
  • 8.1 绘制地图
function initGame() {
  // 1.绘制地图
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
}
  • 8.2 绘制蛇
function initGame() {
  // 1.绘制地图
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
  // 2.绘制蛇,写一个公共方法
  drawSnake(snake);
}
  • 下面是绘制蛇的公共方法 drawSnake
/**
 * 绘制蛇的方法
 */
function drawSnake(snake) {
  for (let i = 0; i < snake.snakePos.length; i++) {
    if (!snake.snakePos[i].domContent) {
      // 如果进入进入此if,说明是第一次创建蛇
      // 先创建一个div
      snake.snakePos[i].domContent = document.createElement('div')
      // 设置下绝对定位
      snake.snakePos[i].domContent.style.position = 'absolute'
      // 设置下蛇身体的宽高
      snake.snakePos[i].domContent.style.width = snakeBody + 'px'
      snake.snakePos[i].domContent.style.height = snakeBody + 'px'
      // 设置下每节蛇身体的位置
      snake.snakePos[i].domContent.style.left = snake.snakePos[i].x * snakeBody + 'px'
      snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody + 'px'
      if (snake.snakePos[i].flag === 'head') {
        // 说明是蛇头
        snake.snakePos[i].domContent.style.background = `
        url("./image/head.png") center/contain no-repeat
        `
      } else {
        // 说明是蛇身
        snake.snakePos[i].domContent.style.background = '#9ddbb1'
        snake.snakePos[i].domContent.style.borderRadius = '50%'
      }
    }
    // 需要讲创建的DOM元素添加到container容器上面
    document.querySelector('.container').append(snake.snakePos[i].domContent)

  }
}
  • 保存看一下效果
image.png
  • 8.3 绘制食物
function initGame() {
  // 1.绘制地图
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
  // 2.绘制蛇,写一个公共方法
  drawSnake(snake);
  // 绘制食物,写一个公共方法
  drawFood();
}
  • drawFood 方法
/** 
 * 绘制食物的方法
 */
function drawFood() {
  // 1. 食物的坐标是随机的
  // 2. 食物不能生成在蛇头或蛇身上面
  while (true) {
    // 构成一个死循环,直到生成符合要求的食物坐标才能退出该循环
    var isRepeat = false; //默认生成的坐标是符合要求的
    // 随机生成一个坐标
    food.x = Math.floor(Math.random() * tr)
    food.y = Math.floor(Math.random() * tr)
    // console.log(food);
    // 查看坐标是否符合要求(遍历蛇)
    for (let i = 0; i < snake.snakePos.length; i++) {
      if (snake.snakePos[i].x === food.x && snake.snakePos[i].y === food.y) {
        // 进入此if,说明当前生成的食物坐标和蛇的身体或者头的坐标冲突了
        isRepeat = true
        break;
      }
    }
    if (!isRepeat) {
      // 跳出while循环
      break;
    }
  }
  // 整个while循环跳出来之后,食物的坐标一定是ok的
  // console.log(food);
  // console.log(snake.snakePos);
  if (!food.domContent) {
    food.domContent = document.createElement('div')
    food.domContent.style.width = snakeBody + 'px'
    food.domContent.style.height = snakeBody + 'px'
    food.domContent.style.position = 'absolute'
    food.domContent.style.background = `
    url("./image/apple.png") center/contain no-repeat
    `
    document.querySelector('.container').append(food.domContent)
  }
  food.domContent.style.left = food.x * snakeBody + 'px'
  food.domContent.style.top = food.y * snakeBody + 'px'
}
  • 下面就是效果,生产的食物的坐标都是随机的
image.png
image.png

  • 下面就是要绑定事件
function main() {
  // 一 初始化游戏
  initGame()
  // 二 绑定事件
  bindEvent()
}
  • bindEvent
/**
 * 绑定事件的方法
 */
function bindEvent() {
  // 1. 首先是键盘事件 用户按下上下左右,蛇能够移动
  document.onkeydown = (e) => {
    // ArrowUp     上
    // ArrowRight  右
    // ArrowDown   下
    // ArrowLeft   左
    // console.log(e.key);
    // 先来明确下,新的蛇头坐标和旧的蛇头坐标的关系,先在config.js内配置下位置关系
    if (e.key == 'ArrowUp' || e.key == 'w') {
      // 用户按的是上
      snake.direction = directionNum.top
    }
    if (e.key == 'ArrowRight' || e.key == 'd') {
      // 用户按的是右
      snake.direction = directionNum.right
    }
    if (e.key == 'ArrowDown' || e.key == 's') {
      // 用户按的是下
      snake.direction = directionNum.bottom
    }
    if (e.key == 'ArrowLeft' || e.key == 'a') {
      // 用户按的是左
      snake.direction = directionNum.left
    }
    snakeMove()
  }
}
/**
 * 蛇移动的方法
 */
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根据方向计算出新的蛇头的坐标
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }
  //把旧蛇头改成身体
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  //把新蛇头push到数组内去
  snake.snakePos.push(newHead)
  // 重新绘制蛇
  drawSnake(snake)
}
  • 这样就实现了蛇的上下左右移动
image.png
  • 但是还存在几个问题
  • 第一,就是蛇头的方向不对,要根据方向旋转下蛇头,在绘制蛇的方法drawSnake里完善,代码如下
// 设置下蛇身和蛇头
      if (snake.snakePos[i].flag === 'head') {
        // 说明是蛇头
        snake.snakePos[i].domContent.style.background = `
          url("./image/head.png")   center no-repeat
        `
        snake.snakePos[i].domContent.style.backgroundSize = `100%`
       switch (snake.direction.flag) {
          case "left":
            snake.snakePos[i].domContent.style.transform = "rotate(180deg)"
            break;
          case "top":
            snake.snakePos[i].domContent.style.transform = "rotate(-90deg)"
            break;
          case "right":
            snake.snakePos[i].domContent.style.transform = "rotate(0deg)"
            break;
          case "bottom":
            snake.snakePos[i].domContent.style.transform = "rotate(90deg)"
            break;

          default:
            break;
        }
      }
  • 这样蛇头就正常了
image.png
  • 第二,就是要做碰撞检测,在生成新的蛇头后,要检测蛇头有没有碰到食物、或者蛇身体、或者墙壁
  • 要在哪做这个碰撞检测呢?很明显要在snakeMove方法里,生成新蛇头后面,就要做碰撞检测,在snakeMove方法里也封装一个碰撞检测方法isCollide
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根据方向计算出新的蛇头的坐标
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }

  // 接下来就是要做碰撞检测
  // 检测新蛇头有没有碰到食物、墙壁或者蛇身体
  // 也要封装一个碰撞检测方法 isCollide
  isCollide(newHead)
  // 将旧的蛇头变成身体
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  snake.snakePos.push(newHead)
  // 重新绘制蛇
  drawSnake(snake)
}
  • isCollide碰撞检测方法
/**
 * 碰撞检测
 * @param {新计算出来的蛇头坐标} newHead 
 */
function isCollide(newHead) {
  // 先定义一个对象,默认是否碰撞了
  var collideCheckInfo = {
    isCollide: false,//默认没有碰撞到墙壁、蛇身
    isEat: false //默认没有吃到食物
  }
  // 1. 检测是否碰到墙壁 新蛇头的x坐标小于0或者大于等于td了,或者新蛇头的y坐标小于0或者大于等于tr了,就是越界了
  if (newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr) {
    collideCheckInfo.isCollide = true
    return collideCheckInfo
  }
  // 2. 检测是否碰到自己了 遍历蛇 新蛇头的x坐标==蛇的每个x坐标并且新蛇头的y坐标==每个y坐标 就是碰到自己了
  for (let i = 0; i < snake.snakePos.length; i++) {
    const element = snake.snakePos[i];
    if (element.x === newHead.x && element.y === newHead.y) {
      collideCheckInfo.isCollide = true
      return collideCheckInfo
    }
  }
  // 3. 检测是否吃到食物了
  if(newHead.x===food.x&&newHead.y===food.y){
    collideCheckInfo.isEat = true
  }
  return collideCheckInfo
}
  • 然后在完善下snakeMove方法,接收碰撞检测方法的返回值
/**
 * 蛇移动的方法
 */
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根据方向计算出新的蛇头的坐标
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }

  // 接下来就是要做碰撞检测
  // 检测新蛇头有没有碰到食物、墙壁或者蛇身体
  // 也要封装一个碰撞检测方法 isCollide
  let collideCheckResult = isCollide(newHead)
  if (collideCheckResult.isCollide) {
    // 进入此if,说明越界了或者碰到自己了,游戏结束
    alert("游戏结束")
  }
  // 将旧的蛇头变成身体
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  snake.snakePos.push(newHead)
  // 判断是否吃到食物
  if (collideCheckResult.isEat) {
    // 吃到了,就重新生成新的食物
    drawFood()
  } else {
    // 没吃到 就移除最后一个游戏
    document.querySelector('.container').removeChild(snake.snakePos[0].domContent)
    snake.snakePos.shift()
  }
  // 重新绘制蛇
  drawSnake(snake)
}
  • 下面在做一个分数自增,计算成绩
  • 在config.js新建一个变量score
var score = 0
  • 在吃到食物后,将score++
// 判断是否吃到食物
  if (collideCheckResult.isEat) {
    // 吃到了,就重新生成新的食物
    score++
    drawFood()
  }
  • 游戏结束后,刷新页面,重新再来,直接刷新浏览器,简单粗暴
if (collideCheckResult.isCollide) {
    // 进入此if,说明越界了或者碰到自己了,游戏结束
    alert("游戏结束")
    location.reload()
  }
  • 下面再来解决下 向左(上)走的时候,按右(下)键导致游戏结束的问题
if ((e.key == 'ArrowUp' || e.key == 'w')&&snake.direction.flag!='bottom') {
      // 用户按的是上
      snake.direction = directionNum.top
    }
    if ((e.key == 'ArrowRight' || e.key == 'd')&&snake.direction.flag!='left') {
      // 用户按的是右
      snake.direction = directionNum.right
    }
    if ((e.key == 'ArrowDown' || e.key == 's')&&snake.direction.flag!='top') {
      // 用户按的是下
      snake.direction = directionNum.bottom
    }
    if ((e.key == 'ArrowLeft' || e.key == 'a')&&snake.direction.flag!='right') {
      // 用户按的是左
      snake.direction = directionNum.left
    }
  • 现在已经可以玩了,接下来就是让他自己动起来
  • 在bindEvent方法里
 if ((e.key == 'ArrowLeft' || e.key == 'a') && snake.direction.flag != 'right') {
      // 用户按的是左
      snake.direction = directionNum.left
    }
    clearInterval(timer)
    startGame()
  • startGame
/**
 * 蛇自己动
 */
function startGame() {
  timer = setInterval(() => {
    snakeMove()
  }, time);
}
  • timer 和time 在config.js里要配置下
var timer = null
var time = 200
  • 碰墙后,一直弹alert,清除下定时器 就好了
if (collideCheckResult.isCollide) {
    // 进入此if,说明越界了或者碰到自己了,游戏结束
    clearInterval(timer)
    alert("游戏结束")
    location.reload()
  }
  • 还有很多可以优化和功能拓展,感兴趣的同学可以试试~
  • 比如吃到苹果加音效、添加开始、暂停功能等等
  • over

相关文章

网友评论

      本文标题:原生js开发【贪吃蛇】小游戏

      本文链接:https://www.haomeiwen.com/subject/tszzfdtx.html