近期出现一款魔性的消除类HTML5游戏《神奇的六边形》,今天我们一起来看看如何通过开源免费的青瓷引擎()来实现这款游戏。
(点击图片可进入游戏体验)
因内容太多,为方便大家阅读,所以分成四部分来讲解。
本文为第三部分,主要包括:
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.PREFAB20 });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,否则返回false3 */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.PREFAB18 });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.PREFAB19 });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脚本):