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

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

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

来记录一下实现思路,

先上代码
静态页




  
  贪吃蛇




 

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 
 

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;i0;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;i0;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()

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

第一种


第二种


第三种


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

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

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

首先 第一步

建立一个画布

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

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

Food.js 代码分析

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

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第1张图片

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

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

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

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

那我们怎么定位呢?

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

高度同理

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第2张图片

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

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

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


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

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

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

那怎么办呢?

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

Snake.js 代码分析

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

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

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

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第4张图片


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

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第5张图片

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

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

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

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第6张图片

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

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

对了,我们自定义构造函数的时候不是设置过一个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实例详解_第7张图片

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


Game.js 代码分析

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

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

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

传入地图对象,并赋值

JS学习笔记之贪吃蛇小游戏demo实例详解_第8张图片

属性设置好了

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

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

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

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

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

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

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

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

取出蛇头自身的x和y

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

y同理

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

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

JS学习笔记之贪吃蛇小游戏demo实例详解_第9张图片

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

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

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

然后将Game 对象暴露给 window

JS学习笔记之贪吃蛇小游戏demo实例详解_第10张图片

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

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

最后绑定上keydown 事件

JS学习笔记之贪吃蛇小游戏demo实例详解_第11张图片

最后的最后

实例化一个Game对象

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


效果展示

JS学习笔记之贪吃蛇小游戏demo实例详解_第12张图片

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《JavaScript数学运算用法总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript数组操作技巧总结》、《JavaScript排序算法总结》、《JavaScript遍历算法与技巧总结》、《JavaScript查找算法技巧总结》及《JavaScript错误与调试技巧总结》

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

你可能感兴趣的:(JS学习笔记之贪吃蛇小游戏demo实例详解)