赞
踩
index.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>
- <link rel="stylesheet" href="../css/index.css">
- </head>
- <body>
- <!-- 整个游戏容器 -->
- <div class="container">
- <!-- 开始按钮 -->
- <button class="startBtn"></button>
- <!-- 暂停按钮 -->
- <button class="pauseBtn"></button>
- </div>
- <script src="../js/config.js"></script>
- <script src="../js/index.js"></script>
- </body>
- </html>
包括游戏整体的容器container,开始游戏按钮startBtn,中途暂停按钮pauseBtn
index.css
绘制高和宽为600px的正方形
插入开始游戏和继续游戏的按钮,需要水平垂直居中
- /*父元素设置为*/
- display: flex;
- /*水平居中*/
- justify-content: center;
- /*垂直居中*/
- align-items: center;
- *{
- /* 去除内外边距 */
- margin: 0;
- padding: 0;
- }
-
- /* 整体游戏容器 */
- .container{
- display: flex;
- /* 蛇要移动,是绝对定位 */
- position: relative;
- width: 600px;
- height: 600px;
- background-color: #fdfdfd;
- border: 20px solid #9bd0e7;
- margin: 20px auto;
- justify-content: center;
- align-items: center;
- }
-
- .container button{
- border: none;
- outline: none;
- }
-
- .startBtn{
- width: 200px;
- height: 120px;
- background: url(../img/start.jpg.jpg) center/contain no-repeat;
- display: block;
- }
- .pauseBtn{
- width: 200px;
- height: 70px;
- background: url(../img/play.webp.jpg) center/contain no-repeat;
- display: none;
- }
其中的图片是自己用画板绘制的,可在附录中找到
index.js
- function main() {
- //1.初始化游戏
- initGame();
- //2.绑定事件
- bindEvent();
- }
- main();
config.js 创建变量
- var gridData = []; //存储地图对象
-
- //整个网格的行与列
- var tr = 30;
- var td = 30; //因为地图600px, 即每个格子20pixel
'运行
index.js
- /**
- * 初始化游戏方法
- */
- function initGame() {
- //1.初始化地图
- for (var i = 0; i < tr; i++) {
- for (var j = 0; j < td; j++) {
- gridData.push({ //从左往右从上至下存放坐标{x,y} = {j,i}
- x: j,
- y: i
- });//遍历每一个格子,存放数据
- }
- }
- //console.log(gridData);
-
- //2.绘制蛇
- drawSnake(snake);
-
- //3.绘制食物
- drawFood();
- }
'运行
config.js
- //蛇的身体大小
- 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'},
- ]
- }
'运行
index.js
- /**
- * 绘制蛇的方法
- * @param {*} snake
- */
- function drawSnake(snake) {
- //遍历snakePos
- for (var i = 0; i < snake.snakePos.length; i++) {
- //判断是否创建
- if (!snake.snakePos[i].domContent) {
- //进入此if表示第一次创建蛇
- snake.snakePos[i].domContent = document.createElement("div");
- //靠父元素计算移动位置
- snake.snakePos[i].domContent.style.position = "absolute"; //postion: absolute
- snake.snakePos[i].domContent.style.width = snakeBody + "px"; //20px
- 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 = "linear-gradient(to right, #32cd32, #006400)";
- snake.snakePos[i].domContent.style.borderRadius = '50%';
- } else {
- //当前是蛇身
- snake.snakePos[i].domContent.style.background = "#ADFF2F";
- snake.snakePos[i].domContent.style.borderRadius = '50%';
- }
- }
- //需要将创建的dom元素添加到container容器中
- document.querySelector(".container").append(snake.snakePos[i].domContent);
- }
- }
'运行
蛇头和蛇身都用填充背景色来完成绘制,添加圆角'borderRadius'使得其展示为圆形
config.js
- //食物相关配置
- var food = {
- x: 0,
- y: 0,
- domContent: ""
- }
index.js
- /**
- * 绘制食物的方法
- */
- function drawFood() {
- //1.食物坐标随机
- //2.食物不能生成在container之外,border上,snakeBody,snakeHead)
- while (true) {
- //构造死循环,直到生成符合要求的食物坐标才能退出循环
- var isRepeat = false; //默认生成食物坐标符合要求
- food.x = Math.floor(Math.random() * tr);
- food.y = Math.floor(Math.random() * tr);
- //查看坐标是否符合要求(遍历蛇)
- for (let i = 0; i < snake.snakePos.length; i++) {
- if (snake.snakePos[i].x === food.x && snake.snakePos[i].y === food.y) {
- //当前食物与蛇重合,不符合要求
- isRepeat = true;
- break;//跳出当前for循环
- }
- }
- if (!isRepeat) {
- //当isRepeat为false的时候
- //跳出while
- break;
- }
- }
- //跳出while循环,得到food.x, food.y
- if (!food.domContent) {
- food.domContent = document.createElement("div");
- food.domContent.style.position = "absolute";
- food.domContent.style.width = snakeBody + "px";
- food.domContent.style.height = snakeBody + "px";
- food.domContent.style.background = "#A52A2A";
- food.domContent.style.borderRadius = '50%';
- document.querySelector('.container').append(food.domContent);
- }
- food.domContent.style.left = food.x * snakeBody + "px";
- food.domContent.style.top = food.y * snakeBody + "px";
- }
'运行
生成food随机,可以使用Math.random() * tr + 0来完成,共有30种情况(30个格子,包含坐标0,可省略)(这种算法能保证food不会生成在border上)
用for循环遍历snake,保证food的坐标和snake坐标不同,即没有生成在snake上,即合法
当用户在键盘上按下上下左右的时候,snake需要有方向上的更改(包括整体转向和蛇头角度转向)
index.js
- /**
- * 绑定事件
- */
- function bindEvent() {
- //1.用户键盘点击上下左右,蛇移动
-
- document.onkeydown = function (e) {
- if ((e.key === 'ArrowRight' || e.key.toLowerCase() === 'd') && snake.direction.flag !== 'left') {
- //1.右,右侧新增元素,遍历移动到最右侧,原最左侧的元素删除
- //x+1
- snake.direction = directionNum.right;
- }
- if ((e.key === 'ArrowUp' || e.key.toLowerCase() === 'w') && snake.direction.flag !== 'bottom') {
- //2.上
- //y-1
- //往下走的时候不能按上键
- snake.direction = directionNum.top;
- }
- if ((e.key === 'ArrowDown' || e.key.toLowerCase() === 's') && snake.direction.flag !== 'top') {
- //3.下
- //y+1
- snake.direction = directionNum.bottom;
- }
- if ((e.key === 'ArrowLeft' || e.key.toLowerCase() === 'a') && snake.direction.flag !== 'right') {
- //4.左
- //x-1
- snake.direction = directionNum.left;
- }
-
- }
- }
'运行
需要注意的是,默认如下情况:
当用户更改的方向不能和原来的方向相反,否则会碰到自己的身体。
逻辑:
定义一个新蛇头(newHead),移动后增加一个新的dom元素放入新头,旧头(oldHead)更改为body
如果没有吃到food,蛇长度不会增加,则原最后一段body的dom元素被删除
如果吃到了food,蛇长度增加,原最后一段body的dom元素不删除
因此,还需要增加一个碰撞检测
另外:如果吃到了food,food元素display更改为none,且重新绘制一次(drawFood();)
config.js
给snake对象增加默认的移动方向为right
控制蛇上下左右移动,坐标会相应地增加/减少
- //新蛇头和旧蛇头之间的关系
- //用directionNum和旧蛇头坐标做计算
- var directionNum = {
- left: {x: -1, y: 0, flag: 'left'},
- right: {x: 1, y: 0, flag: 'right'},
- top: {x: 0, y: -1, flag: 'top'},
- bottom: {x: 0, y: 1, flag: 'bottom'},
- }
-
- //蛇相关配置
- var snake = {
- //蛇一开始移动的方向->右
- direction:directionNum.right,
- //蛇的初始位置
- 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'},
- ]
- }
'运行
newHead的坐标 = oldHead的坐标 + config中定义的不同方向会导致的坐标变化
因此,总体index.js如下
- /**
- * 蛇移动方法
- */
- function snakeMove() {
- var oldHead = snake.snakePos[snake.snakePos.length - 1];
- //根据方向计算出新蛇头的坐标
- var newHead = {
- domContent: "",
- x: oldHead.x + snake.direction.x,
- y: oldHead.y + snake.direction.y,
- flag: 'head'
- }
-
- //碰撞检测
- //food, body, 墙壁
- var collideCheckResult = isCollide(newHead);
- if (collideCheckResult.isCollide) {
- //进入此if,碰到墙或者body
- if (window.confirm(`
- 游戏结束,您当前的得分为${score}分,是否要重新开始游戏?
- `)) {
- //用户点确定,重新开始游戏
- //清空所有div
- document.querySelector('.container').innerHTML = `
- <!-- 开始按钮 -->
- <button class="startBtn" style="display: none"></button>
- <!-- 暂停按钮 -->
- <button class="pauseBtn" style="display: none"></button>
- `;
- score = 0;
- snake = {
- // 蛇一开始移动的方向 -> 右
- direction: directionNum.right,
- //蛇的初始位置
- 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' },
- ]
-
- }
- food = {
- x: 0,
- y: 0,
- domContent: ""
- }
- initGame();
-
- } else {
- //结束游戏
- document.onkeydown = null; //删除绑定的事件
- clearInterval(timeStop); //蛇停止移动,否则结束游戏之后仍然一直撞墙,会持续游戏结束
- }
- return;
-
- }
-
- //将旧的head改为body
- oldHead.flag = 'body';
- oldHead.domContent.style.background = "#ADFF2F";
- oldHead.domContent.style.borderRadius = '50%';
- snake.snakePos.push(newHead);
- //判断是否吃到food
- if (collideCheckResult.isEat) {
- //进入此if,吃到food
- //生成新food
- drawFood();
- } else {
- //没吃到food,移除最后一个元素,保持原长度
- document.querySelector(".container").removeChild(snake.snakePos[0].domContent);
- snake.snakePos.shift(); //删除第一个元素,即最后一个body
- }
- //重新绘制snake
- drawSnake(snake);
-
-
- }
'运行
如果控制蛇转向,蛇头的角度也需要转,index.js -> drawSnake()新增
- if (snake.snakePos[i].flag === 'head') {
- //当前是蛇头
- snake.snakePos[i].domContent.style.background = "linear-gradient(to right, #32cd32, #006400)";
- snake.snakePos[i].domContent.style.borderRadius = '50%';
- //根据方向进行旋转
- switch (snake.direction.flag) {
- case 'top': {
- snake.snakePos[i].domContent.style.transform = `
- rotate(-90deg)
- `; //倒着转90度
- }
- case 'bottom': {
- snake.snakePos[i].domContent.style.transform = `
- rotate(90deg)
- `; //转90度
- }
- case 'left': {
- snake.snakePos[i].domContent.style.transform = `
- rotate(180deg)
- `;
- }
- case 'right': {
- snake.snakePos[i].domContent.style.transform = `
- rotate(0deg)
- `;
- }
- break;
- }
- } else {
- //当前是蛇身
- snake.snakePos[i].domContent.style.background = "#ADFF2F";
- snake.snakePos[i].domContent.style.borderRadius = '50%';
- }
碰撞检测需要检查两个逻辑:
1.snake是否碰到了food(isEat)
2.snake是否碰到了墙壁或者自己的body(isCollide)
index.js
- /**
- * 碰撞检测
- * @param {*} newHead
- */
- function isCollide(newHead) {
- var collideCheckInfo = {
- isCollide: false, //是否碰撞墙壁、body
- isEat: false //是否吃到food
- }
- //1.检测是否碰到墙壁
- if (newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr) {
- collideCheckInfo.isCollide = true;
- return collideCheckInfo;
- }
-
- //2.检测是否碰到body
- for (let i = 0; i < snake.snakePos.length; i++) {
- if (newHead.x === snake.snakePos[i].x && newHead.y === snake.snakePos[i].y) {
- collideCheckInfo.isCollide = true;
- return collideCheckInfo;
- }
- }
-
- //3.检测是否吃到food
- if (newHead.x === food.x && newHead.y === food.y) {
- collideCheckInfo.isEat = true;
- score++; //分数自增
- }
- return collideCheckInfo;
- }
'运行
用户点击container,游戏暂停(蛇移动停止),出现继续游戏(pauseBtn)按钮
点击游戏继续(pauseBtn),蛇继续移动
index.js -> bindEvent函数中新增
- //2.计数器自动调用蛇移动的方法
- startGame();
-
- document.querySelector('.container').onclick = function (e) {
- if (e.target.className === "container") {
- //3.点击整个容器container,暂停游戏
- //暂停操作
- document.querySelector('.pauseBtn').style.display = "block";
- //蛇运动暂停
- clearInterval(timeStop);
- } else {
- //4.点击'continue'按钮,游戏继续
- //游戏继续操作 className === "pauseBtn"
- document.querySelector('.pauseBtn').style.display = "none";
- //蛇运动继续
- startGame();
- }
-
- }
游戏开始时,需要点击开始游戏(startBtn),游戏才运行
修改main函数
- /**
- * 游戏的主方法
- */
-
- function main() {
- //用户点击开始游戏之后才开始后续的流程
- document.querySelector('.startBtn').onclick = function (e) {
- e.stopPropagation();
- document.querySelector('.startBtn').style.display = "none";
-
- //1.初始化游戏
- initGame();
- //2.绑定事件
- bindEvent();
- }
- }
- main();
config.js
- //停止计时器
- var timeStop = null;
-
- var time = 500; //0.5s
'运行
index.js
- /**
- * 计时器自动调用snakeMove()
- */
- function startGame() {
- timeStop = setInterval(function () {
- snakeMove();
- }, time)
- }
'运行
config.js
var score = 0;
'运行
每次吃到食物,score自增
当snake死亡(碰到墙壁或body)时,弹出对话框,显示分数${score}
此时可以选择重新开始游戏或取消
1. 如果选择重新开始游戏,重新增加button且都设置为'display: none',分数score、snake和food重置,再次调用initGame()初始化游戏
2. 如果选择取消重玩,删除绑定的事件(则按下取消后用户再按键盘或点击屏幕都没有反应),计时器停止计时(否则取消后snake仍然自动移动,会持续碰墙或body,持续发生游戏结束的弹窗)
实现:
snakeMove()中新增
- if (collideCheckResult.isCollide) {
- //进入此if,碰到墙或者body
- if (window.confirm(`
- 游戏结束,您当前的得分为${score}分,是否要重新开始游戏?
- `)) {
- //用户点确定,重新开始游戏
- //清空所有div
- document.querySelector('.container').innerHTML = `
- <!-- 开始按钮 -->
- <button class="startBtn" style="display: none"></button>
- <!-- 暂停按钮 -->
- <button class="pauseBtn" style="display: none"></button>
- `;
- score = 0;
- snake = {
- // 蛇一开始移动的方向 -> 右
- direction: directionNum.right,
- //蛇的初始位置
- 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' },
- ]
-
- }
- food = {
- x: 0,
- y: 0,
- domContent: ""
- }
- initGame();
-
- } else {
- //结束游戏
- document.onkeydown = null; //删除绑定的事件
- clearInterval(timeStop); //蛇停止移动,否则结束游戏之后仍然一直撞墙,会持续游戏结束
- }
- return;
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。