拼图小游戏

主要实现功能:

  • 图片拖动后位置的变换效果
  • 拼图成功进入更难一级游戏
  • 游戏计时

哔哩哔哩演示

技术栈:

  • jQuery、面向对象方式编程

实现原理:

  • 在多个子元素上利用backgroundPosition来实现多张图片拼成一张完整的图片。

  • 根据生成的子元素的数组索引来渲染出一张完整的图片,根据子元素数组的索引乱序来渲染出一张打乱的图片。(根据子元素的left和top值打乱)。

  • 子元素发生拖拽,当鼠标在棋盘外,元素不交换位置,并回到原位置;当鼠标在棋盘内,将拖动的元素和当前鼠标位置上的元素的位置发生交换。

具体讲解:

  • 初始化页面

    • 根据传进来的行数与列数来生成一个棋盘,第一个for循环可以理解为生成行(i)的空间(没有元素生成),第二个for循环理解为每一行的(每一小列或者说每一个元素(j));然后保存当前子元素在父级上(oImgArea)的索引。(因为sort方法改变原数组,所以存了两个,一个是标准的,用于复原图片,一个是用于乱序的,用于打乱图片)。
 // 初始化
  init() {
    
    randerGameTime();

    this.cellHW = this.getCellWH();
    let count = 0;

    this.oImgArea.text('');

    // 生成棋盘
    for (let i = 0; i < this.tr; i++) {
      for (let j = 0; j < this.td; j++) {

        // 创建元素
        let cell = $('<div class="imgCell"></div>');

        // 根据行与列的关系生成棋盘,并移动每个子元素上背景图片的位置
        $(cell).css({
          'width': this.cellHW.W + 'px',
          'height': this.cellHW.H + 'px',
          'left': this.cellHW.W * j,
          'top': this.cellHW.H * i,
          'backgroundPosition': (-this.cellHW.W) * j + 'px ' + (-this.cellHW.H) * i + 'px',
        });

        // 将当前的这个子元素插入到页面中
        this.oImgArea.append(cell);

        // 保存当前子元素在父级上的索引
        this.origArr.push(count);
        this.randArr.push(count);
        count++
      }
    }

    // 调用开始游戏方法
    this.startGame();
  }
  • 乱序方法

    • 将乱序数组里的数进行乱序,当初始化后由于生成的数组的长度较短,有小概率不能将数组打乱,所以需要多次调用打乱方法。
 // 生成随机数
  randomNum() {

    this.randArr.sort(function () {
      return Math.random() - 0.5;
    })

    // 如果检测到数组没有被打乱就再次调用打乱数组方法,但要限定数组长度较短时才去检测,数组较长检测会有调用栈溢出情况
    if (this.randArr.toString() == this.origArr.toString() && this.tr == 2) {
      this.randomNum();
    }
  }
  • 游戏开始

    • 实现点击按钮的变换,给所有子元素实现拖拽;在鼠标点击到子元素时,记录当前点击到的子元素的在标准数组里的索引;
    • 鼠标抬起时,记录鼠标相对于父元素(oImgArea)的坐标,取消事件;
    • 根据上面得到的坐标,调用获取拖拽运动的子元素在鼠标抬起时,鼠标该位置上子元素的在乱序数组里的索引
      • 得到的索引与鼠标点击到子元素的索引相同,就直接调用子元素运动方法,让子元素运动到原来的位置上
      • 得到的索引与鼠标点击到子元素的索引不同,就调用子元素位置交换方法

  // 开始游戏
  startGame() {

    this.oImgCellArr = $('.imgCell');
    let That = this;

    // 给点击按钮绑定click事件
    $('.start').on('click', () => {

      // 点击按钮的切换效果
      if (this.play) {

        this.randomNum()

        $('.start').text('复原').css('backgroundColor', '#0ff');
        this.play = false;

        // 以乱序数组来渲染子元素的位置
        this.cellRander(this.randArr);

        // 给每个子元素绑定事件
        this.oImgCellArr.on('mousedown', function (e) {

          // 获取当前点击到的元素在父级里的索引
          let indexDown = $(this).index();

          // 获取鼠标点击的位置到父级元素左顶点位置的距离(x, y)
          let leftDown = e.pageX - That.oImgCellArr.eq(indexDown).offset().left;
          let topDown = e.pageY - That.oImgCellArr.eq(indexDown).offset().top;


          $(document).on('mousemove', function (event) {
            That.oImgCellArr.eq(indexDown).css({
              'z-index': '40',

              // 获取鼠标这一时刻的位置相较于上一时刻的位置的变化量
              'left': event.pageX - leftDown - That.oImgArea.offset().left + 'px',
              'top': event.pageY - topDown - That.oImgArea.offset().top + 'px'
            })
          }).on('mouseup', function (event) {

            // 保存鼠标松开时该拖拽子元素的位置
            let topUp = event.pageY - That.oImgArea.offset().top;
            let leftUp = event.pageX - That.oImgArea.offset().left;

            // 获取拖拽运动的元素在鼠标抬起时,该位置上元素的在乱序数组里的索引
            let indexUp = That.getIndexUp(leftUp, topUp, indexDown);

            if (indexUp == indexDown) {

              // 鼠标越界,子元素回弹
              That.cellMove(That.randArr, indexDown, indexDown);

            } else {

              // 以修改乱序数组里的索引来达到让元素替换位置的效果
              That.cellsPositionChange(indexDown, indexUp);
            }

            $(document).off('mousemove').off('mouseup');
          })
        })
      } else {
        $('.start').text('打乱').css('backgroundColor', '#f0f');

        this.cellRander(this.origArr);
        this.play = true;

        $(this.oImgCellArr).off('mousemove').off('mouseup').off('mousedown');
      }
    })
  }

  • 获取拖拽运动的鼠标抬起时该位置上元素的乱序数组里的索引

    • 鼠标的位置越出棋盘
      • 直接返回拖拽的子元素在标准数组里的索引
    • 鼠标的位置在棋盘里
      • 根据鼠标当前相对于父级元素(oImgArea)左顶点为原点的坐标值求出鼠标下面的子元素的索引(未乱序),再根据这个索引求出该子元素在乱序数组里的索引
      • 梨子: 标准[0, 1, 2,3, 4] --求–>[4, 1, 0, 2, 3]
      • 主要是根据两个数组里的数字是一样的,只是索引的位置不同,假如传进来的索引是3,求法可理解为[4, 1, 0, 2, 3]里第几个的值是3;多理解几遍就知道了。

  // 获取当前鼠标抬起时的位置下的元素的索引
  getIndexUp(x, y, index) {

    if (x < 0 || x > (this.td * this.cellHW.W) || y < 0 || y > (this.tr * this.cellHW.H)) {
      return index;

    } else {

      // 行 = 鼠标当前相对于父级元素(oImgArea)左顶点为原点的坐标y值 / 子元素的高度
      let row = Math.floor(y / this.cellHW.H);

      // 列 同上
      let col = Math.floor(x / this.cellHW.W);

      // 求出该元素在标准数组里的索引(可理解为:一行子元素的个数 * 行数 + 最后一行的子元素的个数)
      let l = row * this.td + col;

      // 根据该元素在标准数组里的索引求出在乱序数组里索引
      let i = 0;
      let len = this.randArr.length;
      while ((i < len) && this.randArr[i] !== l) {
        i++;
      }
      return i;
    }
  }
  • 子元素运动函数

    • 这个方法是自己封装的,三个参数分别代表:依照正序数组还是乱序数组进行运动、拖动的元素运动到乱序数组里索引为to的元素上,乱序数组里索引为to的元素远动到乱序数组里索引为from的元素上;

    • 规则就是将数组里的索引转换为行与列,然后再根据子元素的宽和高来求出需要运动到的left和top的值,(注意:在标准数组里index = 标准数组[index],乱序数组里是index = 乱序数组[i]),即: 第(index - index % this.td) / this.td 行,第(index % this.td)列,然后top = 子元素的高 * 行数,left = 子元素 * 列数

    • 如果子元素有位置的调换,就需要将拖动的子元素在乱序数组的索引传入from,然后调换也需要在乱序数组里将这两个值进行调换;如果没有元素位置的调换,那么第三参数的值与第二是参数的值一样,即让拖动的元素回到元素回到原来的位置。

// 子元素运动函数
  cellMove(arr, to, from) {
    this.oImgArea.find('.imgCell').eq(to).animate({
      'top': (this.cellHW.H) * (arr[from] - (arr[from] % this.td)) / this.td + 'px',
      'left': (this.cellHW.W) * (arr[from] % this.td) + 'px'
    }, 600, function () {

      // 修改层级
      $(this).css('z-index', '10')
    })
  }

  • 渲染进行游戏时间

    • 主要是将一个毫秒时间数字转为00:00:00格式的时间,并在页面刷新一次就渲染一次时间。
// 获取当前游戏时间
let firstTime = new Date().getTime();
let randerGameTime = function  () {
  let currTime = new Date().getTime();

  let time = (currTime - firstTime) / 1000;
  let str, hours, minute, second;

  if (time > 3600) {
    let num = parseInt(parseInt(time / 3600));
    hours = num >= 10 ? num + ':' : '0' + num + ':';
    minute = (time - num * 3600) / 60 >= 10 ? parseInt((time - num * 3600) / 60) + ':' : '0' + parseInt((time - num * 3600) / 60) + ':';
  } else {
    hours = parseInt(time / 3600) > 0 ? (parseInt(time / 3600) >= 10 ? (parseInt(time / 3600) + ':') : ('0' + parseInt(time / 3600) + ':')) : '00:';
    minute = parseInt(time / 60) >= 10 ? (parseInt(time % 3600) >= 59 ? (parseInt(parseInt(time % 3600) / 60) + ':') : (parseInt(time / 60) + ':')) : ('0' + parseInt(time / 60) + ':');
  }

  second = parseInt(time % 60) >= 10 ? parseInt(time % 60) : '0' + parseInt(time % 60);

  // 更新上次保留的时间
  str = hours + minute + second;

  $('.time').find('span').text(str)

  requestAnimationFrame( randerGameTime );
}


  • 检测拼图是否成功

    • 将标准数组和乱序数组进行比对,如果两个数组里的数字顺序相同,则拼图成功,然后清空两个数组,进入下一个难度的游戏

  // 检测所有子元素是否能拼成一张完整图片 
  chack() {
    if (this.randArr.toString() == this.origArr.toString()) {

      // 延迟执行,使得图片运动完成后再触发
      setTimeout(() => {
        
        alert('恭喜你!拼图成功!!请进入下一关');
        
        // 清空数组,防止干扰
        this.origArr = [];
        this.randArr = [];

        // 以增加行与列的方式来增加游戏难度
        this.tr += 1;
        this.td += 1;

        let game = new Game(this.tr, this.td);
        game.init();

        $('.start').text('打乱').css('backgroundColor', '#f0f');
      
      }, 1000)
    }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值