近期出現一款魔性的消除類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腳本):