JS学习笔记之贪吃蛇小游戏demo实例详解


Posted in Javascript onMay 29, 2019

本文实例讲述了JS学习笔记之贪吃蛇小游戏demo实例。分享给大家供大家参考,具体如下:

最近跟着视频教程打了一个贪吃蛇,

来记录一下实现思路,

先上代码
静态页

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>贪吃蛇</title>
</head>
<style>
*{
  margin: 0;
  padding: 0;
}
  .map{
    width:800px;
    height: 600px;
    background-color: #ccc;
    position:relative;
  }

</style>
<body>
<!-- 画出地图,设置样式 -->
 <div class="map">

 </div>
</body>
<script src="food.js"></script>
<script src="snake.js"></script>
<script src="game.js"></script>
</html>

food.js

//食物就是一个对象,宽高横纵坐标,先定义构造函数,然后创建对象
(function (){
  var elements=[];//用来保存每个小方块食物
  function Food(x,y,width,height,color){
    //横纵坐标
    this.x=x||0;
    this.y=y||0;
    this.width=width||20;
    this.height=height||20;
    //背景颜色
    this.color=color||"green";
  }
  //为原型添加初始化的方法(作用:在页面上取显示这个食物)
  //因为食物要在地图上显示,所以,需要地图这个参数
  Food.prototype.init=function(map){
    //先删除这个食物
    //外部无法访问的函数
    remove()
    var div=document.createElement("div");
    //把这个div加到map中
    map.appendChild(div);
    //设置div的样式
    div.style.width=this.width+"px";
    div.style.height=this.height+"px";
    div.style.backgroundColor=this.color;
    // div.style.left=this.x+"px";

    //先脱离文档流
    div.style.position="absolute";
    //随机横纵坐标
    this.x=parseInt(Math.random()*(map.offsetWidth/this.width))*this.width;
    this.y=parseInt(Math.random()*(map.offsetHeight/this.height))*this.height;
    div.style.left=this.x+"px";
    div.style.top=this.y+"px";
    // Food.prototype.init=function(map){

    // }
    //把div加入到数组elements中
    elements.push(div);
  }

  function remove(){
    //elements数组中有这个食物
    for(var i=0;i<elements.length;i++){
      var ele=elements[i]
      //找到这个子元素的父级元素,然后删除这个子元素
      ele.parentNode.removeChild(ele);
      //再次把elements中的这个子元素也要删除
      elements.splice(i,1)
    }
  }
  //把Food暴露给Window,外部可以使用
  window.Food=Food;
}());

snake.js

//蛇
(function(){
  var elements=[];//存放小蛇的每个身体部分
  //蛇的构造函数
  function Snake(width,height,direction){
    //小蛇的每个部分的宽
    this.width=width||20;
    this.height=height||20;
    //身体
    this.body=[
      {x:3,y:2,color:"red"},
      {x:2,y:2,color:"orange"},
      {x:1,y:2,color:"orange"}
    ];

    this.direction=direction||"right";
  }
//蛇的初始化
  Snake.prototype.init=function(map){
    remove()
    //循环遍历创建div
    for(var i=0;i<this.body.length;i++){
      var obj=this.body[i];
      //创建div
      var div=document.createElement("div");
      //把div加入到map地图中
      map.appendChild(div);
      //设置div的样式;
      div.style.position="absolute";
      div.style.width=this.width+"px";
      div.style.height=this.height+"px";
      div.style.left=obj.x*this.width+"px";
      div.style.top=obj.y*this.height+"px";
      div.style.backgroundColor=obj.color;

      //把div加入到elements数组中--目的是删除
      elements.push(div)
    }
  }
  //蛇的移动
  Snake.prototype.move=function(food,map){
    //改变蛇身体位置
    var i=this.body.length-1; //2
    for(;i>0;i--){
      this.body[i].x=this.body[i-1].x;
      this.body[i].y=this.body[i-1].y;
    }
    //判断方向---改变小蛇的头的坐标位置
    switch (this.direction){
      case "right":
        this.body[0].x+=1;
        break;
      case "left":
        this.body[0].x-=1;
        break;
      case "top":
        this.body[0].y-=1;
        break;
      case "bottom":
        this.body[0].y+=1;
        break;
    }

    //判断有没有吃到食物
    //小蛇的头的坐标和食物位置
    var headX=this.body[0].x*this.width;
    var headY=this.body[0].y*this.height;
    //食物的横纵坐标
    var foodX=food.x;
    var foodY=food.y;
    if(headX==foodX&&headY==foodY){
      //获取蛇的最后尾巴
      var last=this.body[this.body.length-1];
      //把最后的蛇尾复制一份
      this.body.push({
        x:last.x,
        y:last.y,
        color:last.color
      })
      //重新初始化食物
      food.init(map);
    }



  }

  //删除小蛇的私有函数
  function remove(){
    //获取数组
    var i=elements.length-1;
    for(;i>=0;i--){
      //先从当前的子元素中找到该子元素的父级元素,然后再弄死这个子元素
      var ele=elements[i];
      //从map地图上删除这个子元素div
      ele.parentNode.removeChild(ele);
      elements.splice(i,1);
    }
  }
  window.Snake=Snake;
}());

//蛇
(function(){
  var elements=[];//存放小蛇的每个身体部分
  //蛇的构造函数
  function Snake(width,height,direction){
    //小蛇的每个部分的宽
    this.width=width||20;
    this.height=height||20;
    //身体
    this.body=[
      {x:3,y:2,color:"red"},
      {x:2,y:2,color:"orange"},
      {x:1,y:2,color:"orange"}
    ];

    this.direction=direction||"right";
  }
//蛇的初始化
  Snake.prototype.init=function(map){
    remove()
    //循环遍历创建div
    for(var i=0;i<this.body.length;i++){
      var obj=this.body[i];
      //创建div
      var div=document.createElement("div");
      //把div加入到map地图中
      map.appendChild(div);
      //设置div的样式;
      div.style.position="absolute";
      div.style.width=this.width+"px";
      div.style.height=this.height+"px";
      div.style.left=obj.x*this.width+"px";
      div.style.top=obj.y*this.height+"px";
      div.style.backgroundColor=obj.color;

      //把div加入到elements数组中--目的是删除
      elements.push(div)
    }
  }
  //蛇的移动
  Snake.prototype.move=function(food,map){
    //改变蛇身体位置
    var i=this.body.length-1; //2
    for(;i>0;i--){
      this.body[i].x=this.body[i-1].x;
      this.body[i].y=this.body[i-1].y;
    }
    //判断方向---改变小蛇的头的坐标位置
    switch (this.direction){
      case "right":
        this.body[0].x+=1;
        break;
      case "left":
        this.body[0].x-=1;
        break;
      case "top":
        this.body[0].y-=1;
        break;
      case "bottom":
        this.body[0].y+=1;
        break;
    }

    //判断有没有吃到食物
    //小蛇的头的坐标和食物位置
    var headX=this.body[0].x*this.width;
    var headY=this.body[0].y*this.height;
    //食物的横纵坐标
    var foodX=food.x;
    var foodY=food.y;
    if(headX==foodX&&headY==foodY){
      //获取蛇的最后尾巴
      var last=this.body[this.body.length-1];
      //把最后的蛇尾复制一份
      this.body.push({
        x:last.x,
        y:last.y,
        color:last.color
      })
      //重新初始化食物
      food.init(map);
    }



  }

  //删除小蛇的私有函数
  function remove(){
    //获取数组
    var i=elements.length-1;
    for(;i>=0;i--){
      //先从当前的子元素中找到该子元素的父级元素,然后再弄死这个子元素
      var ele=elements[i];
      //从map地图上删除这个子元素div
      ele.parentNode.removeChild(ele);
      elements.splice(i,1);
    }
  }
  window.Snake=Snake;
}());

game.js

//游戏
(function(){
   var that=null;
  //游戏的构造函数
  function Game(map){
    this.food=new Food();
    this.snake=new Snake();
    this.map=map;//地图
    that=this;
  }
  Game.prototype.init=function(){
    //初始化游戏
    //食物初始化
    this.food.init(this.map);
    this.snake.init(this.map);

    this.runSnake(this.food,this.map)
    this.bindKey();
  }

  Game.prototype.runSnake=function(food,map){
    //自动的去移动
    var timeId=setInterval(function(){
      //此时的this是window
      //蛇的移动
      this.snake.move(food,map);
      //初始化蛇
      this.snake.init(map);
      //横坐标最大值
      var maxX=map.offsetWidth/this.snake.width;
      //获取纵坐标的最大值
      var maxY=map.offsetHeight/this.snake.height;
      //蛇头的坐标
      var headX=this.snake.body[0].x;
      var headY=this.snake.body[0].y;
      //判断横坐标
      if(headX<0||headX>=maxX){
        clearInterval(timeId)
        alert("游戏结束")
      }
      //判断纵坐标
      if(headY<0||headY>maxY){
        clearInterval(timeId)
        alert("游戏结束")
      }
      console.log(headX)
    }.bind(that),150)
  }

  Game.prototype.bindKey=function(){
    //获取用户的按键,改变小蛇的方向
    document.addEventListener("keydown",function(e){
      //获取案件的值
      switch(e.keyCode){
        case 37:
          this.snake.direction="left";
          break;
        case 38:
          this.snake.direction="top";
          break;
        case 39:
          this.snake.direction="right";
          break;
        case 40:
          this.snake.direction="bottom";
          break;
      }
    }.bind(that),false)
  }


  window.Game=Game;

}());

//初始化游戏对象
var gm=new Game(document.querySelector(".map"));
gm.init()

这里加一个小插曲,关于匿名函数自调用的三种写法

第一种

JS学习笔记之贪吃蛇小游戏demo实例详解

第二种

JS学习笔记之贪吃蛇小游戏demo实例详解

第三种

JS学习笔记之贪吃蛇小游戏demo实例详解

注意!注意! 注意! 匿名函数的最后不要忘记加封号;  因为如果忘了加,系统很容易与后面的代码混淆 造成各种很奇葩的报错;

这里我推荐第三种写法,比较清晰明了

好,代码贴完了,我们来分析一下实现思路

首先 第一步

建立一个画布

设置画布的宽度为800px   高度为600px  因为小蛇 需要在画布内任意移动,需要脱离标准文档流,所以需要设置绝对定位, 因此我给画布添加了position:relative;  ,再给背景添加一个颜色 ,灰色#ccc

好,画布创建好了,我们可以开始编写逻辑代码了

Food.js 代码分析

首先我们需要创建一个贪吃蛇 吃的“食物”,因此我们需要创建一个食物的对象,这里我在food.js中创建了一个自定义构造函数

定义了 “食物Food”的 x值、y值、宽度、高度、颜色

JS学习笔记之贪吃蛇小游戏demo实例详解

这里我利用 || 运算设置了默认值,如果 || 左边为false 则会自动取右边的值,所以当实例化对象时若未传参时 自动取 “||” 运算符右边的值

然后在"Food"的原型上定义了一个 init 初始化方法

首先创建一个div ,并将此对象保存在 div变量中

然后 在地图中 添加上这个 div  再逐步给这个 div元素 加上他的宽度、高度、背景颜色、并且设置绝对定位

那我们怎么定位呢?

这里我们可以把整个地图看成是一个坐标系,把地图的宽度除以 “食物”的宽度 来切分这个地图 ,x=1则相当于1个“食物宽度”的单位长,x=3 则相当于3个“食物宽度的单位长”

高度同理

这里我取了随机数  乘以  地图被切分的总份数   这样就会的到 随机的 X和Y   然后乘以 宽度和高度 就的到了不会超出地图的随机坐标 ,举例 :  因为Math.random(0,5)  是不包括5的

JS学习笔记之贪吃蛇小游戏demo实例详解

因为“食物”是会被贪吃蛇 “吃”掉的

所以我们必须创建一个方法来“消灭”这个“食物”,因此我定义了一个 remove 函数

并且 上方创建了一个 数组elements用于存放 创建出来的 "食物" 这个div元素 的对象,方便用来删除,每次初始化“食物” 时,将对象追加入elements 数组 

JS学习笔记之贪吃蛇小游戏demo实例详解

我们遍历 elements 数组, 通过数组中每个div对象 先找到其父级,然后通过removeChild 方法将其自身删除JS学习笔记之贪吃蛇小游戏demo实例详解

因为有保存在elements 数组中,那我们想要删除“食物就很方便了”,每次初始化之前我们秩序要调用一次 remove函数就是实现了“消灭”食物,然后再生成新的食物

因为此处的所有函数都写在了一个 自调用的匿名函数中,所以内部的Food 对象,在外部是访问不到的,

那怎么办呢?

这里我调用了window 对象,将配置好的Food 对象暴露给window ,这样,我们再其他的地方有需要时也可以实例化 Food了 

Snake.js 代码分析

其实 “贪吃蛇”身体的实现和 “食物”的实现原理大体相同 ,首先我也同样建立了一个elements数组,用于存放之后小蛇移动时所产生的旧的“身体”,用于删除,因为都是局部变量,所以虽然两个数组名字相同,但不会冲突

这里我也给小蛇设置了宽、高,宽高我设定为默认和食物相同,并且还设置了方向direction 这用来控制小蛇的移动方向,这里我默认给了“right”向右移动,

并且,因为当游戏开始时,小蛇必当有一个初始的长度,我给了它一个脑袋 二节身体,脑袋设置成了红色,方便识别

所以我之后如果需要让小蛇增加长度,体现越吃越长的感觉 ,只需要在 body这个数组中追加对象就可以了

JS学习笔记之贪吃蛇小游戏demo实例详解

好,小蛇的基本属性配置完了,我们下一步就是要初始化小蛇 

同样的,上方也提到了,我创建过一个elements数组,用于存放“小蛇”的旧身体,所以在初始化之前,我们需要调用remove函数,遍历elements数组,和删除“食物”一样的方法,将旧的“蛇身”都给删除了

JS学习笔记之贪吃蛇小游戏demo实例详解

执行完删除之后呢,我们就可以专心初始化了,

蛇身这么长,那我们该怎么知道蛇身的每一节到底该在哪里呢

这时就用到了我们上方定义的  body 这个数组了,它存放了小蛇的身体的所有部分

我们只需要遍历它,根据其中每个对象的属性都进行创建新的div对象,同意设置其宽、高、left、top,并且,将创建好的对象又存入elements数组中,方便下一次删除

JS学习笔记之贪吃蛇小游戏demo实例详解

定义完初始化的方法后,我们就得考虑小蛇的移动该怎么实现了,

既然是贪吃蛇游戏,我们肯定需要与玩家互动,让玩家来操控小蛇的走向,

对了,我们自定义构造函数的时候不是设置过一个direction 属性吗,我们就得利用起来,依据此来判断小蛇的走向

至于更改方向,我们放在之后的代码中实现

这里我们定义了一个move 函数 ,并传入两个参数 food "食物对象" 和 map“地图对象”  

那为什么要传呢,

虽然我们这个demo里面只有一条小蛇,但这样的写法保留了同时开启多个游戏的可能性

  首先我们获取 body 数组的长度 存入i 中,然后倒序倒序倒序遍历 i  ,根据 i 作为索引, 从蛇尾巴开始向蛇脑袋遍历,大家想象一下,贪吃蛇的蛇身是不是都是按部就班的沿着脑袋走过的路径走的? 你给它绕个直角、或者正方形它总是老老实实的走完,所以我们每次移动,只需要控制蛇脑袋移动,让蛇身体让它他们挨个获取他们前面那一节身体的坐标就可以了

所以,这里我们倒序遍历,将第 i 节身体赋值前一节身体的 x 属性和 y 属性

蛇身的重新赋值做完了,我们判断一下蛇头的移动方向,因为是固定的4个方向,所以这里使用switch较为方便,

根据 上、下、左、右不一样的情况对 头部的x和y增加或减少

既然是贪吃蛇,我们食物也创建好了,需要实现贪吃蛇吃食物这个过程

首先我们分别计算出 蛇脑袋和食物的X和Y

然后,我们判断一下,

当蛇脑袋的x,y 和食物的x,y都相等的时候,

我们延长一节蛇身,这里我复制了一份最后一节蛇身体 然后追加入body数组,注意!

因为对象是引用类型,所以必须这样拆开赋值

最后,再调用一次食物的初始化,产生新的食物

JS学习笔记之贪吃蛇小游戏demo实例详解

同样的,这里我也将,Snake 对象暴露给window,供下方的Game.js中的代码调用

JS学习笔记之贪吃蛇小游戏demo实例详解

Game.js 代码分析

在Game.js中,开头就定义了that 

用来保存this 的指向,供后面使用

我们分别实例化一个 “食物”对象和一个“贪吃蛇”对象

传入地图对象,并赋值

JS学习笔记之贪吃蛇小游戏demo实例详解

属性设置好了

那既然是游戏那我们是不是应该设定点游戏规则,

当我们的小蛇到达地图边界时,小蛇就会一头撞死了,游戏结束,

并且我们也没有实现小蛇的移动,让我们来接着实现吧

这里我定义了一个runSnake函数,传入 food 和map 对象

首先,定义一个计时器,存入timeId这个变量中

调用一个蛇的 move(移动) 和 init (初始化函数)

在小蛇成功移动之后,我们再判断一下,小蛇是否已经走到边界了,

计算出,地图宽度最多能被蛇头的宽度分为几份,高度同理

取出蛇头自身的x和y

判断 如果蛇头x<0 说明越过左边界,超过maxX则说明超过右边界,

y同理

如果越过边界,则清除定时器,执行一个弹框

注意,我在这个定时器中的方法后加个一个bind 并传入了开始定义的 that ,也就是提前保存的this 指向,如果不加,这里的代码多处用到了this ,因为setInterVal 的指向为window 所以会导致代码出现错误,无法找到这些方法和属性

JS学习笔记之贪吃蛇小游戏demo实例详解

接下来我们再来实现一下如何用键盘控制小蛇的移动

根据keycode 来更改 snake对象的 direction ,

同样的,此处的this 指向也不正确,指向的是 触发该事件的对象,这是无法调用snake对象的,所以我们必须改变它,在bind中传入(that)

然后将Game 对象暴露给 window

JS学习笔记之贪吃蛇小游戏demo实例详解

接着定义初始化游戏的函数

分别调用food对象的初始化函数、小蛇的初始化函数,调用runSnake函数开启定时器让小蛇跑起来

最后绑定上keydown 事件

JS学习笔记之贪吃蛇小游戏demo实例详解

最后的最后

实例化一个Game对象

调用gm 的init  贪吃蛇小demo就实现了

JS学习笔记之贪吃蛇小游戏demo实例详解

效果展示

JS学习笔记之贪吃蛇小游戏demo实例详解

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
让getElementsByName适应IE和firefox的方法
Sep 24 Javascript
javascript获取当前ip的代码
May 10 Javascript
JavaScript中原型和原型链详解
Feb 11 Javascript
基于javascript实现表格的简单操作
May 21 Javascript
浅谈layer的iframe弹窗给里面的标签赋值的问题
Nov 10 Javascript
Node.js常用工具之util模块
Mar 09 Javascript
AngularJS实现进度条功能示例
Jul 05 Javascript
深入理解vue2.0路由如何配置问题
Jul 18 Javascript
深入理解vue.js中$watch的oldvalue与newValue
Aug 07 Javascript
Vue+Mock.js模拟登录和表格的增删改查功能
Jul 26 Javascript
js实现ATM机存取款功能
Oct 27 Javascript
vue权限问题的完美解决方案
May 08 Javascript
elementUI select组件value值注意事项详解
May 29 #Javascript
elementUI select组件使用及注意事项详解
May 29 #Javascript
通过vue手动封装on、emit、off的代码详解
May 29 #Javascript
el-select数据过多懒加载的解决(loadmore)
May 29 #Javascript
JS学习笔记之原型链和利用原型实现继承详解
May 29 #Javascript
vue读取本地的excel文件并显示在网页上方法示例
May 29 #Javascript
vue-cli3中vue.config.js配置教程详解
May 29 #Javascript
You might like
《逃离塔科夫》——“萌新劝退,老手自嗨”的硬核FPS游戏
2020/04/03 其他游戏
javascript脚本调试方法小结
2008/11/24 Javascript
过虑特殊字符输入的js代码
2010/08/05 Javascript
JS实现在Repeater控件中创建可隐藏区域的代码
2010/09/16 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
JavaScript函数的调用以及参数传递
2015/10/21 Javascript
你不需要jQuery(三) 新AJAX方法fetch()
2016/06/14 Javascript
TypeScript学习之强制类型的转换
2016/12/27 Javascript
angularjs ocLazyLoad分步加载js文件实例
2017/01/17 Javascript
利用Vue实现移动端图片轮播组件的方法实例
2017/08/23 Javascript
nodejs操作mongodb的增删改查功能实例
2017/11/09 NodeJs
详解如何用模块化的方式写vuejs
2017/12/16 Javascript
Vue 使用计时器实现跑马灯效果的实例代码
2019/07/11 Javascript
koa2 数据api中间件设计模型的实现方法
2020/07/13 Javascript
javascript递归函数定义和用法示例分析
2020/07/22 Javascript
Python中操作MySQL入门实例
2015/02/08 Python
Python中的FTP通信模块ftplib的用法整理
2016/07/08 Python
Python 爬虫模拟登陆知乎
2016/09/23 Python
python调用其他文件函数或类的示例
2019/07/16 Python
python提取照片坐标信息的实例代码
2019/08/14 Python
python 实现字符串下标的输出功能
2020/02/13 Python
python爬虫开发之Beautiful Soup模块从安装到详细使用方法与实例
2020/03/09 Python
Django admin 实现search_fields精确查询实例
2020/03/30 Python
Python如何使用队列方式实现多线程爬虫
2020/05/12 Python
PyTorch-GPU加速实例
2020/06/23 Python
详解python方法之绑定方法与非绑定方法
2020/08/17 Python
使用HTML5在网页中嵌入音频和视频播放的基本方法
2016/02/22 HTML / CSS
关于母亲节的感言
2014/02/04 职场文书
意向书范文
2014/03/31 职场文书
律师授权委托书范本
2014/10/07 职场文书
社区好人好事材料
2014/12/26 职场文书
神农溪导游词
2015/02/11 职场文书
六一儿童节主持开场白
2015/05/28 职场文书
Python打包为exe详细教程
2021/05/18 Python
JS封装cavans多种滤镜组件
2022/02/15 Javascript
python数据处理之Pandas类型转换
2022/04/28 Python