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

近期出现一款魔性的消除类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游戏《神奇的六边形》(五)


转载于:https://my.oschina.net/u/2510595/blog/531592

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值