• JS开发HTML5游戏《神奇的六边形》(三)


    近期出现一款魔性的消除类HTML5游戏《神奇的六边形》,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏。

    (点击图片可进入游戏体验)

    因内容太多,为方便大家阅读,所以分成部分来讲解。

    本文为第三部分,主要包括:

    11.显示出3个形状

    12.形状的拖放处理

    13.形状放入棋盘的实现

    14.界面管理

    15.消除行

    若要一次性查看所有文档,也可点击这里

    十一. 显示出3个形状

    1. 在Scripts/ui创建文件:Pool.js,绘制3个形状。

     1  var s = qc.Serializer;
     2 
     3  /**
     4   * 3个形状的绘制
     5   */
     6  var Pool = qc.defineBehaviour('qc.tetris.Pool', qc.Behaviour, function() {
     7      var self = this;
     8 
     9      /**
    10       * 形状的预置
    11       */ 
    12      self.blocksPrefab = null;
    13 
    14      /**
    15       * 记录下面3个形状的实例
    16       */
    17      self.shapes = [];
    18  }, {
    19      blocksPrefab: s.PREFAB
    20  });
    21 
    22  /**
    23   * 初始化处理
    24   */
    25  Pool.prototype.awake = function() {
    26      var self = this;
    27      self.redraw();
    28  };
    29 
    30  /**
    31   * 绘制3个形状
    32   */
    33  Pool.prototype.redraw = function() {
    34      var self = this;
    35 
    36      // 先干掉旧的形状数据
    37      for (var i = 0; i < self.shapes.length; i++) {
    38          self.shapes[i].destroy();
    39      }
    40      self.shapes = [];
    41 
    42      // 创建3个新的形状
    43      for (i = 0; i < 3; i++) {
    44          self.add(i);
    45      }
    46      self.resize();
    47  };
    48 
    49  /**
    50   * 调整位置
    51   */
    52  Pool.prototype.resize = function() {
    53      var self = this, o = self.gameObject;
    54 
    55      // 计算X方向的偏移
    56      var offset = o.width * (0.5 - 0.165);
    57      for (var i = 0; i < 3; i++) {
    58          var child = self.shapes[i];
    59          if (!child) return;
    60          child.anchoredX = offset * (i - 1);
    61          child.anchoredY = 0;
    62      }
    63  };
    64 
    65  /**
    66   * 添加一个形状
    67   */
    68  Pool.prototype.add = function(index) {
    69      var self = this;
    70 
    71      var o = self.game.add.clone(self.blocksPrefab, self.gameObject);
    72      var c = o.getScript('qc.tetris.BlocksUI');
    73      c.data = qc.Tetris.Shapes.pool[index];
    74      self.shapes[index] = o;
    75  };
    76 
    77  /**
    78   * 删除一个形状
    79   */
    80  Pool.prototype.remove = function(index) {
    81      var o = this.shapes[index];
    82      o.destroyImmediately();
    83      this.shapes.splice(index, 1);
    84  };

    整个代码逻辑比较简单,根据3个形状的数据进行绘制。请参考注释进行理解。

     

    2. 将此脚本挂载到UIRoot/pool节点,关联blocksPrefab属性:

    3. 运行测试下效果,3个形状正确显示了:

     

    十二. 形状的拖放处理

    形状在被按下时,需要变大,如果是手机上需要向上做一定的位置偏移。拖拽时形状应该跟着鼠标或手指进行移动。

    修改脚本Scripts/ui/BlocksUI.js,添加如下代码:

    1. 修改reset函数,增加放大区块的逻辑:

     1 BlocksUI.prototype.reset = function(fixToBoard) {
     2      var self = this, o = self.gameObject;
     3      for (var pos in self._blocks) {
     4          var p = qc.Tetris.readPos(pos);
     5          var pt = qc.Tetris.board.toWorld(p, fixToBoard ? qc.Tetris.BLOCK_H : qc.Tetris.POOL_DISTANCE_NORMAL);
     6          var block = self._blocks[pos];
     7          block.anchoredX = pt.x;
     8          block.anchoredY = pt.y;
     9 
    10          var scale = fixToBoard ? 1.13 : 1;
    11          block.find('shadow').scaleX = scale;
    12          block.find('shadow').scaleY = scale;
    13          block.find('block').scaleX = scale;
    14          block.find('block').scaleY = scale;
    15      }
    16  };

    2. 添加按下的逻辑处理,放大区块:

     1 /**
     2   * 鼠标按下:放大区块
     3   */
     4  BlocksUI.prototype.onDown = function(e) {
     5      var self = this, o = self.gameObject;
     6      self.drop = false;
     7      self.reset(true);
     8 
     9      // 在手机下,需要往上做点偏移
    10      o.y -= self.offsetY;
    11  };
    • drop标记当前区块是否被放到棋盘了,刚开始按下清理下环境
    • 按下时需要向上做偏移offsetY

    3. 添加鼠标松开或触摸结束的处理,还原区块的位置和大小:

    1 /**
    2   * 鼠标松开:重置区块大小
    3   */
    4  BlocksUI.prototype.onUp = function() {
    5      var self = this;
    6      self.reset();
    7  };

    4. 添加开始拖拽的处理:

     1 /**
     2   * 拖拽开始
     3   */
     4  BlocksUI.prototype.onDragStart = function(e) {
     5      var self = this;
     6      self.drop = false;
     7      self.drag = true;
     8      self.lastPos = '';
     9      self.game.input.nativeMode = true;
    10      self.reset(true);
    11 
    12      self.game.log.trace('Start drag:{0}', self.index);
    13 
    14      // 复制出可放入标记
    15      var ob = self.flagBlocks = self.game.add.clone(self.gameObject, qc.Tetris.boardUI.gameObject);
    16      ob.children.forEach(function(block) {
    17          block.find('shadow').visible = false;
    18          var b = block.find('block');
    19          b.width = qc.Tetris.BLOCK_W;
    20          b.height = qc.Tetris.BLOCK_H;
    21          b.scaleX = 1;
    22          b.scaleY = 1;
    23          b.frame = 'dark' + b.frame;
    24      });
    25      ob.scaleX = 1;
    26      ob.scaleY = 1;
    27      ob.interactive = false;
    28      self.hideFlag();
    29  };
    • 初始时,标记正在拖拽(drag = true),并且没有被放下(drop = false)
    • 当拖拽到棋盘时,需要实时指示是否可以放下本形状。拖拽开始先清理下最近一次检测的逻辑坐标点(last = '')
    • 设置输入模式nativeMode = true。确保输入事件能被实时处理(默认情况下延后一帧处理,运行效率比较高),本游戏对拖拽的实时响应比较重要。
    • 拖拽开始时,放大并偏移形状(和鼠标按下的逻辑一样)
    • 后续的逻辑:另外复制出本形状,并隐藏掉。这个形状在后续拖拽中,会在棋盘显示出来以指示当前是否可以放入。这个指示的格子图片,使用暗色的图片。

    5. 添加拖拽的处理,每帧都会进行调度:

     1  /**
     2   * 拖拽中
     3   */
     4  BlocksUI.prototype.onDrag = function(e) {
     5      var self = this,
     6          o = self.gameObject;
     7      if (self.drag) {
     8          // 改变节点的目标位置
     9          var p = o.getWorldPosition();
    10          p.x += e.source.deltaX;
    11          p.y += e.source.deltaY;
    12          var lp = o.parent.toLocal(p);
    13          o.x = lp.x;
    14          o.y = lp.y;
    15 
    16          // 计算当前对应棋盘中心点的偏移
    17          var board = qc.Tetris.boardUI.gameObject;
    18          p = board.toLocal(p);
    19          p.y += board.height * 0.5;
    20 
    21          // 反算出对应的归一化坐标
    22          var xy = qc.Tetris.board.toLocal(p);
    23          var x = Math.round(xy.x),
    24              y = Math.round(xy.y),
    25              pos = qc.Tetris.makePos(x, y);
    26          if (self.lastPos !== pos) {
    27              self.lastPos = pos;
    28              if (qc.Tetris.board.data[pos] &&
    29                  qc.Tetris.board.checkPutIn(pos, self.data.list)) {
    30                  self.showFlag(pos);
    31              }
    32              else {
    33                  self.hideFlag();
    34              }
    35          }
    36      }
    37  };
    • 在拖拽的事件e中,会指明本帧到上一帧的移动偏移量(屏幕坐标),本形状加上屏幕坐标偏移,这样就移动起来了
    • 然后计算本形状的中心点,对应到棋盘的逻辑坐标。并检查目标是否可以放入,如果可以就需要显示指示
    • 最近一次检测的逻辑坐标需要记录下来,防止每帧都对同一逻辑坐标检查是否可以放入(白耗CPU)

    6. 打开脚本Scripts/logic/Board.js,实现checkPutIn方法:

     1 Board.prototype.checkPutIn = function(pos, list) {
     2      var self = this;
     3      var pt = qc.Tetris.readPos(pos),
     4          x = pt.x,
     5          y = pt.y;
     6 
     7      for (var i = 0; i < list.length; i++) {
     8          var x0 = x + list[i][0],
     9              y0 = y + list[i][1];
    10 
    11          // 这个点应该是空的
    12          var block = self.data[qc.Tetris.makePos(x0, y0)];
    13          if (!block) return false;
    14          if (block.value !== 0) return false;
    15      }
    16      return true;
    17  };

    7. 继续打开Scripts/ui/Blocks.js,继续实现拖拽结束的逻辑:

     1  /**
     2   * 拖拽结束
     3   */
     4  BlocksUI.prototype.onDragEnd = function(e) {
     5      var self = this,
     6          o = self.gameObject;
     7      self.drag = false;
     8 
     9      if (self.flagBlocks.visible && self.lastPos) {
    10          // 放到这个位置中去
    11          self.drop = true;
    12          qc.Tetris.operation.putIn(self.index, self.lastPos, self.data);
    13      }
    14      else {
    15          self.reset();
    16          o.parent.getScript('qc.tetris.Pool').resize();
    17      }
    18 
    19      // 显示标记可以干掉了
    20      self.flagBlocks.destroy();
    21      delete self.flagBlocks;
    22  };
    23 
    24  /**
    25   * 隐藏指示标记
    26   */
    27  BlocksUI.prototype.hideFlag = function() {
    28      this.flagBlocks.visible = false;
    29  };
    30 
    31  /**
    32   * 显示指示标记
    33   */
    34  BlocksUI.prototype.showFlag = function(pos) {
    35      this.flagBlocks.visible = true;
    36      var pt = qc.Tetris.board.data[pos];
    37      this.flagBlocks.anchoredX = pt.x;
    38      this.flagBlocks.anchoredY = pt.y;
    39  };
    • 拖拽结束后,需要判定形状是否被放入目标节点
    • 如果可以放入,则调用指令:qc.Tetris.operation.putIn(下步骤实现)
    • 如果不能放入,则需要将位置和大小等还原
    • 最后,指示对象需要被析构

    8. 在Scripts/operation创建文件PutIn.js,实现放入形状指令:

    1 /**
    2   * 请求放入指定格子,如果成功放入返回true,否则返回false
    3   */
    4  qc.Tetris.operation.putIn = function(index, pos) {
    5      // TODO: 逻辑待实现
    6  };

    9. 在Blocks.js中,我们使用到了棋盘对象:qc.Tetris.boardUI.gameObject,但目前这个值(BoardUI)尚未被赋值。
    打开BoardUI.js,在构造函数中加入代码赋值:

     1 var BoardUI = qc.defineBehaviour('qc.tetris.BoardUI', qc.Behaviour, function() {
     2      var self = this;
     3 
     4      // 登记下本对象
     5      qc.Tetris.boardUI = self;
     6 
     7      /**
     8       * 棋盘的棋子元素
     9       */
    10      self.pieces = {};
    11 
    12      ...

    10. 运行测试下,形状可以随意拖拽了,并且可以反弹回原来位置。不过还无法放入(因为PutIn我们还没实现),请继续后面教程。

    十三. 形状放入棋盘的实现

    处理流程如下图:

    打开文件Scripts/operation/PutIn.js,实现上述代码:

      1 /**
      2      * 请求放入指定格子,如果成功放入返回true,否则返回false
      3      */
      4     qc.Tetris.operation.putIn = function(index, pos) {
      5         var shape = qc.Tetris.Shapes.pool[index],
      6             board = qc.Tetris.board,
      7             ui = qc.Tetris.game.ui,
      8             log = qc.Tetris.game.log;
      9         log.trace('尝试将({0})放入({1})', index, pos);
     10 
     11         if (!board.checkPutIn(pos, shape.list)) {
     12             // 禁止放入
     13             return false;
     14         }
     15         log.trace('放入格子:({0})', pos);
     16 
     17         // 更新棋盘信息
     18         board.putIn(pos, shape.list, shape.value);
     19 
     20         // 计算可以消除的行,并同时消除掉
     21         var lines = board.getFullLines();
     22         lines.forEach(function(flag) {
     23             var children = ui.killLineEffect.find(flag).gameObject.children;
     24             var pts = [];
     25             children.forEach(function(child) { pts.push(child.name); })
     26             board.clearLine(pts);
     27         });
     28 
     29         // 计算分数明细,并添加之
     30         var scoreDetail = qc.Tetris.operation.calcScore(lines);
     31         qc.Tetris.score.current += scoreDetail.total;
     32 
     33         // 替换为新的形状
     34         qc.Tetris.Shapes.pool.splice(index, 1);
     35         qc.Tetris.Shapes.pool.push(qc.Tetris.Shapes.random());
     36 
     37         // 重新绘制棋盘
     38         ui.board.redraw();
     39 
     40         // 行消除与分数增加的动画表现
     41         if (lines.length > 0) {
     42             for (var i = 0; i < lines.length; i++) {
     43                 ui.killLineEffect.play(i, lines[i], scoreDetail.lines[i]);
     44             }
     45         }
     46         else {
     47             ui.board.getScript('qc.tetris.FlyScore').play(pos, scoreDetail.total);
     48         }
     49 
     50         // 当前分数的动画表现
     51         ui.currentScore.setScore();
     52 
     53         // 形状飞入的动画表现,并将旧的形状删除掉
     54         ui.pool.remove(index);
     55         ui.pool.add(2);
     56         ui.pool.flyIn(index);
     57 
     58         // 死亡检测
     59         if (board.die) {
     60             // 延迟显示死亡界面
     61             log.trace('Game Over!');
     62             qc.Tetris.game.timer.add(3000, function() {
     63                 ui.onDie();
     64             });
     65         }
     66 
     67         // 放入成功了
     68         return true;
     69     };
     70 
     71     /**
     72      * 计算分数明细
     73      * total: 总分数
     74      * lines: [各行的分数]
     75      */
     76     qc.Tetris.operation.calcScore = function(lines) {
     77         var scores = {
     78             total: 40,
     79             lines: []
     80         };
     81         if (lines.length < 1) return scores;
     82 
     83         // 计算加成
     84         var append = Math.max(0, lines.length - 1 * 10);
     85 
     86         for (var i = 0; i < lines.length; i++) {
     87             var flag = lines[i];
     88 
     89             var line = qc.Tetris.game.ui.killLineEffect.find(flag);
     90             var len = line.gameObject.children.length;
     91             scores.lines[i] = len * 20 + append * len;
     92             scores.total += scores.lines[i];
     93 
     94             // 40合并到第一行去做表现
     95             if (i === 0) {
     96                 scores.lines[i] += 40;
     97             }
     98         }
     99 
    100         return scores;
    101     };
    • calcScore方法为计算分数的逻辑
    • 代码中出现了qc.Tetris.game.ui(即UIManager),在下文中陆续实现
    • 另外,本逻辑中加入了一些动画表现,在下文中也陆续实现之
    • 先大致理解下处理流程,细节可以后续章节中逐一理解

    十四. 界面管理

    1. 在Scripts/ui新建UIManager.js:

     1 /**
     2   * 负责管理所有的游戏界面
     3   */
     4  var UIManager = qc.defineBehaviour('qc.tetris.UIManager', qc.Behaviour, function() {
     5      var self = this;
     6      self.game.ui = self;
     7 
     8      self.runInEditor = true;
     9  }, {
    10      bestScoreNode: qc.Serializer.NODE,
    11      currentScoreNode: qc.Serializer.NODE,
    12      boardNode: qc.Serializer.NODE,
    13      poolNode: qc.Serializer.NODE,
    14      killLineEffectNode: qc.Serializer.NODE,
    15 
    16      uiRoot: qc.Serializer.NODE,
    17      gameOverPrefab: qc.Serializer.PREFAB
    18  });
    19 
    20  /**
    21   * 初始化管理
    22   */
    23  UIManager.prototype.awake = function() {
    24      var self = this;
    25 
    26      /**
    27       * bestScore: BestScore组件
    28       */
    29      if (self.bestScoreNode)
    30          self.bestScore = self.bestScoreNode.getScript('qc.tetris.BestScore');
    31 
    32      /**
    33       * currentScore: CurrentScore组件
    34       */
    35      if (self.currentScoreNode)
    36          self.currentScore = self.currentScoreNode.getScript('qc.tetris.CurrentScore');
    37 
    38      /**
    39       * board: 棋盘绘制组件
    40       */
    41      if (self.boardNode)
    42          self.board = self.boardNode.getScript('qc.tetris.BoardUI');
    43 
    44      /**
    45       * pool: 3个形状的方块
    46       */
    47      if (self.poolNode)
    48          self.pool = self.poolNode.getScript('qc.tetris.Pool');
    49 
    50      /**
    51       * killLineEffect: 方块消除的动画组件
    52       */
    53      if (self.killLineEffectNode)
    54          self.killLineEffect = self.killLineEffectNode.getScript('qc.tetris.KillLineEffect');
    55  };
    56 
    57  /**
    58   * 游戏重新开始的界面处理
    59   */
    60  UIManager.prototype.restart = function() {
    61      var self = this;
    62 
    63      // 重新生成3个新的形状
    64      self.pool.redraw();
    65 
    66      // 棋盘重绘制
    67      self.board.redraw();
    68 
    69      // 重绘当前分数
    70      self.currentScore.setScore();
    71  };
    72 
    73  /**
    74   * 死亡的处理
    75   */
    76  UIManager.prototype.onDie = function() {
    77      // 显示失败页面
    78      this.game.add.clone(this.gameOverPrefab, this.uiRoot);
    79  };
    • UIManager引用了几个界面逻辑,其中KillLineEffect脚本下章节再实现
    • 同时,加入了死亡处理接口、重新开始游戏接口,具体的逻辑在后续章节中逐一实现

    2. 将脚本挂载到UIRoot,并关联各属性:

    部分属性先留空

     

    十五. 消除行

    以下的行是可以被消除的:

      

    逻辑实现

    1. 打开Scripts/logic/board.js,将上述3类型的行建立数据结构:

     1 var Board = qc.Tetris.Board = function() {
     2      // 省略一堆代码
     3      ...
     4 
     5      // 左斜的9条线,指明起始点坐标
     6      self.xyLines = [
     7          [0, -4],
     8          [1, -4],
     9          [2, -4],
    10          [3, -4],
    11          [4, -4],
    12 
    13          [4, -3],
    14          [4, -2],
    15          [4, -1],
    16          [4, 0]
    17      ];
    18 
    19      // 横向9条线,指明起始点坐标和长度
    20      self.yLines = [
    21          [0, -4, 5],
    22          [-1, -3, 6],
    23          [-2, -2, 7],
    24          [-3, -1, 8],
    25          [-4, 0, 9],
    26          [-4, 1, 8],
    27          [-4, 2, 7],
    28          [-4, 3, 6],
    29          [-4, 4, 5]
    30      ];
    31 
    32      // 右斜9条线,指明起始点坐标和长度
    33      self.xLines = [
    34          [-4, 0, 5],
    35          [-3, -1, 6],
    36          [-2, -2, 7],
    37          [-1, -3, 8],
    38          [0, -4, 9],
    39          [1, -4, 8],
    40          [2, -4, 7],
    41          [3, -4, 6],
    42          [4, -4, 5]
    43      ];
    44  };

     

    2. 实现putIn接口:

     1 Board.prototype.putIn = function(pos, list, value) {
     2      var self = this;
     3      var pt = qc.Tetris.readPos(pos),
     4          x = pt.x,
     5          y = pt.y;
     6 
     7      for (var i = 0; i < list.length; i++) {
     8          var x0 = x + list[i][0],
     9              y0 = y + list[i][1];
    10 
    11          // 这个点应该是空的
    12          var block = self.data[qc.Tetris.makePos(x0, y0)];
    13          block.value = value;
    14      }
    15  };

    3. 实现clearLine接口,干掉一行数据:

    1 // 干掉一行
    2  Board.prototype.clearLine = function(pts) {
    3      var self = this;
    4      pts.forEach(function(pos) {
    5          self.data[pos].value = 0;
    6      });
    7  };

    4. 实现getFullLines接口,将所有可以消除的行返回:

     1 // 取得可以消除的行
     2  Board.prototype.getFullLines = function() {
     3      var self = this,
     4          lines = [];
     5 
     6      // 横向9条线
     7      var pts = self.yLines;
     8      for (var i = 0; i < pts.length; i++) {
     9          var start = pts[i], end = [start[0] + start[2] - 1, start[1]];
    10          var ok = true;
    11          for (var x = start[0], y = start[1]; x <= end[0];) {
    12              var pos = qc.Tetris.makePos(x, y);
    13              if (self.data[pos].value === 0) {
    14                  // 不符合,不能消除
    15                  ok = false; break;
    16              }
    17 
    18              // 下一个点
    19              x++;
    20          }
    21          if (ok) {
    22              // 这条线可以消除,添加进来
    23              lines.push('y' + qc.Tetris.makePos(start[0], start[1]));
    24          }
    25      }
    26 
    27      // 右斜9条线
    28      var pts = self.xLines;
    29      for (var i = 0; i < pts.length; i++) {
    30          var start = pts[i], end = [start[0], start[1] + start[2] - 1];
    31          var ok = true;
    32          for (var x = start[0], y = start[1]; y <= end[1];) {
    33              var pos = qc.Tetris.makePos(x, y);
    34              if (self.data[pos].value === 0) {
    35                  // 不符合,不能消除
    36                  ok = false; break;
    37              }
    38 
    39              // 下一个点
    40              y++;
    41          }
    42          if (ok) {
    43              // 这条线可以消除,添加进来
    44              lines.push('x' + qc.Tetris.makePos(start[0], start[1]));
    45          }
    46      }
    47 
    48      // 左斜的9条线
    49      var pts = self.xyLines;
    50      for (var i = 0; i < pts.length; i++) {
    51          var start = pts[i], end = [start[1], start[0]];
    52          var ok = true;
    53          for (var x = start[0], y = start[1]; true;) {
    54              var pos = qc.Tetris.makePos(x, y);
    55              if (self.data[pos].value === 0) {
    56                  // 不符合,不能消除
    57                  ok = false; break;
    58              }
    59 
    60              // 下一个点
    61              if (end[0] > start[0]) {
    62                  x++, y--;
    63                  if (x > end[0]) break;
    64              }
    65              else {
    66                  x--, y++;
    67                  if (x < end[0]) break;
    68              }
    69          }
    70          if (ok) {
    71              // 这条线可以消除,添加进来
    72              lines.push('xy' + qc.Tetris.makePos(start[0], start[1]));
    73          }
    74      }
    75 
    76      return lines;
    77  };

    界面实现

    预先将所有的行创建出来,当行被删除时直接显示出来做动画表现。以下流程中,我们首先创建一个格子的预制,再创建一个行的预置。

    1. 在board节点下,创建Image对象,设置属性如下图:

    2.将新创建的block节点拖入Assets/prefab目录,创建预制。然后从场景中删除。

    3. 在board节点下,创建Node对象,设置属性如下图:

    4. 为节点挂载TweenAlpha动画组件,消失时需要淡出:

    • 透明度从1变化到0
    • 耗时0.5秒
    • 变化的曲线是:先平缓的做变化,然后在快速变化为0
    • 图片中from和to值设置反了,请手工设置下from=1,to=0

    5. 在Scripts/ui下创建脚本Line.js,控制行的绘制和表现:

      1 /**
      2   * 消除一行的表现界面
      3   */
      4  var LineUI = qc.defineBehaviour('qc.tetris.LineUI', qc.Behaviour, function() {
      5      var self = this;
      6 
      7      // 描述行的信息
      8      self.flag = 'xy';
      9      self.x = 0;
     10      self.y = 0;
     11  }, {
     12      blockPrefab: qc.Serializer.PREFAB
     13  });
     14 
     15  Object.defineProperties(LineUI.prototype, {
     16      /**
     17       * 取得行标记
     18       */
     19      key: {
     20          get: function() {
     21              return this.flag + qc.Tetris.makePos(this.x, this.y);
     22          }
     23      },
     24 
     25      /**
     26       * 取得本行的格子数量
     27       */
     28      count: {
     29          get: function() {
     30              return this.gameObject.children.length;
     31          }
     32      }
     33  });
     34 
     35  /**
     36   * 初始化行
     37   */
     38  LineUI.prototype.init = function(flag, start, end) {
     39      var self = this;
     40      self.flag = flag;
     41      self.x = start[0];
     42      self.y = start[1];
     43 
     44      // 创建一个格子
     45      var createBlock = function(pos) {
     46          var block = self.game.add.clone(self.blockPrefab, self.gameObject);
     47          block.frame = 'white.png';
     48          block.anchoredX = qc.Tetris.board.data[pos].x;
     49          block.anchoredY = qc.Tetris.board.data[pos].y;
     50          block.name = pos;
     51          return block;
     52      };
     53 
     54      switch (flag) {
     55      case 'xy':
     56          for (var x = self.x, y = self.y; true;) {
     57              createBlock(qc.Tetris.makePos(x, y));
     58 
     59              // 下一个点
     60              if (end[0] > start[0]) {
     61                  x++, y--;
     62                  if (x > end[0]) break;
     63              }
     64              else {
     65                  x--, y++;
     66                  if (x < end[0]) break;
     67              }
     68          }
     69          break;
     70 
     71      case 'y':
     72          for (var x = start[0], y = start[1]; x <= end[0];) {
     73              createBlock(qc.Tetris.makePos(x, y));
     74              x++;
     75          }
     76          break;
     77 
     78      case 'x':
     79          for (var x = start[0], y = start[1]; y <= end[1];) {
     80              createBlock(qc.Tetris.makePos(x, y));
     81              y++;
     82          }
     83      }
     84 
     85      // 初始时隐藏掉
     86      self.gameObject.name = self.key;
     87      self.gameObject.visible = false;
     88  };
     89 
     90  /**
     91   * 播放消失的动画
     92   */
     93  LineUI.prototype.playDisappear = function(index) {
     94      var self = this,
     95          o = self.gameObject,
     96          ta = self.getScript('qc.TweenAlpha');
     97 
     98      o.visible = true;
     99      o.alpha = 1;
    100 
    101      ta.delay = 0;
    102      ta.resetToBeginning();
    103      ta.onFinished.addOnce(function() {
    104          // 隐藏掉
    105          o.visible = false;
    106      });
    107      ta.playForward();
    108  };
    • flag和x、y属性描述了行的信息(左斜行、水平行还是右斜行,起始点的坐标)

    6. 将此脚本挂载到Line节点,并设置blockPrefab为第一步骤创建的格子预置:

    7. 将line拖进Assets/prefab目录,创建预制。然后从场景中删除。

    8. 在Scripts/ui创建脚本KillLineEffect.js,处理行消失表现的逻辑

     1 /**
     2   * 行消除的动画表现
     3   */
     4  var KillLineEffect = qc.defineBehaviour('qc.tetris.KillLineEffect', qc.Behaviour, function() {
     5      var self = this;
     6 
     7      /**
     8       * 所有的行
     9       */
    10      self.lines = {};
    11 
    12      /**
    13       * 两行之间的播放延迟
    14       */
    15      self.delay = 300;
    16  }, {
    17      delay: qc.Serializer.NUMBER,
    18      linePrefab: qc.Serializer.PREFAB
    19  });
    20 
    21  /**
    22   * 初始化:将用于表现的行全部创建出来放着
    23   */
    24  KillLineEffect.prototype.awake = function() {
    25      var self = this;
    26 
    27      // 创建用于消除表现的格子行
    28      var createLine = function(flag, start, end) {
    29          var ob = self.game.add.clone(self.linePrefab, self.gameObject);
    30          var line = ob.getScript('qc.tetris.LineUI');
    31          line.init(flag, start, end);
    32          self.lines[line.key] = line;
    33      };
    34      var pts = qc.Tetris.board.xyLines;
    35      for (var i = 0; i < pts.length; i++) {
    36          var start = pts[i], end = [start[1], start[0]];
    37          createLine('xy', start, end);
    38      }
    39 
    40      var pts = qc.Tetris.board.yLines;
    41      for (var i = 0; i < pts.length; i++) {
    42          var start = pts[i], end = [start[0] + start[2] - 1, start[1]];
    43          createLine('y', start, end);
    44 
    45      }
    46      var pts = qc.Tetris.board.xLines;
    47      for (var i = 0; i < pts.length; i++) {
    48          var start = pts[i], end = [start[0], start[1] + start[2] - 1];
    49          createLine('x', start, end);
    50      }
    51  };
    52 
    53  KillLineEffect.prototype.find = function(flag) {
    54      return this.lines[flag];
    55  };
    56 
    57  KillLineEffect.prototype.play = function(index, flag, score) {
    58      var self = this;
    59      var line = self.find(flag);
    60      var delay = index * self.delay;
    61 
    62      var playFunc = function() {
    63          // 冒出分数
    64          var children = line.gameObject.children;
    65          var pos = children[Math.round(children.length/2) - 1].name;
    66          self.getScript('qc.tetris.FlyScore').play(pos, score);
    67 
    68          // 消失动画
    69          line.playDisappear();
    70      };
    71      if (delay <= 0) {
    72          playFunc();
    73      }
    74      else {
    75          self.game.timer.add(delay, playFunc);
    76      }
    77  };
    • 在脚本初始化时,将所有行的数据构建出来,并隐藏掉
    • delay表示在多行消失时,其动画的间隔时间
    • 在动画表现时,有分数表现,FlyScore下一章再补充

    9. 将KillLineEffect挂载到board节点(棋盘),并设置linePrefab:

    10. 运行工程,就可以看到这些“行”了:

    11. 选中UIRoot节点,设置UIManager的Kill Line Effect Node属性(board节点,因为board挂载了KillLineEffect脚本):

     

     

    上一篇:JS开发HTML5游戏《神奇的六边形》(二) 

    下一篇:JS开发HTML5游戏《神奇的六边形》(四)

  • 相关阅读:
    Python3-shutil模块-高级文件操作
    Python3-sys模块-解释器相关参数与函数
    Python3-os模块-操作系统的各种接口
    Python3-算法-冒泡排序
    Python3-re模块-正则表达式
    Python3-算法-递归
    Python3-设计模式-迭代器模式
    Python3-设计模式-装饰器模式
    PTA 7-28 搜索树判断(镜像二叉搜索树的后序遍历)
    PTA 7-26 Windows消息队列(小顶堆+输入优化)
  • 原文地址:https://www.cnblogs.com/qici/p/4956345.html
Copyright © 2020-2023  润新知