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游戲《神奇的六邊形》(四)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM