-
2021-03-08 12:10:49
JS贪吃蛇
Js原生贪吃蛇:canvas
HTML
<canvas id="can"></canvas>
#can{ background: #000000; height: 400px; width: 850px; }
JS
//公共板块 var blockSize = 10; //地图的高宽 var mapW = 300; var mapH = 150; //历史食物var var historyfood = new Array(); //食物数组 var img = new Image() var arrFood = ["ananas.png","hamburg.png","watermelon.png"] historyfood =[{x: 0,y:0}]; //贪吃蛇默认值 var snake = [{x:3,y:0},{x:2,y:0},{x:1,y:0}] //贪吃蛇方向 var directionX = 1; var directionY = 0; //添加一个标记,标记食物是否被吃掉 //默认值:没有被吃掉 var isFood = false; //判断游戏状态 //默认游戏继续 var gameState = false; //限制贪吃蛇所移动的方向不能反方向操作 var lockleft = true; var lockright = true; var lockup = true; var lockdown = true; //贪吃蛇分数 var count = 0; //贪吃蛇速度 var speed = 1000 - (count + 5); $(function () { $("#divContainer").height($("#can").height()); //1,获取到画布对象 var can = document.getElementById("can"); //2,获取到画图工具箱 var tools = can.getContext('2d'); tools.beginPath(); //设置食物默认值 var XY = Randomfood(); console.log(XY); var x1 = Randomfood().x; var y1 = Randomfood().y; img.src = "/aimless/img/GluttonousSnakeFood/"+ arrFood[Math.floor(Math.random() * arrFood.length)]; //控制贪吃蛇移动 document.addEventListener('keydown',function (e){ switch (e.keyCode) { case 38: if (lockup){ directionX = 0; directionY = -1; lockdown = false; lockleft = true; lockright = true; } break; case 40: if (lockdown){ directionX = 0; directionY = 1; lockup = false; lockleft = true; lockright = true; } break; case 37: if (lockleft){ directionX = - 1; directionY = 0; lockright = false; lockup = true; lockdown = true; } break; case 39: if (lockright){ directionX = 1; directionY = 0; lockleft = false; lockup = true; lockdown = true; } break; } }) setIntervalSnake(tools,x1,y1); //4,找位置 }) function setIntervalSnake(tools,x1,y1){ setInterval(function (){ if (gameState){ return; } //1,擦除画板 tools.clearRect(0,0,850,420); var oldHead = snake[0]; if (oldHead.x < 0 || oldHead.y < 0 || oldHead.x * blockSize >= mapW || oldHead.y * blockSize >= mapH){ gameState = true; alert("游戏结束"); }else { //蛇移动 if (snake[0].x * blockSize === x1 && snake[0].y * blockSize === y1){ isFood = true; } else { snake.pop() } var newHead = { x: oldHead.x + directionX, y: oldHead.y + directionY } snake.unshift(newHead); } //2,判断食物是否被吃掉,吃掉刷新,不吃掉不刷新 if (isFood){ count = count + 1; $("#count").html(count); x1 = Randomfood().x; y1 = Randomfood().y; img.src = "/aimless/img/GluttonousSnakeFood/"+ arrFood[Math.floor(Math.random() * arrFood.length)]; isFood = false; } tools.drawImage(img,x1,y1,blockSize,blockSize); //蛇身数组 var Thesnakebody = new Array(); //3,画蛇 for (var i = 0; i < snake.length; i++){ if (i == 0){ tools.fillStyle = "#9933CC"; } else { var newHead1 = { x: snake[i].x, y: snake[i].y } Thesnakebody.unshift(newHead1); tools.fillStyle = "#33adcc"; } tools.fillRect(snake[i].x * blockSize,snake[i].y * blockSize,blockSize,blockSize); } // //判断蛇头咬到了蛇身 Thesnakebody.forEach(item=>{ if(item.x == snake[0].x && item.y == snake[0].y){ gameState = true; setIntervalSnake(tools,x1,y1); } }) //4,画地图 var width = Math.round($("#can").width() / blockSize); var height = Math.round($("#can").height() / blockSize); for (var i = 1; i < width;i++){ tools.moveTo(0,blockSize * i); tools.lineTo($("#can").width(),blockSize * i); } for (var i = 1; i < height;i++){ tools.moveTo(blockSize * i,0); tools.lineTo(blockSize * i,$("#can").height()); } tools.strokeStyle = "#FFFFFF"; //5,绘制 tools.stroke(); },speed / 3); } //随机食物不 function Randomfood() { var RandomX = Math.floor(Math.random() * mapW / blockSize) * blockSize; var RandomY = Math.floor(Math.random() * mapH / blockSize) * blockSize; setInterval(function (){ snake.forEach(item=>{ console.log(RandomX / blockSize,RandomY / blockSize); // console.log(item.x,item.y); if(item.x == RandomX / blockSize && item.y == RandomY / blockSize){ return Randomfood(); } else { return ; } }) }, 10 / 3); var newRandom = { x: RandomX, y: RandomY } return newRandom; }
(第一次写如果觉得有用点个赞)
更多相关内容 -
Javascript贪吃蛇游戏源码
2018-10-31 11:05:05贪吃蛇小游戏源码,可以实现分数累加,吃到食物可以随机变化颜色等 -
JavaScript贪吃蛇
2021-11-19 09:35:58今天博主呕心沥血写了一个贪吃蛇的小游戏,整个过程从无到有简直扣人心弦。 接下来本博主就来好好说到说到这个过程!! 话不多说,我们还是先来看看最后的呈现效果吧。 看完了才知道什么叫做操作,简直传奇!!...这篇文章不简单!!
今天博主呕心沥血写了一个贪吃蛇的小游戏,整个过程从无到有简直扣人心弦。
接下来本博主就来好好说到说到这个过程!!
话不多说,我们还是先来看看最后的呈现效果吧。
看完了才知道什么叫做操作,简直传奇!!
接下来不吹牛来讲讲实际操作 !
首先我们要知道所谓的贪吃蛇无非就是在表格上面行走的颜色!最初的效果也就是如下图所示
当然在这个地方是用数组来操作的,也是用构造函数来封装的。
在这个地方封装了三个 js 文件,分别用来写 食物、蛇、还有我们的中介者 Game.js 文件!
我刚刚也说了,写贪吃蛇是一个从无到有的过程,所以最开始在我们的 index.html 里面什么都没有,就只有一个 div 盒子,其他的表格、蛇、食物都是后期用函数创建的。
一、创建html结构
以下是我们 body 里面最开始的代码:
<body> <div id="app"> </div> <script src="js/Snake.js"></script> <script src="js/Food.js"></script> <script src="js/Game.js"></script> <script> var game=new Game(); </script> </body>
二、创建表格
从无到有第一步,当然是先把我们的表格追加到页面上,得有一个大体的框架!!这样就能看到刚刚上面图中所示的表格了,看到这仿佛就觉得接下来的每一步都是值得的!
function Game() { this.row = 25; // 行数 this.col = 25; // 列数 this.init(); //初始化节点 } Game.prototype.init = function () { this.dom = document.createElement('table'); // 创建表格 var tr, td; // 遍历行和列 for (var i = 0; i < this.row; i++) { tr = document.createElement('tr'); // 创建行 for (var j = 0; j < this.col; j++) { td = document.createElement('td'); // 创建列 tr.appendChild(td); // 把列追加到行 } this.dom.appendChild(tr); // 把行追加到表格 } document.querySelector('#app').appendChild(this.dom); //把表格追加到div里 }
三、创建蛇头、蛇身
从无到有第二步,蛇头蛇头蛇头、蛇身蛇身蛇身!看到这觉得学到知道是多么重要的环节。
function Snake() { // 蛇的初始化身体 this.body = [ { 'row': 3, 'col': 5 }, { 'row': 3, 'col': 4 }, { 'row': 3, 'col': 3 }, { 'row': 3, 'col': 2 } ]; } Snake.prototype.render = function () { // 蛇头的渲染 game.setColorHead(this.body[0].row, this.body[0].col); // 蛇身的渲染 for (var i = 1; i < this.body.length; i++) { game.setColor(this.body[i].row, this.body[i].col, '#649c49') } }
Snake 里面调用 中介者 Game 原型对象里面的属性!
Game.prototype.setColor = function (row, col, color) { this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.background = color; }
四、创建食物
从无到有第三步,食物食物食物! 看到这,我们的基本形态就算是全都有了!
function Food(gameSnake) { var self = this; // 下面的 do-while 循环语句作用是先创建一个 row 和 col 然后判断这个 row 和 col 是否在蛇的身上 do { // 食物的位置 this.row = parseInt(Math.random() * gameSnake.row) this.col = parseInt(Math.random() * gameSnake.col) } while ((function () { // 遍历蛇的 row col 然后和 food 新随机出来的 row col 进行判断,是否重合 for (var i = 0; i < gameSnake.snake.body.length; i++) { if (self.row == gameSnake.snake.body[i].row && self.col == gameSnake.snake.body[i].col) { return true; } } return false; })()); } Food.prototype.render = function () { game.setHTML(this.row, this.col); }
五、让蛇动起来
从无到有第四步,动起来动起来动起来!这里用数组的头增尾删简直就是天作之合!
// 蛇的运动 Snake.prototype.update = function () { this.direction = this.willDirection; switch (this.direction) { case 'R': //右 this.body.unshift({ 'row': this.body[0].row, 'col': this.body[0].col + 1 }); break; case 'D': //下 this.body.unshift({ 'row': this.body[0].row + 1, 'col': this.body[0].col }); break; case 'L': //左 this.body.unshift({ 'row': this.body[0].row, 'col': this.body[0].col - 1 }); break; case 'U': //上 this.body.unshift({ 'row': this.body[0].row - 1, 'col': this.body[0].col }); break; } // 死亡的判断,超出了表格边缘的部分 if (this.body[0].col > game.col - 1 || this.body[0].col < 0 || this.body[0].row > game.row - 1 || this.body[0].row < 0) { alert('撞到墙了哦,一共吃掉了' + game.score + '颗草莓'); this.body.shift(); clearInterval(game.timer); location.reload(); } // 自己撞到自己的时候会判定死亡 for (var i = 1; i < this.body.length; i++) { // 如果当前蛇的头部和身体的某一个部位的 row 和 col 完全重合的时候 if (this.body[0].row == this.body[i].row && this.body[0].col == this.body[i].col) { alert('撞到自己了,吃掉了' + game.score + '颗草莓'); this.body.shift(); clearInterval(game.timer); location.reload(); } } // 蛇吃食物 // 判断如果当前的蛇的头部没有和食物进行重合,就代表此时没有吃到食物,此时就进行尾部删除,如果重合了就代表迟到了,此时我们不进行删除尾部 if (this.body[0].row == game.food.row && this.body[0].col == game.food.col) { // 此时情况是只有头部增加了,尾部没有删除 game.food = new Food(game); //创建新的食物 game.score++; game.f = 0; } else { this.body.pop(); //删除数组最后一个元素 } }
六、控制蛇的方向
从无到有第五步,蛇的方向! 做到这里,回望过去的每一步,都是浮云!!
// 蛇的方向改变,防止的是在一次渲染之前会出现调头的情况 Snake.prototype.changeDirection = function (d) { this.willDirection = d; }
// 设置键盘的事件监听 Game.prototype.bindEvent = function () { var self = this; document.addEventListener('keydown', function (e) { // 用ASCII码值判断键盘方向 switch (e.keyCode) { case 37: //左 if (self.snake.direction == 'R') return; // 先进行判断,如果当前的方向是向右移动,此时我们不能按左键 self.snake.changeDirection('L'); self.d = 'L'; break; case 38: //上 if (self.snake.direction == 'D') return; // 先进行判断,如果当前的方向是向下移动,此时我们不能按上键 self.snake.changeDirection('U'); self.d = 'U'; break; case 39: //右 if (self.snake.direction == 'L') return; // 先进行判断,如果当前的方向是向左移动,此时我们不能按右键 self.snake.changeDirection('R'); self.d = 'R'; break; case 40: //下 if (self.snake.direction == 'U') return; // 先进行判断,如果当前的方向是向上移动,此时我们不能按下键 self.snake.changeDirection('D'); self.d = 'D'; break; } }) }
最后按照我们喜欢的样子设置样式就好啦,这里我按我自己喜欢粉色设置相应的颜色以及插入我喜换的食物的样子!!
七、完整代码
index.html
<!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> * { padding: 0; margin: 0; } #app { position: relative; border: 20px solid #f8bbd0; background-color: #fce4ec; width: 500px; height: 500px; margin: 15px auto; } table { border-collapse: collapse; background-color: #fce4ec; } td { position: relative; background-size: 100% 100%; border-radius: 50%; width: 20px; height: 20px; text-align: center; /* background-color: #fce4ec; */ /* border: 1px solid #aaa; */ } td .snake { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .start, .suspend { cursor: pointer; position: absolute; width: 150px; top: 50%; left: 50%; transform: translate(-50%, -50%); } .suspend { display: none; z-index: 2; } </style> </head> <body> <!-- <h3 id="f">帧编号:0</h3> <h3 id="score">分数:0</h3> --> <div id="app"> <img src="images/start.gif" alt="" class="start"> <img src="images/suspended.png" alt="" class="suspend"> </div> <!-- <script src="js/last.js"></script> --> <script src="js/Snake.js"></script> <script src="js/Food.js"></script> <script src="js/Game.js"></script> <script> var game = null; var flag = true; var suspend = document.querySelector('.suspend'); document.querySelector('.start').addEventListener('click', function () { // document.querySelector('#app').style.backgroundColor='white'; this.style.display = 'none'; game = new Game(); document.querySelector('table').addEventListener('click', function () { clearInterval(game.timer); suspend.style.display = 'block'; }) suspend.addEventListener('click', function () { suspend.style.display = 'none'; game.timer = setInterval(function () { game.f++; // document.getElementById('f').innerHTML = '帧编号:' + game.f; // document.getElementById('score').innerHTML = '分数:' + game.score; // 清屏 game.clear(); // 蛇的运动(更新) // 蛇的更新速度,当蛇变长的时候,速度要加快 var during = game.snake.body.length < 30 ? 30 - game.snake.body.length : 1; game.f % during == 0 && game.snake.update(); // game.snake.update(); // 渲染蛇 game.snake.render(); // 渲染食物 game.food.render(); }, 10) }) }) </script> </body> </html>
Game.js
function Game() { this.row = 25; // 行数 this.col = 25; // 列数 this.score = 0; //分数 this.init(); //初始化节点 this.snake = new Snake(); //实例化蛇类 this.food = new Food(this); //初始化食物 // this.last = new Last(); this.start(); //执行定时器任务 this.bindEvent(); //键盘的事件监听 this.d = 'R'; } Game.prototype.init = function () { this.dom = document.createElement('table'); // 创建表格 var tr, td; // 遍历行和列 for (var i = 0; i < this.row; i++) { tr = document.createElement('tr'); // 创建行 for (var j = 0; j < this.col; j++) { td = document.createElement('td'); // 创建列 tr.appendChild(td); // 把列追加到行 } this.dom.appendChild(tr); // 把行追加到表格 } document.querySelector('#app').appendChild(this.dom); //把表格追加到div里 } // 遍历表格,清除表格上的颜色 Game.prototype.clear = function () { for (var i = 0; i < this.row; i++) { for (var j = 0; j < this.col; j++) { this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].style.background = ''; this.dom.getElementsByTagName('tr')[i].getElementsByTagName('td')[j].innerHTML = ''; } } } // 设置颜色的方法 让表格的第几行,第几列设置什么颜色 Game.prototype.setColor = function (row, col, color) { this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.background = color; } // 设置蛇头 Game.prototype.setColorHead = function (row, col) { var img = document.createElement('img'); img.src = 'images/snake.png'; img.className = 'snake'; this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].appendChild(img); // this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.backgroundColor='transparent' switch (this.d) { case 'R': //右 break; case 'D': //下 img.style.transform = 'rotate(90deg)'; break; case 'L': //左 img.style.transform = 'rotate(180deg)'; break; case 'U': //上 img.style.transform = 'rotate(-90deg)'; break; } } // 渲染食物 Game.prototype.setHTML = function (row, col) { this.dom.getElementsByTagName('tr')[row].getElementsByTagName('td')[col].style.backgroundImage = 'url(./images/food.png)'; } // 设置键盘的事件监听 Game.prototype.bindEvent = function () { var self = this; document.addEventListener('keydown', function (e) { // 用ASCII码值判断键盘方向 switch (e.keyCode) { case 37: //左 if (self.snake.direction == 'R') return; // 先进行判断,如果当前的方向是向右移动,此时我们不能按左键 self.snake.changeDirection('L'); self.d = 'L'; break; case 38: //上 if (self.snake.direction == 'D') return; // 先进行判断,如果当前的方向是向下移动,此时我们不能按上键 self.snake.changeDirection('U'); self.d = 'U'; break; case 39: //右 if (self.snake.direction == 'L') return; // 先进行判断,如果当前的方向是向左移动,此时我们不能按右键 self.snake.changeDirection('R'); self.d = 'R'; break; case 40: //下 if (self.snake.direction == 'U') return; // 先进行判断,如果当前的方向是向上移动,此时我们不能按下键 self.snake.changeDirection('D'); self.d = 'D'; break; } }) } Game.prototype.start = function () { // 帧编号 this.f = 0; // 定时器里面的核心就是游戏的渲染本质:清屏-更新-渲染 this.timer = setInterval(function () { game.f++; // document.getElementById('f').innerHTML = '帧编号:' + game.f; // document.getElementById('score').innerHTML = '分数:' + game.score; // 清屏 game.clear(); // 蛇的运动(更新) // 蛇的更新速度,当蛇变长的时候,速度要加快 var during = game.snake.body.length < 30 ? 30 - game.snake.body.length : 1; game.f % during == 0 && game.snake.update(); // game.snake.update(); // 渲染蛇 game.snake.render(); // 渲染食物 game.food.render(); }, 10) }
Snake.js
function Snake() { // 蛇的初始化身体 this.body = [ { 'row': 3, 'col': 5 }, { 'row': 3, 'col': 4 }, { 'row': 3, 'col': 3 }, { 'row': 3, 'col': 2 } ]; this.direction = 'R'; //信号量,设置运动方向 this.willDirection = 'R'; //即将改变的方向,目的就是为了方向出现原地调头的情况 } Snake.prototype.render = function () { // 蛇头的渲染 game.setColorHead(this.body[0].row, this.body[0].col); // 蛇身的渲染 for (var i = 1; i < this.body.length; i++) { game.setColor(this.body[i].row, this.body[i].col, '#649c49') } } // 蛇的运动 Snake.prototype.update = function () { this.direction = this.willDirection; switch (this.direction) { case 'R': //右 this.body.unshift({ 'row': this.body[0].row, 'col': this.body[0].col + 1 }); break; case 'D': //下 this.body.unshift({ 'row': this.body[0].row + 1, 'col': this.body[0].col }); break; case 'L': //左 this.body.unshift({ 'row': this.body[0].row, 'col': this.body[0].col - 1 }); break; case 'U': //上 this.body.unshift({ 'row': this.body[0].row - 1, 'col': this.body[0].col }); break; } // 死亡的判断,超出了表格边缘的部分 if (this.body[0].col > game.col - 1 || this.body[0].col < 0 || this.body[0].row > game.row - 1 || this.body[0].row < 0) { alert('撞到墙了哦,一共吃掉了' + game.score + '颗草莓'); this.body.shift(); clearInterval(game.timer); location.reload(); } // 自己撞到自己的时候会判定死亡 for (var i = 1; i < this.body.length; i++) { // 如果当前蛇的头部和身体的某一个部位的 row 和 col 完全重合的时候 if (this.body[0].row == this.body[i].row && this.body[0].col == this.body[i].col) { alert('撞到自己了,吃掉了' + game.score + '颗草莓'); this.body.shift(); clearInterval(game.timer); location.reload(); } } // 蛇吃食物 // 判断如果当前的蛇的头部没有和食物进行重合,就代表此时没有吃到食物,此时就进行尾部删除,如果重合了就代表迟到了,此时我们不进行删除尾部 if (this.body[0].row == game.food.row && this.body[0].col == game.food.col) { // 此时情况是只有头部增加了,尾部没有删除 game.food = new Food(game); //创建新的食物 game.score++; game.f = 0; } else { this.body.pop(); //删除数组最后一个元素 } } // 蛇的方向改变,防止的是在一次渲染之前会出现调头的情况 Snake.prototype.changeDirection = function (d) { this.willDirection = d; }
Food.js
function Food(gameSnake) { var self = this; // 下面的 do-while 循环语句作用是先创建一个 row 和 col 然后判断这个 row 和 col 是否在蛇的身上 do { // 食物的位置 this.row = parseInt(Math.random() * gameSnake.row) this.col = parseInt(Math.random() * gameSnake.col) } while ((function () { // 遍历蛇的 row col 然后和 food 新随机出来的 row col 进行判断,是否重合 for (var i = 0; i < gameSnake.snake.body.length; i++) { if (self.row == gameSnake.snake.body[i].row && self.col == gameSnake.snake.body[i].col) { return true; } } return false; })()); } Food.prototype.render = function () { game.setHTML(this.row, this.col); }
八、图片
接下里我把我用的图片放到这个地方,喜欢的宝宝们也可以直接拿去用!
九、总结
看到最后的朋友们有没有觉得其实还是很简单的!感兴趣的赶紧去试试叭!!
-
javascript贪吃蛇
2019-01-22 14:13:33瞎写的,很多功能没实现,比如暂停开始,本来想整合到一起 -
javaScript贪吃蛇源代码
2020-05-11 14:16:22javaScript贪吃蛇源代码,详细的javaScript贪吃蛇代码,里面都写了注释 -
JS贪吃蛇游戏代码
2020-12-01 22:39:24JS贪吃蛇游戏代码是一款很好玩的网页游戏代码。游戏难度分为初级、中级、高级、超高级、变态,选择难度开始游戏,游戏过程中可以随时调整难度级别。 -
JavaScript贪吃蛇小游戏.zip
2021-04-19 17:27:55JavaScript贪吃蛇小游戏 -
js贪吃蛇源码
2018-11-23 11:09:31纯 javascript网页贪吃蛇源码下载 几百行 源码不多,有注释,浏览器打开可运行 -
javascript贪吃蛇游戏设计与实现
2020-10-14 18:25:08主要为大家详细介绍了javascript贪吃蛇游戏设计与实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
Javascript贪吃蛇
2019-05-27 01:05:30NULL 博文链接:https://liusu.iteye.com/blog/534281 -
html+CSS+js贪吃蛇
2018-07-10 13:04:07HTML+CSS+JS实现一个简单贪吃蛇的功能,一百多行的代码 -
js贪吃蛇游戏实现思路和
2020-10-22 13:31:56主要介绍了js贪吃蛇游戏实现思路,并分享贪吃蛇游戏源码,感兴趣的小伙伴们可以参考一下 -
js贪吃蛇(举一反三)
2020-04-16 14:40:42自己编写,大家举一反三,共同学习!我的邮箱 lizhilimaster@163.com,可以访问我的仓库码云仓库 https://github.com/lizhilicctv/ github仓库 https://gitee.com/lizhiliwo -
JS贪吃蛇
2020-08-21 00:33:45用JS代码实现网页版贪吃蛇小游戏 老规矩,代码自用自取,复制粘贴直接使用,喜欢的话可以查看博主其它文章,贡献一丢丢的浏览量,感激不尽。 <!DOCTYPE html> <html> <head> <title>...300行JS代码,让你重回经典
诺基亚贪吃蛇——我的游戏启蒙 儿时的经典游戏,无非俄罗斯方块、贪吃蛇、推箱子,之后才有了智能拼图、麻将、象棋、连连看、消消乐。今天拿出来一款JS贪吃蛇供大家学习、参考。
游戏玩法不多做赘述,可以调整关卡,可以调整爬行速度,祝您游戏愉快。部分游戏截图:
(如果你对前端还比较陌生,偶然刷到了这篇文章又不知该如何操作,请点击“ 这里 ”,一次就能学废)<!DOCTYPE html> <html> <head> <title>贪吃蛇</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> .backDiv { text-align: center; position:absolute; top:20px; left:300px; width:820px; height:550px; } .display { position:absolute; top:0px; left:685px; width:180px; height:510px; border: 5px solid black; background-color: #ccffff; } #gamePanel table { border-collapse: collapse;/*合并单元格边框*/ border: 5px solid black; background-color: #f0f0f0; } #gamePanel td { width:15px; height:15px; } #scoreDiv { position: absolute; top: 20px; left:20px; text-align: left; font-size: 20px; font-weight: bold; } #prompt { position: absolute; top: 180px; left: 20px; text-align: left; font-weight: bold; } #operator { position: absolute; top:550px; left:100px; } #result { position: absolute; top:200px; left:220px; background-color: #00ffff; font-size: 20px; font-weight: bold; text-align: left; padding: 20px; } </style> <script> var snake = [];// 蛇数组 var barrers = [];// 障碍物数组 var game = {sizeX:"30",sizeY:"40",record:"0"}; var appearTime = disappearTime = 0;// 超级食物出现 var superFoodX = superFoodY = null; var color = 0;// 食物颜色 function init(){ clearTimeout(game.time); snake = []; barrers = []; game.x = 0;game.y = 1; game.dir = null; game.direction = "right"; game.rate = null; game.lengths = 2;// 蛇的长度 game.score = 0; $("scoreDiv").innerHTML = "得分:"+game.score+"<br/><br/>长度:"+ game.lengths+"<br/><br/>"+"最高分:"+game.record; $("pass").disabled = false; $("rate").disabled = false; $("start").disabled = false; bord();// 设置界面 barrer();// 设置障碍物 $("result").style.visibility = "hidden"; snake.push("tb_" + game.x + "_" + (game.y-1)); snake.push("tb_" + game.x + "_" + game.y);// 蛇头 } function $(id){ return document.getElementById(id); } // 表格 function bord(){ var panel = [];// 游戏面板 var proPanel = [];// 提示面板 panel.push("<table>"); for(var i=0; i<game.sizeX; i++){ panel.push("<tr>"); for(var j=0; j<game.sizeY; j++){ panel.push('<td id="tb_' + i + "_" + j + '" > </td>'); } panel.push("</tr>"); } panel.push("</table>"); $("gamePanel").innerHTML = panel.join(""); proPanel.push("<table>"); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#ff3300"></td>' + '<td> 得分+10</td>'+'</tr>'); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#33cc33"></td>' + '<td> 得分+30</td>'+'</tr>'); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#99ff66"></td>' + '<td> 得分+50</td>'+'</tr>'); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#ffff00"></td>' + '<td> 得分+100</td>'+'</tr>'); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#006600"></td>' + '<td> 得分-30</td>'+'</tr>'); proPanel.push('<tr>'+'<td width="20px" height="20px" bgcolor="#000000"></td>' + '<td> 得分-50</td>'); proPanel.push("</table>"); $("prompt").innerHTML = proPanel.join(""); } // 关卡障碍物设置 function barrer() { var passValue = $("pass").value; for(var i=0; i<barrers.length; i++) { $(barrers[i]).style.backgroundColor = ""; } barrers = []; if(passValue == 2) { for(var i=8; i<game.sizeX-8; i++) { barrers.push("tb_" + i + "_" + 10); barrers.push("tb_" + i + "_" + (game.sizeY-10)); } } if(passValue == 3) { for(var i=8; i<game.sizeY-8; i++) { barrers.push("tb_" + Math.floor(game.sizeX/2) + "_" + i); } for(var i=6; i<game.sizeX-6; i++) { barrers.push("tb_" + i + "_" + Math.floor(game.sizeY/2)); } } if(passValue == 4) { for(var i=12; i<game.sizeY-11; i++) { barrers.push("tb_" + Math.floor(game.sizeX/2) + "_" + i); } for(var j=6; j<game.sizeX-6; j++) { barrers.push("tb_" + j + "_" + Math.floor(game.sizeY/2+1)); } for(var i=6; i<game.sizeX-6; i++) { if(i < game.sizeX/2) {barrers.push("tb_"+i+"_"+12);} else{barrers.push("tb_"+i+"_"+(game.sizeY-11));} } for(var j=12; j<game.sizeY-11; j++) { if(j <= game.sizeY/2) {barrers.push("tb_"+(game.sizeX-7)+"_"+j);} else {barrers.push("tb_"+6+"_"+(j+1));} } } if(passValue == 5) { for(var i=0; i<14; i++) { barrers.push("tb_" + 7 + "_" + i); barrers.push("tb_" + (game.sizeX-8) + "_" + (game.sizeY-i-1)); } for(var i=15; i<25; i++) { if(i < 18 || i > 21){ barrers.push("tb_"+10 +"_"+i); barrers.push("tb_"+(game.sizeX-11)+"_"+i); } } for(var i=0; i<9; i++) { barrers.push("tb_" + i + "_" + (game.sizeY-13)); barrers.push("tb_" + (game.sizeX-i-1) + "_" + 12); } for(var i=11; i<19; i++) { if(i < 13 || i > 16){ barrers.push("tb_"+i+"_"+15); barrers.push("tb_"+i+"_"+(game.sizeY-16)); } } } // 设置障碍物颜色 for(var i=0; i<barrers.length; i++) { $(barrers[i]).style.backgroundColor = "#660033"; } } // 开始游戏 function startGame() { food(); game.start = true; $("pass").disabled = true; $("rate").disabled = true; $("start").disabled = true; game.rate = $("rate").value; $(snake[0]).style.backgroundColor = "#0066ff"; $(snake[1]).style.backgroundColor = "blue"; game.time = setTimeout(function(){ snakeMove(); },game.rate); superFood(); } // 蛇移动 速度,方向 function snakeMove() { if(game.direction == "right") {game.y += 1; }// 右移 if(game.direction == "left") {game.y -= 1; }// 左移 if(game.direction == "up") {game.x -= 1; }// 上移 if(game.direction == "down") {game.x += 1; }// 下移 // 判断游戏是否结束 if(result() == true) { clearTimeout(game.time); clearTimeout(appearTime); clearTimeout(disappearTime); game.start = false; var res = "游戏结束!<br/>"; if(game.score > game.record){ res += "恭喜你破纪录啦!<br/>"; $("scoreDiv").innerHTML = "得分:" + game.score + "<br/><br/>最高分:" + game.record; } res += "您的得分为:" + game.score + "<br/>长度:"+game.lengths; $("result").style.visibility = "visible"; $("result").innerHTML = res; return; } if(game.x==game.fx && game.y==game.fy) {eat(1);} if(game.x==superFoodX && game.y==superFoodY) {eat(2);} move(); game.time = setTimeout(function() { snakeMove(game.rate,game.direction); },game.rate); } function move() { $(snake[0]).style.backgroundColor = ""; // 保留蛇当前的位置 for(var i=1; i<snake.length; i++) { snake[i-1] = snake[i]; } snake[snake.length-1] = "tb_" + game.x + "_" + game.y;// 蛇头位置 for(var i=0; i<snake.length-1; i++) { $(snake[i]).style.backgroundColor = "#0066ff"; } $(snake[i]).style.backgroundColor = "blue"; } // 食物 位置随机且不能与蛇位置重复 function food() { game.fx = Math.floor(Math.random(game.sizeX)*game.sizeX); game.fy = Math.floor(Math.random(game.sizeY)*game.sizeY); if($("tb_" + game.fx + "_" + game.fy).style.backgroundColor != "") {food(); } else {$("tb_" + game.fx + "_" + game.fy).style.backgroundColor = "#ff3300"; } } // 超级食物出现 function superFood() { appearTime = setTimeout(function(){ var n = Math.floor(Math.random(10)*10); superFoodX = Math.floor(Math.random(game.sizeX)*game.sizeX); superFoodY = Math.floor(Math.random(game.sizeY)*game.sizeY); if($("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor != "") {superFood(); } else{ if(("tb_" + superFoodX + "_" + superFoodY) == ("tb_" + game.fx + "_" + game.fy)){superFood(); return ;} if(n < 3){$("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = "#33cc33"; color = 0;} else if(3 <= n && n < 5){$("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = "#006600"; color = 1;} else if(5 <= n && n < 7){$("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = "#99ff66"; color = 2;} else if(7 <= n && n < 9){$("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = "#000000"; color = 3;} else {$("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = "#ffff00"; color = 4;} clearTimeout(disappearTime); superFood(); // 一定时间后超级食物消失 disappearTime = setTimeout(function(){ $("tb_" + superFoodX + "_" + superFoodY).style.backgroundColor = ""; superFoodX = superFoodY = null; },game.rate*120-game.rate*50); } },game.rate*120); } // 蛇吃食物 function eat(ef) { // 吃普通食物 if(ef == 1) {snake.push("tb_" + game.fx + "_" + game.fy); game.score += 10; food();} // 吃超级食物 if(ef == 2) { if(color == 0) {game.score += 30;} else if(color == 1) {game.score -= 30;} else if(color == 2) {game.score += 50;} else if(color == 3) {game.score -= 50;} else {game.score += 100;} snake.push("tb_" + superFoodX + "_" + superFoodY); if(game.score < 0) {game.score = 0;} clearTimeout(disappearTime); } game.lengths += 1; $("scoreDiv").innerHTML = "得分:"+game.score+"<br/><br/>长度:"+game.lengths+"<br/><br/>最高分:"+game.record; } // 游戏结束 function result() { var next = "tb_" + game.x + "_" + game.y; if(game.x<0 || game.x>=game.sizeX || game.y<0 || game.y>=game.sizeY) {return true;} for(var i=0; i<snake.length; i++) {if(next == snake[i]) {return true;}} for(var i=0; i<barrers.length; i++) {if(next == barrers[i]) {return true;}} return false; } // 键盘控制 function control(v) { var evt = v; game.dir = game.direction; if(37 <= evt.keyCode && evt.keyCode <= 40 && game.start){ if(evt.keyCode == 37){game.dir=="right" ? game.direction = "right":game.direction = "left";}// 左 if(evt.keyCode == 38){game.dir=="down" ? game.direction = "down" : game.direction = "up";}// 上 if(evt.keyCode == 39){game.dir=="left" ? game.direction = "left" : game.direction = "right";}// 右 if(evt.keyCode == 40){game.dir=="up" ? game.direction = "up" : game.direction = "down";}// 下 if(game.dir != game.direction){clearTimeout(game.time); snakeMove(); } } } </script> </head> <body onload="init();" onkeyup="control(event);" > <div class="backDiv"> <div class="display"> <div id="scoreDiv">分数:0</div><br/> <div id="prompt"></div> </div> <div id="gamePanel" ></div><br/> <div id="operator"> 选择关卡:<select id="pass" onchange="barrer();"> <option value="1">关卡1</option> <option value="2">关卡2</option> <option value="3">关卡3</option> <option value="4">关卡4</option> <option value="5">关卡5</option> </select>   速度:<select id="rate"> <option value="500">慢</option> <option value="250">中</option> <option value="100">快</option> </select>   <input type="button" id="start" value="开始" onclick="startGame();" />   <input type="button" id="resets" value="重玩" onclick="init();" /> </div> <div id="result"></div> </div> </body> </html>
文章中的主体部分都添加了注释,不懂的地方可以评论在下方,博主常在,欢迎探讨学习,能帮到路过的你,我非常荣幸 如果你觉得我的代码还算有趣,在你的学习中能有所帮助,请查看我的置顶文章,我由衷感谢!
推荐阅读: -
js 贪吃蛇完美版
2018-09-06 20:29:25一 款 超 好 玩 的 纯 手 工 版 的 贪 吃 蛇 游 戏 感 觉 还 行,可 以 下 载 参 考 -
js贪吃蛇特效zip
2019-07-03 13:28:59js贪吃蛇特效是一款用原生js写的一个简单的贪吃蛇游戏代码。 js贪吃蛇特效演示图: -
javascript贪吃蛇完整版(源码)
2020-10-26 13:43:24主要是对javascript贪吃蛇完整版(源码)进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 -
js 贪吃蛇 代码源文件
2017-09-09 11:11:49使用canvas 和js实现的不到100行的代码。 学习js时候个人做的贪吃蛇的小游戏,可以供大家参考。 。。。。。。。。。。。。。。 -
js贪吃蛇源码下载
2018-12-19 21:38:07js贪吃蛇源码下载 -
Phaser.js 贪吃蛇游戏
2018-06-26 20:02:52Phaser的伟大的开源游戏开发框架,使用HTML5创建游戏。这是一款利用Phaser.js制作仿Nokia经典的贪吃蛇游戏。 -
js写贪吃蛇
2018-11-14 09:30:30js写贪吃蛇,纯js和html 写的绝对可以用 用起来很方便 -
原生JS贪吃蛇游戏实战开发
2021-06-10 00:55:32使用原生JS完成贪吃蛇游戏开发 -
JavaScript贪吃蛇案例
2018-11-26 14:34:45一个简单js代码,, -
原生js贪吃蛇小游戏代码
2018-11-07 16:33:00原生js贪吃蛇小游戏代码 -
JavaScript实现贪吃蛇小游戏
2022-03-20 09:56:121、贪吃蛇的基本功能 2、统计得分 3、开始与暂停 4、选择难度等级 5、设置快捷键 5.1、通过IJKL,WSAD也能实现方向的切换 5.2、 通过“P” 表示暂停,“C”表示开始或继续,"R"表示重新开始 实现步骤思路: ...效果图:
需求:
1、贪吃蛇的基本功能
2、统计得分
3、开始与暂停
4、选择难度等级
5、设置快捷键
5.1、通过IJKL,WSAD也能实现方向的切换
5.2、 通过“P” 表示暂停,“C”表示开始或继续,"R"表示重新开始
实现步骤思路:
1、画一个蛇的移动区域
2、画一条蛇
3、画食物
4、让蛇动起来
5、设定游戏规则
6、设置难度等级
7、设置开始与暂停
8、设置游戏结束后续操作
9、实现人机交互页面
具体实现代码如下:
1、画蛇的活动区域
首先我们画蛇的活动区域,我们采用JavaScript的canvas进行绘制,我们用一个400 × 400 的区域作为蛇的活动区域<canvas id="canvas" width="400" height="400"></canvas>
同时通过CSS设置一个边界线
#canvas { border: 1px solid #000000; /* 设置边框线 */ }
2、画蛇和食物
效果如下:
在画蛇前我们需要想下蛇的数据结构,在这里我们采取最简单的队列表示蛇,队首表示蛇头位置,队尾表示蛇尾位置,我们将之前画的400 × 400 区域划分为400个20 × 20 的方块,用这些方块组成蛇,那么蛇所在方块的位置的取值范围就是0~399
举个例子:
var snake=[42,41,40];
上述代码表示蛇所在的位置为42,41,40三个方块,其中蛇头为42,蛇尾为40,对于食物,我们可以用一个变量
food
存储食物的位置即可,食物的取值范围为0~399,且不包括蛇的部分,由于游戏中需要随机产生食物,随机函数实现如下:// 产生min~max的随机整数,用于随机产生食物的位置 function random(min, max) { const num = Math.floor(Math.random() * (max - min)) + min; return num; }
当食物被蛇吃掉后就需要重新刷新食物,由于食物不能出现在蛇所在的位置,我们用一个while循环,当食物的位置不在蛇的数组中则跳出循环
while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部
接下来通过canvas进行绘制,首先在JS中获取canvas组件
const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d");
然后写绘制函数用于绘制方格,绘制方格的时候注意我们预留1px作为边框,即我们所画的方格的边长为18,我们实际填充的是18 × 18 的方块,方块的x、y坐标(方块的左上角)的计算也需要注意加上1px
注:canvas的原点坐标在左上角,往右为x轴正方向,往下为y轴正方向
// 用于绘制蛇或者是食物代表的方块,seat为方块位置,取值为0~399,color为颜色 function draw(seat, color) { ctx.fillStyle = color; // 填充颜色 // fillRect的四个参数分别表示要绘制方块的x坐标,y坐标,长,宽,这里为了美观留了1px用于边框 ctx.fillRect( (seat % 20) * 20 + 1, Math.floor(seat / 20) * 20 + 1, 18, 18 ); }
让蛇动起来
首先要规定蛇运动的方向,我们用一个变量direction来表示蛇运动的方向,其取值范围为{1,-1,20,-20},1 表示向右,-1 表示向左,20 表示向下,-20 表示向上,运动时只需要将蛇头的位置加上direction就可以表示新蛇头的位置,这样我们就可以表示蛇的运动了。那么如何让蛇动起来呢,对于蛇的每次移动,我们需要完成下面几个基本操作:
1、将蛇运动的下一个位置变成新蛇头
1.1、将下一个位置加入蛇队列
1.2、绘制下一个方块为浅蓝色
2、把旧蛇头变成蛇身
2.1、绘制旧蛇头为浅灰色
3、删除旧蛇尾
3.1、将旧蛇尾弹出蛇队列
3.2、绘制旧蛇尾位置为白色
当蛇吃掉食物时(蛇的下一个位置为食物所在位置)则需更新食物的位置,并绘制新食物为黄色,此时则不需要删除旧蛇尾,这样可以实现蛇吃完食物后长度的增加功能我们需要写一个函数实现上述操作,并且要不断地调用这个函数,从而实现页面的刷新,即我们所说的动态效果
n = snake[0] + direction; // 找到新蛇头坐标 snake.unshift(n); // 添加新蛇头 draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色 draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色) if (n == food) { while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部 draw(food, "Yellow"); // 绘制食物 } else { draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色 }
接下来,我们需要实现通过键盘控制蛇的运动
我们需要获取键盘的key值,然后通过一个监听函数去监听键盘按下的操作,我们这里通过上下左右键(还拓展了WSAD键和IJKL键控制上下左右方向),同时设置一个变量
n
表示下一步的方向// 用于绑定键盘上下左右事件,上下左右方向键,代表上下左右方向 document.onkeydown = function (event) { const keycode = event.keyCode; if (keycode <= 40) { // 上 38 下 40 左 37 右 39 n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode为其他值,即表示按了没用的键,则方向不变 } else if (keycode <= 76 && keycode >= 73) { // i 73 j 74 k 75 l 76 n = [-20, -1, 20, 1][keycode - 73] || direction; } else { switch (keycode) { case 87: //w n = -20; break; case 83: //s n = 20; break; case 65: //a n = -1; break; case 68: //d n = 1; break; default: n = direction; } } direction = snake[1] - snake[0] == n ? direction : n; // 若方向与原方向相反,则方向不变 };
设定游戏规则
贪吃蛇的最基础的游戏规则如下:
- 蛇如果撞到墙或者蛇的身体或尾巴则游戏结束
- 蛇如果吃掉食物则蛇的长度会增加(上一步已经实现)且得分会增加
先实现第一个,具体如下:
注:下面的一段代码中的n即为新蛇头的位置
// 判断蛇头是否撞到自己或者是否超出边界 if ( snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || (direction == 1 && n % 20 == 0) || (direction == -1 && n % 20 == 19) ) { game_over(); }
接下来我们实现得分统计,对于得分的计算我们只需要设置一个变量
score
,用于统计得分,然后每吃一个食物,该变量加一,然后将得分信息更新到网页相应位置score = score + 1; score_cal.innerText = "目前得分: " + score; // 更新得分
设置难度等级
我们在网页上设置一个单选框,用于设置难度等级
<form action="" id="mode_form"> 难度等级: <input type="radio" name="mode" id="simply" value="simply" checked /> <label for="simply">简单</label> <input type="radio" name="mode" id="middle" value="middle" /> <label for="middle">中级</label> <input type="radio" name="mode" id="hard" value="hard" /> <label for="hard">困难</label> </form>
效果如下:
那么后台具体如何设置难度等级的功能呢?我们采取调用蛇运动的函数的时间间隔来代替难度,时间间隔越小则难度越大,我们分三级:简单、中级、困难,创建一个时间间隔变量time_internal,然后用一个函数获取单选框的取值,并将相应模式的时间间隔赋值给time_internal
// 用刷新间隔代表蛇的速度,刷新间隔越长,则蛇的速度越慢 const simply_mode = 200; const middle_mode = 100; const hard_mode = 50; var time_internal = simply_mode; // 刷新时间间隔,用于调整蛇的速度,默认为简单模式 // 同步难度等级 function syncMode() { var mode_value = ""; for (var i = 0; i < mode_item.length; i++) { if (mode_item[i].checked) { mode_value = mode_item[i].value; } } switch (mode_value) { case "simply": time_internal = simply_mode; break; case "middle": time_internal = middle_mode; break; case "hard": time_internal = hard_mode; break; } }
最后只需要在蛇每次移动前调用一次上述函数
syncMode()
就可以实现难度切换,至于蛇的速度的具体调节且看下面如何讲解设置开始与暂停
如何实现蛇的移动动态效果,如何暂停,如何继续,速度如何调节,这就涉及到JavaScript的动画的部分其实现动态效果的方法为设置一个立即执行函数!function() {}();,然后在该函数中使用setTimeout(arguments.callee, 150);,每隔0.15秒调用此函数,从而实现了网页的不断刷新,也就是所谓的动态效果。在requestAnimationFrame方法可以实现动画效果,实现难度调节,蛇的速度var requestID; // 用于标记请求ID与取消动画 function updateProgress() { // do something... requestID = requestAnimationFrame(updateProgress); // 调用后在函数中反复调用该函数 } id = requestAnimationFrame(updateProgress); // 第一次调用(即开始动画) cancelAnimationFrame(requestID); // 取消动画
requestAnimationFrame()已经解决了浏览器不知道 JavaScript 动画何时开始的问题, 以及最佳间隔是多少的问题。······
传给 requestAnimationFrame()的函数实际上可以接收一个参数,此参数是一个 DOMHighResTimeStamp 的实例(比如 performance.now()返回的值),表示下次重绘的时间。这一点非常重要: requestAnimationFrame()实际上把重绘任务安排在了未来一个已知的时间点上,而且通过这个参数 告诉了开发者。基于这个参数,就可以更好地决定如何调优动画了。
requestAnimationFrame()返回一个请求 ID,可以用于通过另一个 方法 cancelAnimationFrame()来取消重绘任务
如何控制时间间隔的方法:
配合使用一个计时器来限制重绘操作执行的频率。这样,计时器可以限制实际的操作执行间隔,而 requestAnimationFrame 控制在浏览器的哪个渲染周期中执行。下面的例子可以将回调限制为不超过 50 毫秒执行一次
具体实现:
let enabled = true; function expensiveOperation() { console.log('Invoked at', Date.now()); } window.addEventListener('scroll', () => { if (enabled) { enabled = false; requestAnimationFrame(expensiveOperation); setTimeout(() => enabled = true, 50); } });
根据上面的内容,可以设置一个控制函数,用于控制隔一定的时间调用一次蛇运动的函数,实现如下
// 控制游戏的刷新频率,每隔time_internal时间间隔刷新一次 function game_control(){ if(enabled){ enabled = false; requestAnimationFrame(run_game); setTimeout(() => enabled = true, time_internal); } run_id = requestAnimationFrame(game_control); } // 启动或继续游戏 function run_game() { syncMode(); // 同步难度等级 n = snake[0] + direction; // 找到新蛇头坐标 snake.unshift(n); // 添加新蛇头 // 判断蛇头是否撞到自己或者是否超出边界 if ( snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || (direction == 1 && n % 20 == 0) || (direction == -1 && n % 20 == 19) ) { game_over(); } draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色 draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色) if (n == food) { score = score + 1; score_cal.innerText = "目前得分: " + score; // 更新得分 while (snake.indexOf((food = random(0, 400))) >= 0); // 重新刷新食物,注意食物应不在蛇内部 draw(food, "Yellow"); // 绘制食物 } else { draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色 } // setTimeout(arguments.callee, time_internal); //之前的方案,无法实现暂停和游戏的继续 }
至于暂停只需要在特定的位置调用
cancelAnimationFrame(run_id);
就可以了游戏结束后出现一个“弹窗”,显示最终得分和是否再来一把
效果如下:
我们实现网页的弹窗,通过调研发现JavaScript的弹窗可以通过alert()的方法实现,不过在网页上直接弹窗感觉不太美观,而且影响体验,于是我想了一下,可以采用一个div标签实现伪弹窗,在需要显示的时候设置其display属性为block,不需要显示的时候设置其display属性为none,就类似于Photoshop里面的图层概念,这样我们就可以在平常的时候设置其display属性为none触发game over时设置其display属性为block,实现如下:
<div id="game_over"> <h3 id="game_over_text" align="center">游戏结束!</h3> <h3 id="game_over_score" align="center">您的最终得分为: 0分</h3> <button id="once_again">再来一把</button> <button id="cancel">取消</button> </div>
CSS样式如下:
#game_over { display: none; /* 设置game over 窗口不可见 */ position: fixed; top: 190px; left: 65px; width: 280px; height: 160px; background-color: aliceblue; border-radius: 5px; border: 1px solid #000; /* 设置边框线 */ } #once_again { position: relative; left: 20px; } #cancel { position: relative; left: 50px; }
我们需要实现game over的后续操作:暂停动画,显示得分,显示“弹窗”
function game_over(){ cancelAnimationFrame(run_id); game_over_score.innerText = "您的最终得分为: " + score + "分"; game_over_div.style.display = "block"; }
实现人机交互页面
接下来的部分就是提高用户体验的部分,具体实现下列功能/操作游戏说明
人机交互按钮:开始/继续,暂停,重新开始
快捷键
由于在游戏过程中通过鼠标移动到暂停键暂停,时间上太慢,可能造成游戏终止,故应该设置开始/继续(C)、暂停(P)、重新开始(R)的快捷键
有些电脑键盘的上下左右键较小,操作起来不太方便,可以添加WSAD或者IJKL扩展,用于控制上下左右方向
效果如下:界面代码下文末,这里讲解下绑定按键点击事件与绑定快捷键
首先看下绑定按键点击事件,点击”开始/继续“只需要调用requestAnimationFrame(game_control);,点击”暂停“只需要调用cancelAnimationFrame(run_id);
// 绑定开始按钮点击事件 start_btn.onclick = function () { run_id = requestAnimationFrame(game_control); }; // 绑定暂停按钮点击事件 pause_btn.onclick = function () { cancelAnimationFrame(run_id); };
点击“重新开始”的话,则需要先暂停动画,然后删除画面上的蛇和食物,初始化所有设置,然后再调用
requestAnimationFrame(game_control);
开始游戏注:初始化时需要初始化得分与难度等级,这里解释下为什么要将第一个食物设置为蛇头下一个位置,因为这样的话蛇会自动先吃一个食物,继而可以通过“开始 / 继续” 一个按钮实现开始和继续操作,同时run_game()函数中的食物绘制是在蛇吃到食物之后,保证第一个食物顺利绘制,这样的话score就需要初始化为-1
// 用于初始化游戏各项参数 function init_game() { snake = [41, 40]; direction = 1; food = 42; score = -1; time_internal = simply_mode; enabled = true; score_cal.innerText = "目前得分: 0分"; // 更新得分 mode_item[0].checked = true; // 重置难度等级为简单 } // 绑定重新开始按钮点击事件 restart_btn.onclick = function () { cancelAnimationFrame(run_id); // 将原有的食物和蛇的方块都绘制成白色 for(var i = 0; i < snake.length; i++){ draw(snake[i], "White"); } draw(food, "White"); // 初始化游戏各项参数 init_game(); run_id = requestAnimationFrame(game_control); };
我们绑定game over中的两个按键”再来一把“和”取消“,”再来一把“只需要完成“重新开始”里面的事件即可,”取消“只需要完成”重新开始“点击操作中除了开始游戏的部分,即除了run_id = requestAnimationFrame(game_control);这两个按钮都需要设置”弹窗“的display属性为none
具体实现如下:
// 绑定游戏结束时的取消按钮点击事件 cancel_btn.onclick = function () { for(var i = 0; i < snake.length; i++){ draw(snake[i], "White"); } draw(food, "White"); init_game(); game_over_div.style.display = "none"; } // 绑定游戏结束时的再来一把按钮点击事件 once_again_btn.onclick = function () { for(var i = 0; i < snake.length; i++){ draw(snake[i], "White"); } draw(food, "White"); init_game(); game_over_div.style.display = "none"; run_id = requestAnimationFrame(game_control); }
如何设置快捷键,快捷键只需要用JavaScript模拟点击对应的按钮即可,实现如下:
// 同时绑定R 重启,P 暂停,C 继续 document.onkeydown = function (event) { const keycode = event.keyCode; if(keycode == 82){ // R 重启 restart_btn.onclick(); } else if(keycode == 80){ // P 暂停 pause_btn.onclick(); } else if(keycode == 67){ // C 继续 start_btn.onclick(); } };
项目源码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>贪吃蛇小游戏</title> <style> button { width: 100px; height: 40px; font-weight: bold; } #game_title { margin-left: 95px; } #canvas { border: 1px solid #000000; /* 设置边框线 */ } #score { font-weight: bold; } #mode_form { font-weight: bold; } #game_over { display: none; /* 设置game over 窗口不可见 */ position: fixed; top: 190px; left: 65px; width: 280px; height: 160px; background-color: aliceblue; border-radius: 5px; border: 1px solid #000; /* 设置边框线 */ } #once_again { position: relative; left: 20px; } #cancel { position: relative; left: 50px; } </style> </head> <body> <h1 id="game_title">贪吃蛇小游戏</h1> <canvas id="canvas" width="400" height="400"></canvas> <div id="game_over"> <h3 id="game_over_text" align="center">游戏结束!</h3> <h3 id="game_over_score" align="center">您的最终得分为: 0分</h3> <button id="once_again">再来一把</button> <button id="cancel">取消</button> </div> <br> <div id="game_info"> <p><b>游戏说明:</b></p> <p> <b>1</b>. 用键盘上下左右键(或者IJKL键,或者WSAD键)控制蛇的方向,寻找吃的东西 <br><b>2</b>. 每吃一口就能得到一定的积分,同时蛇的身子会越吃越长 <br><b>3</b>. 不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴 <br><b>4</b>. 在下方单选框中选择难度等级,点击"<b>开始 / 继续</b>"即开始游戏,点击"<b>暂停</b>"则暂停游戏, <br> 再点击"<b>开始 / 继续</b>"继续游戏,点击"重新开始"则重新开始游戏 <br><b>5</b>. <b>快捷键</b>: "<b>C</b>"表示开始或继续,"<b>P</b>"表示暂停,"<b>R</b>"表示重新开始 </p> </div> <p id="score">目前得分: 0分</p> <form action="" id="mode_form"> 难度等级: <input type="radio" name="mode" id="simply" value="simply" checked/> <label for="simply">简单</label> <input type="radio" name="mode" id="middle" value="middle"/> <label for="middle">中级</label> <input type="radio" name="mode" id="hard" value="hard"/> <label for="hard">困难</label> </form> <br/> <button id="startButton">开始 / 继续</button> <button id="pauseButton">暂停</button> <button id="restartButton">重新开始</button> <script> const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const start_btn = document.getElementById("startButton"); const pause_btn = document.getElementById("pauseButton"); const restart_btn = document.getElementById("restartButton"); const once_again_btn = document.getElementById("once_again"); const cancel_btn = document.getElementById("cancel"); const game_over_div = document.getElementById("game_over"); const game_over_score = document.getElementById("game_over_score"); const score_cal = document.getElementById("score"); const mode_item = document.getElementsByName("mode"); // 用刷新间隔代表蛇的速度,刷新间隔越长,则蛇的速度越慢 const simply_mode = 200; const middle_mode = 100; const hard_mode = 50; //注意要改为var const是不会修改的 let snake = [41, 40]; // 蛇身体队列 let direction = 1; // 方向:1为向右,-1为向左,20为向下,-20为向上 let food = 42; // 食物位置,取值为0~399 let n; // 蛇的下一步的方向(由键盘和蛇的原方向决定) let score = -1; // 得分 let time_internal = simply_mode; // 刷新时间间隔,用于调整蛇的速度,默认为简单模式 let enabled = true; // 用于控制是否刷新,实现通过一定频率刷新 let run_id; // 请求ID,用于暂停功能 // 产生min~max的随机整数,用于随机产生食物的位置 function random(min, max) { return Math.floor(Math.random() * (max - min)) + min; } // 用于绘制蛇或者是食物代表的方块,seat为方块位置,取值为0~399,color为颜色 function draw(seat, color) { ctx.fillStyle = color; // 填充颜色 // fillRect的四个参数分别表示要绘制方块的x坐标,y坐标,长,宽,这里为了美观留了1px用于边框 ctx.fillRect( (seat % 20) * 20 + 1, Math.floor(seat / 20) * 20 + 1, 18, 18 ); } // 同步难度等级 function syncMode() { let mode_value = ""; for (let i = 0; i < mode_item.length; i++) { if (mode_item[i].checked) { mode_value = mode_item[i].value;//原来是mode_item.value } } switch (mode_value) { case "simply": time_internal = simply_mode; break; case "middle": time_internal = middle_mode; break; case "hard": time_internal = hard_mode; break; } } // 用于绑定键盘上下左右事件,我设置了wsad,或者ijkl,或者上下左右方向键,代表上下左右方向 // 同时绑定R 重启,P 暂停,C 继续,注意:若是这几个键则不需要更新direction的值,操作结束后直接返回即可 document.onkeydown = function (event) { const keycode = event.keyCode; if (keycode === 82) { // R 重启 restart_btn.onclick(); return; } else if (keycode === 80) { // P 暂停 pause_btn.onclick(); return; } else if (keycode === 67) { // C 继续 start_btn.onclick(); return; } else if (keycode <= 40) { // 上 38 下 40 左 37 右 39 n = [-1, -20, 1, 20][keycode - 37] || direction; // 若keycode为其他值,则方向不变 } else if (keycode <= 76 && keycode >= 73) { // i 73 j 74 k 75 l 76 n = [-20, -1, 20, 1][keycode - 73] || direction; } else { switch (keycode) { case 87: //w n = -20; break; case 83: //s n = 20; break; case 65: //a n = -1; break; case 68: //d n = 1; break; default: n = direction; } } direction = snake[1] - snake[0] === n ? direction : n; // 若方向与原方向相反,则方向不变 }; // 用于初始化游戏各项参数 function init_game() { snake = [41, 40]; direction = 1; food = 42; score = -1; time_internal = simply_mode; enabled = true; score_cal.innerText = "目前得分: 0分"; // 更新得分 mode_item[0].checked = true; // 重置难度等级为简单 } function game_over() { cancelAnimationFrame(run_id); game_over_score.innerText = "您的最终得分为: " + score + "分"; game_over_div.style.display = "block"; } // 启动或继续游戏 function run_game() { syncMode(); // 同步难度等级 n = snake[0] + direction; // 找到新蛇头坐标 snake.unshift(n); // 添加新蛇头 // 判断蛇头是否撞到自己或者是否超出边界 if ( snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || (direction === 1 && n % 20 === 0) || (direction === -1 && n % 20 === 19) ) { game_over(); } draw(n, "#1a8dcc"); // 绘制新蛇头为浅蓝色 draw(snake[1], "#cececc"); // 将原来的蛇头(浅蓝色)变成蛇身(浅灰色) if (n === food) { score = score + 1; score_cal.innerText = "目前得分: " + score; // 更新得分 while (snake.indexOf((food = random(0, 400))) >= 0) ; // 重新刷新食物,注意食物应不在蛇内部 draw(food, "Yellow"); // 绘制食物 } else { draw(snake.pop(), "White"); // 将原来的蛇尾绘制成白色 } // setTimeout(arguments.callee, time_internal); //之前的方案,无法实现暂停和游戏的继续 } // 控制游戏的刷新频率,每隔time_internal时间间隔刷新一次 function game_control() { if (enabled) { enabled = false; requestAnimationFrame(run_game); setTimeout(() => enabled = true, time_internal); } run_id = requestAnimationFrame(game_control); } // 绑定开始按钮点击事件 start_btn["onclick"] = function () { run_id = requestAnimationFrame(game_control); }; // 绑定暂停按钮点击事件 pause_btn.onclick = function () { cancelAnimationFrame(run_id); }; // 绑定重新开始按钮点击事件 restart_btn.onclick = function () { cancelAnimationFrame(run_id); // 将原有的食物和蛇的方块都绘制成白色 for (let i = 0; i < snake.length; i++) { draw(snake[i], "White"); } draw(food, "White"); // 初始化游戏各项参数 init_game(); run_id = requestAnimationFrame(game_control); }; // 绑定游戏结束时的取消按钮点击事件 cancel_btn.onclick = function () { for (let i = 0; i < snake.length; i++) { draw(snake[i], "White"); } draw(food, "White"); init_game(); game_over_div.style.display = "none"; } // 绑定游戏结束时的再来一把按钮点击事件 once_again_btn.onclick = function () { for (let i = 0; i < snake.length; i++) { draw(snake[i], "White"); } draw(food, "White"); init_game(); game_over_div.style.display = "none"; run_id = requestAnimationFrame(game_control); } </script> </body> </html>
-
javascript 贪吃蛇实现代码
2020-12-10 11:18:19在习作的过程中尝试着贪吃蛇游戏用JS实现了。竟然成功了。 思路:使用10px*10px的div层担当“像素”,然后使用40*40矩阵160个“像素”构成了游戏的界面。 下面是代码: 代码如下:// JavaScript Document alert(...