【前端|JS实战第1篇】使用JS来实现属于自己的贪吃蛇游戏!

简介: 【前端|JS实战第1篇】使用JS来实现属于自己的贪吃蛇游戏!

前言

贪吃蛇游戏是经典的小游戏,也是学习前端JS的一个很好的练习项目。在本教程中,我们将使用 JavaScript 来逐步构建一个贪吃蛇游戏。我们会从创建游戏区域开始,逐步添加蛇的移动、食物的生成以及游戏逻辑等功能。


🚀 作者简介:程序员小豪,全栈工程师,热爱编程,曾就职于蔚来,现就职于某互联网大厂,技术栈:Vue、React、Python、Java

🎈 本文收录于小豪的前端系列专栏,后续还会更新前端入门以及前端面试的一些相关文章,手把手带你从零学习前端到面试找工作,并如果有想进入前端领域工作的同学,这个前端专栏会对你有所帮助,欢迎关注起来呀

🌼 本人也会持续的去关注AIGC以及人工智能领域的一些动向并总结到博客中,大家感兴趣的可以关注一下我的人工智能专栏

🌊 云原生的入门学习系列,大家有兴趣的可以看一看

步骤1:创建文件


我们需要创建三个文件index.htmlstyles.cssscript.js,index.html用于放置我们界面dom,styles.css用于开发样式,script.js用于放置实现贪吃蛇的逻辑。

步骤2:创建游戏区域


首先,我们需要一个游戏区域来展示游戏。在 HTML 文件中创建一个

元素,用于表示游戏区域。然后,使用 CSS 来设置该元素的样式,使其成为一个矩形的游戏画布。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>贪吃蛇游戏</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="game-area"></div>
  <script src="script.js"></script>
</body>
</html>
/* styles.css */
.game-area {
  width: 400px;
  height: 400px;
  border: 2px solid #000;
}

可以看到,我们在styles.css设置了这个游戏区域的宽度和高度为400px,还设置了一个2px的黑色边框

步骤3:初始化蛇的位置


在 script.js 文件中,我们将初始化蛇的初始位置。定义一个表示蛇身的数组,每个元素代表蛇的一个部分,然后设置蛇的初始位置。

// script.js
const gameArea = document.querySelector('.game-area');
const snake = [{ x: 2, y: 2 }]; // 初始位置
// 初始化蛇的位置
function initializeSnake() {
  snake.forEach(segment => {
    const snakeSegment = document.createElement('div');
    snakeSegment.classList.add('snake');
    snakeSegment.style.left = segment.x * 20 + 'px';
    snakeSegment.style.top = segment.y * 20 + 'px';
    gameArea.appendChild(snakeSegment);
  });
}
initializeSnake();
/* styles.css */
.snake {
  width: 20px;
  height: 20px;
  background-color: green;
  position: absolute;

知识点:

  • document.querySelector('.game-area')用于获取游戏区域的整个dom
  • 蛇的身体我们用一个数组来实现,每一截区域都有一个坐标
  • 我们根据snake这个数组用document.createElement('div')给蛇的每一截身体渲染一个div元素
  • 在styles.css里使用background-color: green;将蛇的身体设置为绿色
  • 在styles.css里使用position: absolute;设置蛇的每一截取的身体为绝对定位。然后通过js设置每一截身体的dom的lefttop,从而设置其位置

步骤4:移动蛇


接下来,我们将实现蛇的移动功能。我们会添加一个监听键盘事件,当玩家按下方向键时,蛇将根据所选择的方向移动。

// script.js
let direction = "right"; // 初始方向
document.addEventListener("keydown", (event) => {
  if (event.key === "ArrowUp" && direction !== "down") {
    direction = "up";
  } else if (event.key === "ArrowDown" && direction !== "up") {
    direction = "down";
  } else if (event.key === "ArrowLeft" && direction !== "right") {
    direction = "left";
  } else if (event.key === "ArrowRight" && direction !== "left") {
    direction = "right";
  }
});
function moveSnake() {
  const head = { x: snake[0].x, y: snake[0].y };
  console.log(direction);
  // 根据方向更新蛇头位置
  if (direction === "up") {
    head.y--;
  } else if (direction === "down") {
    head.y++;
  } else if (direction === "left") {
    head.x--;
  } else if (direction === "right") {
    head.x++;
  }
  console.log(head);
  // 更新蛇的位置
  snake.unshift(head);
  // 移除蛇尾
  const removedSegment = snake.pop();
  // 更新蛇身的样式
  const newHead = document.createElement("div");
  newHead.classList.add("snake");
  newHead.style.left = head.x * 20 + "px";
  newHead.style.top = head.y * 20 + "px";
  gameArea.insertBefore(newHead, gameArea.firstChild);
  // 移除蛇尾的样式
  if (removedSegment) {
    const elementsToDelete = document.querySelectorAll(".snake");
    if (elementsToDelete.length > 0) {
      const lastElement = elementsToDelete[elementsToDelete.length - 1];
      gameArea.removeChild(lastElement);
    }
  }
}
setInterval(moveSnake, 1000); // 每1000毫秒移动一次

在这个步骤中,我们使用了定时器来连续地移动蛇。通过按下方向键,你可以控制蛇的移动方向。

这里我们通过 gameArea.insertBefore(newHead, gameArea.firstChild);在gameArea的子节点的最前面插入一个新的节点,并通过gameArea.removeChild(lastElement);删除最后一个子节点。

最后,通过以下代码,来监听按键行为,来控制贪吃蛇前进方向:

document.addEventListener("keydown", (event) => {
  if (event.key === "ArrowUp" && direction !== "down") {
    direction = "up";
  } else if (event.key === "ArrowDown" && direction !== "up") {
    direction = "down";
  } else if (event.key === "ArrowLeft" && direction !== "right") {
    direction = "left";
  } else if (event.key === "ArrowRight" && direction !== "left") {
    direction = "right";
  }
});

步骤5:生成食物


接下来,我们将实现食物的生成功能。每当蛇吃掉食物后,我们将随机生成一个新的食物位置。

// script.js
let food = { x: 5, y: 5 }; // 初始食物位置
function generateFood() {
  let foodItem = document.querySelectorAll(".food");
  if (foodItem.length >= 1) {
    gameArea.removeChild(foodItem[0]);
  }
  food.x = Math.floor(Math.random() * 20); // 随机生成 x 坐标
  food.y = Math.floor(Math.random() * 20); // 随机生成 y 坐标
  if (food.x <= 0) {
    food.x = 1;
  } else if (food.x >= 20) {
    food.x = 19;
  }
  if (food.y <= 0) {
    food.y = 1;
  } else if (food.y >= 20) {
    food.y = 19;
  }
  // 创建食物元素
  const foodElement = document.createElement("div");
  foodElement.classList.add("food");
  foodElement.style.left = food.x * 20 + "px";
  foodElement.style.top = food.y * 20 + "px";
  gameArea.appendChild(foodElement);
}
generateFood();
/* styles.css */
.food {
  width: 20px;
  height: 20px;
  background-color: red;
  position: absolute;
}

步骤6:检测碰撞与游戏逻辑


最后,我们需要检测蛇是否与食物相撞以及是否撞墙,同时还需要处理游戏结束的情况。

// script.js
function checkCollision() {
  const head = snake[0];
  // 检查是否撞墙
  if (head.x < 0 || head.x >= 20 || head.y < 0 || head.y >= 20) {
    clearInterval(gameInterval); // 停止游戏
    alert("游戏结束!");
    return true;
  }
  // 检查是否与食物相撞
  console.log("head:", head.x, head.y);
  console.log("food:", food.x, food.y);
  if (head.x === food.x && head.y === food.y) {
    // 吃掉食物,增加蛇的长度
    let x, y;
    // 判断蛇尾增加的方向
    if (direction === "right") {
      x = snake[snake.length - 1].x - 1;
      y = snake[snake.length - 1].y;
    } else if (direction === "left") {
      x = snake[snake.length - 1].x + 1;
      y = snake[snake.length - 1].y;
    } else if (direction === "top") {
      x = snake[snake.length - 1].x;
      y = snake[snake.length - 1].y + 1;
    } else {
      x = snake[snake.length - 1].x;
      y = snake[snake.length - 1].y - 1;
    }
    snake.push({ x, y });
    const newTail = document.createElement("div");
    newTail.classList.add("snake");
    newTail.style.left = x * 20 + "px";
    newTail.style.top = y * 20 + "px";
    gameArea.appendChild(newTail);
    generateFood();
  }
  // 检查是否与自身相撞
  for (let i = 1; i < snake.length; i++) {
    if (head.x === snake[i].x && head.y === snake[i].y) {
      clearInterval(gameInterval); // 停止游戏
      alert("游戏结束!");
      return true;
    }
  }
  return false;
}
let gameInterval = setInterval(() => {
  if (!checkCollision()) {
    moveSnake();
  }
}, 1000);

涉及知识点:

  • 通过direction来判断贪吃蛇吃到食物后变长的方向
  • alert是用于弹出提示框的JS api

这样,我们就完成了一个简单的贪吃蛇游戏!通过上述步骤,我们从创建游戏区域、初始化蛇的位置,到实现蛇的移动、食物的生成,最终加入碰撞检测和游戏逻辑,完成了一个可玩的贪吃蛇游戏。

总结


通过这个项目,你不仅可以学习如何使用 JavaScript 来实现一个小游戏,还可以熟练学习js操作dom、数据的常用方法、定时器setInterval、css样式、事件监听器以及实现需求的逻辑思维等等知识,对前面学习的一些理论知识进行了实践,相信你跟着一套教程下来会对前面学习到的知识有更深的理解。

相关文章
|
2天前
|
存储 安全 API
Next.js 实战 (九):使用 next-auth 完成第三方身份登录验证
这篇文章介绍了next-auth,一个为Next.js设计的身份验证库,支持多种认证方式,如电子邮件和密码、OAuth2.0提供商(如Google、GitHub、Facebook等)以及自定义提供商。文章包含了如何配置Github Provider以及会话管理,并提到了适配器Adapters在next-auth中的作用。最后,文章强调了next-auth的强大功能值得进一步探索。
24 10
|
2月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
12天前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
66 33
|
4天前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
17 10
|
20天前
|
前端开发 API 开发者
Next.js 实战 (五):添加路由 Transition 过渡效果和 Loading 动画
这篇文章介绍了Framer Motion,一个为React设计的动画库,提供了声明式API处理动画和页面转换,适合创建响应式用户界面。文章包括首屏加载动画、路由加载Loading、路由进场和退场动画等主题,并提供了使用Framer Motion和next.js实现这些动画的示例代码。最后,文章总结了这些效果,并邀请读者探讨更好的实现方案。
|
17天前
|
JavaScript 前端开发 API
Next.js 实战 (六):如何实现文件本地上传
这篇文章介绍了在Next.js中如何实现文件上传到本地的方法。文章首先提到Next.js官方文档中没有提供文件上传的实例代码,因此开发者需要自行实现,通常有两种思路:使用Node.js原生上传或使用第三方插件如multer。接着,文章选择了使用Node.js原生上传的方式来讲解实现过程,包括如何通过哈希值命名文件、上传到指定目录以及如何分类文件夹。然后,文章展示了具体的实现步骤,包括编写代码来处理文件上传,并给出了代码示例。最后,文章通过一个效果演示说明了如何通过postman模拟上传文件,并展示了上传后的文件夹结构。
|
1月前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
2月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
1月前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
1月前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。

热门文章

最新文章