近期出现一款魔性的消除类HTML5游戏《神奇的六边形》,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏。
(点击这里可进入游戏体验)
因内容太多,为方便大家阅读,所以分成八部分来讲解。
本文为第四部分,主要包括:
12. 形状的拖放处理
13.形状放入棋盘的实现
若要一次性查看所有文档,也可点击这里。
形状在被按下时,需要变大,如果是手机上需要向上做一定的位置偏移。拖拽时形状应该跟着鼠标或手指进行移动。
修改脚本Scripts/ui/BlocksUI.js,添加如下代码:
1. 修改reset函数,增加放大区块的逻辑:
BlocksUI.prototype.reset = function(fixToBoard) { var self = this, o = self.gameObject; for (var pos in self._blocks) { var p = qc.Tetris.readPos(pos); var pt = qc.Tetris.board.toWorld(p, fixToBoard ? qc.Tetris.BLOCK_H : qc.Tetris.POOL_DISTANCE_NORMAL); var block = self._blocks[pos]; block.anchoredX = pt.x; block.anchoredY = pt.y; var scale = fixToBoard ? 1.13 : 1; block.find('shadow').scaleX = scale; block.find('shadow').scaleY = scale; block.find('block').scaleX = scale; block.find('block').scaleY = scale; } };
2. 添加按下的逻辑处理,放大区块:
* 鼠标按下:放大区块 */ BlocksUI.prototype.onDown = function(e) { var self = this, o = self.gameObject; self.drop = false; self.reset(true); // 在手机下,需要往上做点偏移 o.y -= self.offsetY; };
drop标记当前区块是否被放到棋盘了,刚开始按下清理下环境
按下时需要向上做偏移offsetY
3. 添加鼠标松开或触摸结束的处理,还原区块的位置和大小:
/** * 鼠标松开:重置区块大小 */ BlocksUI.prototype.onUp = function() { var self = this; self.reset(); };
4. 添加开始拖拽的处理:
/** * 拖拽开始 */ BlocksUI.prototype.onDragStart = function(e) { var self = this; self.drop = false; self.drag = true; self.lastPos = ''; self.game.input.nativeMode = true; self.reset(true); self.game.log.trace('Start drag:{0}', self.index); // 复制出可放入标记 var ob = self.flagBlocks = self.game.add.clone(self.gameObject, qc.Tetris.boardUI.gameObject); ob.children.forEach(function(block) { block.find('shadow').visible = false; var b = block.find('block'); b.width = qc.Tetris.BLOCK_W; b.height = qc.Tetris.BLOCK_H; b.scaleX = 1; b.scaleY = 1; b.frame = 'dark' + b.frame; }); ob.scaleX = 1; ob.scaleY = 1; ob.interactive = false; self.hideFlag(); };
初始时,标记正在拖拽(drag = true),并且没有被放下(drop = false)
当拖拽到棋盘时,需要实时指示是否可以放下本形状。拖拽开始先清理下最近一次检测的逻辑坐标点(last = '')
设置输入模式nativeMode = true。确保输入事件能被实时处理(默认情况下延后一帧处理,运行效率比较高),本游戏对拖拽的实时响应比较重要。
拖拽开始时,放大并偏移形状(和鼠标按下的逻辑一样)
后续的逻辑:另外复制出本形状,并隐藏掉。这个形状在后续拖拽中,会在棋盘显示出来以指示当前是否可以放入。这个指示的格子图片,使用暗色的图片。
5. 添加拖拽的处理,每帧都会进行调度:
/** * 拖拽中 */ BlocksUI.prototype.onDrag = function(e) { var self = this, o = self.gameObject; if (self.drag) { // 改变节点的目标位置 var p = o.getWorldPosition(); p.x += e.source.deltaX; p.y += e.source.deltaY; var lp = o.parent.toLocal(p); o.x = lp.x; o.y = lp.y; // 计算当前对应棋盘中心点的偏移 var board = qc.Tetris.boardUI.gameObject; p = board.toLocal(p); p.y += board.height * 0.5; // 反算出对应的归一化坐标 var xy = qc.Tetris.board.toLocal(p); var x = Math.round(xy.x), y = Math.round(xy.y), pos = qc.Tetris.makePos(x, y); if (self.lastPos !== pos) { self.lastPos = pos; if (qc.Tetris.board.data[pos] && qc.Tetris.board.checkPutIn(pos, self.data.list)) { self.showFlag(pos); } else { self.hideFlag(); } } } };
在拖拽的事件e中,会指明本帧到上一帧的移动偏移量(屏幕坐标),本形状加上屏幕坐标偏移,这样就移动起来了
然后计算本形状的中心点,对应到棋盘的逻辑坐标。并检查目标是否可以放入,如果可以就需要显示指示
最近一次检测的逻辑坐标需要记录下来,防止每帧都对同一逻辑坐标检查是否可以放入(白耗CPU)
6. 打开脚本Scripts/logic/Board.js,实现checkPutIn方法:
Board.prototype.checkPutIn = function(pos, list) { var self = this; var pt = qc.Tetris.readPos(pos), x = pt.x, y = pt.y; for (var i = 0; i < list.length; i++) { var x0 = x + list[i][0], y0 = y + list[i][1]; // 这个点应该是空的 var block = self.data[qc.Tetris.makePos(x0, y0)]; if (!block) return false; if (block.value !== 0) return false; } return true; };
7. 继续打开Scripts/ui/Blocks.js,继续实现拖拽结束的逻辑:
/** * 拖拽结束 */ BlocksUI.prototype.onDragEnd = function(e) { var self = this, o = self.gameObject; self.drag = false; if (self.flagBlocks.visible && self.lastPos) { // 放到这个位置中去 self.drop = true; qc.Tetris.operation.putIn(self.index, self.lastPos, self.data); } else { self.reset(); o.parent.getScript('qc.tetris.Pool').resize(); } // 显示标记可以干掉了 self.flagBlocks.destroy(); delete self.flagBlocks; }; /** * 隐藏指示标记 */ BlocksUI.prototype.hideFlag = function() { this.flagBlocks.visible = false; }; /** * 显示指示标记 */ BlocksUI.prototype.showFlag = function(pos) { this.flagBlocks.visible = true; var pt = qc.Tetris.board.data[pos]; this.flagBlocks.anchoredX = pt.x; this.flagBlocks.anchoredY = pt.y; };
拖拽结束后,需要判定形状是否被放入目标节点
如果可以放入,则调用指令:qc.Tetris.operation.putIn(下步骤实现)
如果不能放入,则需要将位置和大小等还原
最后,指示对象需要被析构
8. 在Scripts/operation创建文件PutIn.js,实现放入形状指令:
/** * 请求放入指定格子,如果成功放入返回true,否则返回false */ qc.Tetris.operation.putIn = function(index, pos) { // TODO: 逻辑待实现 };
9. 在Blocks.js中,我们使用到了棋盘对象:qc.Tetris.boardUI.gameObject,但目前这个值(BoardUI)尚未被赋值。
打开BoardUI.js,在构造函数中加入代码赋值:
var BoardUI = qc.defineBehaviour('qc.tetris.BoardUI', qc.Behaviour, function() { var self = this; // 登记下本对象 qc.Tetris.boardUI = self; /** * 棋盘的棋子元素 */ self.pieces = {}; ...
10. 运行测试下,形状可以随意拖拽了,并且可以反弹回原来位置。不过还无法放入(因为PutIn我们还没实现),请继续后面教程。
处理流程如下图:
打开文件Scripts/operation/PutIn.js,实现上述代码:
/** * 请求放入指定格子,如果成功放入返回true,否则返回false */ qc.Tetris.operation.putIn = function(index, pos) { var shape = qc.Tetris.Shapes.pool[index], board = qc.Tetris.board, ui = qc.Tetris.game.ui, log = qc.Tetris.game.log; log.trace('尝试将({0})放入({1})', index, pos); if (!board.checkPutIn(pos, shape.list)) { // 禁止放入 return false; } log.trace('放入格子:({0})', pos); // 更新棋盘信息 board.putIn(pos, shape.list, shape.value); // 计算可以消除的行,并同时消除掉 var lines = board.getFullLines(); lines.forEach(function(flag) { var children = ui.killLineEffect.find(flag).gameObject.children; var pts = []; children.forEach(function(child) { pts.push(child.name); }) board.clearLine(pts); }); // 计算分数明细,并添加之 var scoreDetail = qc.Tetris.operation.calcScore(lines); qc.Tetris.score.current += scoreDetail.total; // 替换为新的形状 qc.Tetris.Shapes.pool.splice(index, 1); qc.Tetris.Shapes.pool.push(qc.Tetris.Shapes.random()); // 重新绘制棋盘 ui.board.redraw(); // 行消除与分数增加的动画表现 if (lines.length > 0) { for (var i = 0; i < lines.length; i++) { ui.killLineEffect.play(i, lines[i], scoreDetail.lines[i]); } } else { ui.board.getScript('qc.tetris.FlyScore').play(pos, scoreDetail.total); } // 当前分数的动画表现 ui.currentScore.setScore(); // 形状飞入的动画表现,并将旧的形状删除掉 ui.pool.remove(index); ui.pool.add(2); ui.pool.flyIn(index); // 死亡检测 if (board.die) { // 延迟显示死亡界面 log.trace('Game Over!'); qc.Tetris.game.timer.add(3000, function() { ui.onDie(); }); } // 放入成功了 return true; }; /** * 计算分数明细 * total: 总分数 * lines: [各行的分数] */ qc.Tetris.operation.calcScore = function(lines) { var scores = { total: 40, lines: [] }; if (lines.length < 1) return scores; // 计算加成 var append = Math.max(0, lines.length - 1 * 10); for (var i = 0; i < lines.length; i++) { var flag = lines[i]; var line = qc.Tetris.game.ui.killLineEffect.find(flag); var len = line.gameObject.children.length; scores.lines[i] = len * 20 + append * len; scores.total += scores.lines[i]; // 40合并到第一行去做表现 if (i === 0) { scores.lines[i] += 40; } } return scores; };
calcScore方法为计算分数的逻辑
代码中出现了qc.Tetris.game.ui(即UIManager),在下文中陆续实现
另外,本逻辑中加入了一些动画表现,在下文中也陆续实现之
先大致理解下处理流程,细节可以后续章节中逐一理解
上一篇:JS开发HTML5游戏《神奇的六边形》(三)
下一篇:JS开发HTML5游戏《神奇的六边形》(五)