#demo#网页小游戏:贪吃蛇

贪吃蛇

目标:
  • 整图为一个 64 * 40 大小的矩形
  • 随机在空白坐标点生成食物
  • 蛇不能撞墙和吃自己
  • 每吃一个食物身体长一格
  • 初始速度为 150ms 移动一次,随着食物越多,速度越快,最快 50ms 移动一次
  • 方向的改变用键盘的方向键控制,不能直接往反方向转向
思路:

使用 canvas 作为基础实现方式

蛇的主体采用一个二维数组来实现绘制

蛇的移动去掉蛇数据结构的最后一位,根据方向和蛇头的数据做相应更改再添加到蛇主体头部,同时判断是否结束

蛇将要移动的坐标点是否是食物的位置,如果是则不删除蛇的最后一位数据

如果蛇的长度为 64 * 40 大小,占满整个矩形,则游戏通关完成

<!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>
    body {
      background-color: #eee;
    }
    .container {
      text-align: center;
    }
    .top {
      margin: 20px auto;
      width: 640px;
    }
    #score {
      float: left;
    }
    .main {
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      width: 642px;
      height: 402px;
    }
    #snake {
      border: 1px solid #000;
      width: 640px;
      height: 400px;
      display: inline-block;
      z-index: 99;
      background-color: rgba(0, 0, 0, .1);
    }

    #mask {
      background-color: rgba(0, 0, 0, .5);
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 100;
      display: block;
      color: #fff;
      line-height: 400px;
      text-align: center;
      font-size: 30px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="top">
      <span id="score">Score: 0</span>
      <button id="restart">重新开始</button>
      <button id="stop">暂停</button>
      <button id="continue">继续</button>
    </div>
    <div class="main">
      <canvas id="snake" width="640" height="400"></canvas>
      <div id="mask">开始</div>
    </div>
  </div>

<script>
  let greedySnake = null
  let score = document.querySelector('#score')
  let restart = document.querySelector('#restart')
  let stop = document.querySelector('#stop')
  let conti = document.querySelector('#continue')
  let mask = document.querySelector('#mask')

  restart.onclick = () => {
    if (!greedySnake.isStart) return
    greedySnake.start()
  }
  stop.onclick = () => {
    if (greedySnake.isStop || !greedySnake.isStart) return
    greedySnake.stop()
  }
  conti.onclick = () => {
    if (!greedySnake.isStop || !greedySnake.isStart) return
    greedySnake.continue()
  }
  mask.onclick = () => {
    if (!greedySnake.isStart) {
      greedySnake.start()
    } else {
      greedySnake.continue()
    }
  }

  // 大小为64 * 40
  class GreedySnake {
    constructor() {
      this.canvas = document.querySelector('#snake')
      this.ctx = this.canvas.getContext('2d')
      this.maxX = 80          // 最大行
      this.maxY = 50          // 最大列
      this.itemWidth = 8      // 每个点的大小
      this.direction = 'right'   // up down right left 方向
      this.speed = 150        // ms 速度
      this.isStop = false     // 是否暂停
      this.isOver = false     // 是否结束
      this.isStart = false    // 是否开始
      this.score = 0          // 分数
      this.timer = null       // 移动定时器
      this.j = 1
      this.canChange = true
      
      this.grid = new Array()//计算得到所有坐标点

      for (let i = 0; i < this.maxX; i++) {
        for (let j = 0; j < this.maxY; j++) {
          this.grid.push([i, j])
        } 
      }

      this.drawGridLine()
    }

    // 开始
    start() {
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (!this.isStart) {
        this.isStart = true
      }
      this.score = 0
      this.speed = 150
      this.isStop = false
      this.isOver = false
      this.direction = 'right'
      this.createSnake()
      this.createFood()
      this.getDirection()
      this.draw()
      this.move()
      mask.style.display = 'none'
    }

    // 创建蛇主体
    //在中间位置取一组数据定义为蛇的初始位置
    createSnake() {
      this.snake = [
        [4, 25],
        [3, 25],
        [2, 25],
        [1, 25],
        [0, 25]
      ]
    }

    // 移动
    move() {
      if (this.isStop) return

      let [x, y] = this.snake[0]
      switch(this.direction) {
        case 'left':
          x--
          break
        case 'right':
          x++
          break
        case 'up':
          y--
          break
        case 'down':
          y++
          break
      }
      
      // 如果下一步不是食物的位置
      if (x !== this.food[0] || y !== this.food[1]) {
        this.snake.pop()
      } else {
        this.createFood()
      }

      if (this.over([x, y])) {
        this.isOver = true
        mask.style.display = 'block'
        mask.innerHTML = '结束'
        return
      }
      if (this.completed()) {
        mask.style.display = 'block'
        mask.innerHTML = '恭喜您,游戏通关'
        return
      }

      this.snake.unshift([x, y])
      
      this.draw()
      this.canChange = true
      this.timer = setTimeout(() => this.move(), this.speed)
    }
    
    // 暂停游戏
    stop() {
      if (this.isOver) return
      this.isStop = true
      mask.style.display = 'block'
      mask.innerHTML = '暂停'
    }

    // 继续游戏
    continue() {
      if (this.isOver) return
      this.isStop = false
      this.move()
      mask.style.display = 'none'
    }

    getDirection() {
      // 上38 下40 左37 右39 不能往相反的方向走
      document.onkeydown = (e) => {
        // 在贪吃蛇移动的间隔内不能连续改变两次方向
        if (!this.canChange) return
        switch(e.keyCode) {
          case 37:
            if (this.direction !== 'right') {
              this.direction = 'left'
              this.canChange = false
            }
            break
          case 38:
            if (this.direction !== 'down') {
              this.direction = 'up'
              this.canChange = false
            }
            break
          case 39:
            if (this.direction !== 'left') {
              this.direction = 'right'
              this.canChange = false
            }
            break
          case 40:
            if (this.direction !== 'up') {
              this.direction = 'down'
              this.canChange = false
            }
            break
          case 32:
            // 空格暂停与继续
            if (!this.isStop) {
              this.stop()
            } else {
              this.continue()
            }
            break
        }
      }
    }
    createPos() {
      let [x, y] = this.grid[(Math.random() * this.grid.length) | 0]

      for (let i = 0; i < this.snake.length; i++) {
        if (this.snake[i][0] == x && this.snake[i][1] == y) {
          return this.createPos()
        }
      }

      return [x, y]
    }
    // 生成食物
    createFood() {
      this.food = this.createPos()

      // 更新分数
      score.innerHTML = 'Score: '+ this.score++
      
      if (this.speed > 50) {
        this.speed--
      }
    }

    // 结束
    over([x, y]) {
      if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
        return true
      }
      
      if (this.snake.some(v => v[0] === x && v[1] === y)) {
        return true
      }
    }

    // 完成
    completed() {
      if (this.snake.length == this.maxX * this.maxY) {
        return true
      }
    }

    // 网格线
    drawGridLine() {
      for (let i = 1; i < this.maxY; i++) {
        this.ctx.moveTo(0, i * this.itemWidth)
        this.ctx.lineTo(this.canvas.width, i * this.itemWidth)
      }
      
      for (let i = 1; i < this.maxX; i++) {
        this.ctx.moveTo(i * this.itemWidth, 0)
        this.ctx.lineTo(i * this.itemWidth, this.canvas.height)
      }
      this.ctx.lineWidth = 1
      this.ctx.strokeStyle = '#ddd'
      this.ctx.stroke()
    }

    // 绘制
    draw() {
      // 清空画布
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

      this.drawGridLine()

      //绘制食物
      this.ctx.fillStyle="#000"
      this.ctx.fillRect(
        this.food[0] * this.itemWidth + this.j,
        this.food[1] * this.itemWidth + this.j,
        this.itemWidth - this.j * 2,
        this.itemWidth -  + this.j * 2
      )
      this.j ^= 1
        //绘制蛇头
      this.ctx.fillStyle="green"
      this.ctx.fillRect(
        this.snake[0][0] * this.itemWidth + 0.5,
        this.snake[0][1] * this.itemWidth + 0.5,
        this.itemWidth - 1,
        this.itemWidth - 1
      )
      //绘制蛇身
      this.ctx.fillStyle="red"
      for (let i = 1; i < this.snake.length; i++) {
        this.ctx.fillRect(
          this.snake[i][0] * this.itemWidth + 0.5,
          this.snake[i][1] * this.itemWidth + 0.5,
          this.itemWidth - 1,
          this.itemWidth - 1
        )
      }
    }
  }
  greedySnake = new GreedySnake()
</script>
</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
http://blog.csdn.net/xiaoxiao108/article/details/8913616 html5 挺火,写个html5游戏玩玩吧,想起cocos2d 貌似各个平台都有,网上找了找,下载了个Cocos2d-html5引擎包。 貌似另一个开源引擎lufylegend.js也很好,下次用用lufylegend.js试试。 开发环境 chrome Cocos2d-html5 游戏地址:http://hehe108.sinaapp.com/cocos2d/ (adsw回车) 实现方法如下 1.创建好 LayerGradient的子类 (里面放坦克子弹) 2.重写 onEnter 方法添加一些基本按钮 跟一些初始坦克,子弹 3.通过schedule方法 控制 坦克 子弹的重画 4.根据键盘按键(ASWD)确定出坦克的方向,根据坦克的方向修改坦克的X,Y轴坐标,来实现坦克的移动 5.通过cc.rectIntersectsRect函数来进行碰撞检测,实现子弹打击坦克 具体代码 1.在项目里面添加方向 var Direction = { L:0, U:1, D:2, R:3, STOP:4 }; 2.添加子弹类的相关属性 SPEED:10, WIDTH:15, HEIGHT:15, x:null, y:null, dir:null, live:true, tankClient:null, //LayerGradient子类TankClient 的引用 good:null, 3.子弹初始化,重画 ctor:function (x,y,good,dir,tankClient) { cc.associateWithNative( this, cc.Sprite ); this.x=x; this.y=y; this.dir=dir; this.tankClient=tankClient; this.good=good; this.initWithFile(s_missile); this.setPosition( cc.p(this.x, this.y) ); this.tankClient.addChild(this); }, Draw:function(){ if(!this.live){ this.tankClient.removeChild(this, true); return; } this.setPosition( cc.p(this.x, this.y) ); this.Move(); }, 4.添加子弹打击坦克的方法 HitTank:function(t){ if (cc.rectIntersectsRect(this.GetRectangle(), t.GetRectangle()) && t.live && this.live && this.good != t.good){ t.live = false; this.live = false; return true; } return false; }, 5.添加坦克类相关属性 SPEED:5, WIDTH:58, HEIGHT:58, x:0, y:0, l:false, u:false, r:false, d:false, dir:Direction["STOP"], ptDir:Direction["D"], tankClient:null, good:null, step:0, live:true, 6.在tank类中 坦克初始化,重画 ctor:function (x,y,good,tankClient) { cc.associateWithNative( this, cc.Sprite ); this.x=x; this.y=y; this.tankClient=tankClient; this.good=good; if(good){ this.initWithFile(s_tank); }else{ this.initWithFile(s_enemy); } this.setPosition( cc.p(this.x, this.y) ); this.tankClient.addChild(this); }, Draw:function(){ if (!this.live){ if (!this.good){ this.tankClient.removeChild(this, true); } this.tankClient.removeChild(this, true); return; } this.setPosition( cc.p(this.x, this.y) ); switch (this.ptDir) { case Direction["D"]: this.setRotation(0); //旋转精灵控制 炮筒方向 break; case Direction["U"]: this.setRotation(180); break; case Direction["L"]: this.setRotation(270); break; case Direction["R"]: this.setRotation(90); break; } this.Move(); }, 7.tank发子弹的方法 Fire:function() { if(!this.live) return null; for(var i=0;i<this.tankClient.missiles.length;i++){ var m = this.tankClient.missiles[i]; if (m.live == false){ m.x=this.x; m.y=this.y; m.live=true; m.dir=this.ptDir; m.good=this.good; this.tankClient.addChild(m); return m; } } var missile=new Missile(this.x,this.y,this.good,this.ptDir,this.tankClient); this.tankClient.missiles.push(missile); return missile; }, 8.LayerGradient加入坦克 this.tanks = []; this.myTank = new Tank(60,20, true, this); for (var i = 0; i < 10; i++){ this.tanks.push(new Tank(50 + 70 * (i + 1), 420, false, this)); } 9.LayerGradient中调用子弹打击坦克的方法 for(var i=0;i<this.missiles.length;i++){ var m = this.missiles[i]; m.HitTank(this.myTank); m.HitTanks(this.tanks); m.Draw(); } 10.控制坦克移动射击的部分代码 onKeyUp:function(key) { this.myTank.KeyReleased(key); }, onKeyDown:function(key) { this.myTank.KeyPressed(key); } 11.用Ant和compiler合并压缩js后发布到sae 如果你发现有什么不合理的,需要改进的地方,请留言。或者可以通过 328452421@qq.com 联系我,非常感谢。 http://blog.csdn.net/xiaoxiao108/article/details/8913616

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值