触摸控制移动与缩放算法 - Cocos2d-JS + CocosBuilder

在移动设备上,一些游戏需要显示更大的场景,经常需要类似于部落冲突和沙漠帝国的场景控制方式,通过手指的划和捏来控制整个场景的移动和缩放。下面介绍一下我刚刚调试出来的这种控制算法。个人认为这种思路还是非常清晰,代码量较少并且获得的效果比较成熟。

我们需要几个层次的 node:(这是在 CocosBuilder 下面的截图)


控制系统依赖于图中的4个container,这是四个 size 为0的 CCNode,因为我们只利用它们的位置做文章。另外说明:我的 core_node 是用百分比对齐到屏幕中心的。

static_container - 位置相对控制系统是完全不变的,相当于控制系统的基准坐标
zoom_container - 用于控制场景缩放,缩放操作全部应用于此
move_container - 用于控制场景移动,移动操作全部应用于此
grid_container - 用于放置场景内容,*其实这个层完全可以被 move_container 替代,把内容直接放到 move_container上面即可,我个人这里为了思路清晰不这样做。

CCLabelTTF是在 zoom_container 的中心,调试时可以以此看出缩放中心的位置。

下面这三个函数是触摸触发的开始,注意里面的调用函数:

    onTouchesBegan: function (touches, event) {
        this.calcMove(touches);
    },

    onTouchesMoved: function (touches, event) {
        this.calcMove(touches);
    },

    onTouchesEnded: function (touches, event) {
        this.calcMove(touches);
        this.calcStop();
    },
下面是算法最最核心的部分:
    // 暂存上次 touch 的 id
    touch_ids: null,

    // 传入 touches 与上次的 touch 相比,返回 true 表示完全相同
    sameTouchesId: function (touches) {
        if (this.touch_ids == null) return false;
        if (touches.length != this.touch_ids.length) return false;
        if (touches[0] != null && touches[0].getId() != this.touch_ids[0]) return false;
        if (touches[1] != null && this.touch_ids[1] != touches[1].getId()) return false;
        return true;
    },

    // 构造暂存 touch_ids
    makeTouchesMap: function (touches) {
        this.touch_ids = [];
        if (touches.length == 1) {
            this.touch_ids[0] = touches[0].getId();
        } else if (touches.length >= 2) {
            this.touch_ids[0] = touches[0].getId();
            this.touch_ids[1] = touches[1].getId();
        }
    },

    // 计算并执行本次操作的动作
    calcMove: function (touches) {
        var t0, t1, t2;
        var tp0, tp1, tp2;
        var delta;
        var diff_zoom_center;
        var touches_distance_current;
        var touches_distance_previous;
        var container_pos;
        var container_scale;
        if (this.sameTouchesId(touches)) {
            if (touches.length >= 2) {
                t0 = touches[0].getLocation();
                t1 = touches[1].getLocation();
                t2 = cc.pMidpoint(t0, t1);
                touches_distance_current = Math.sqrt(Math.pow(t1.x - t0.x, 2) + Math.pow(t1.y - t0.y, 2));
                tp0 = touches[0].getPreviousLocation();
                tp1 = touches[1].getPreviousLocation();
                tp2 = cc.pMidpoint(tp0, tp1);
                touches_distance_previous = Math.sqrt(Math.pow(tp1.x - tp0.x, 2) + Math.pow(tp1.y - tp0.y, 2));

                diff_zoom_center = gLayer_DrawTest['static_container'].convertToNodeSpace(t2);
                cc.log('mid:' + JSON.stringify(diff_zoom_center));

                // 缩放变换
                container_scale = gLayer_DrawTest['zoom_container'].getScale();
                container_scale = container_scale * touches_distance_current / touches_distance_previous;
                gLayer_DrawTest['zoom_container'].setScale(container_scale);

                // 移动变换
                delta = cc.pSub(t2, tp2);
                delta = cc.pMult(delta, 1 / container_scale);// cc.p(delta.x / container_scale, delta.y / container_scale);
                container_pos = gLayer_DrawTest['move_container'].getPosition();
                container_pos = cc.pAdd(container_pos, delta);
                gLayer_DrawTest['move_container'].setPosition(container_pos);

                this.setRelateNode(t2);

            } else if (touches.length == 1) {
                t0 = touches[0].getLocation();
                tp0 = touches[0].getPreviousLocation();
                delta = cc.pSub(t0, tp0);
                container_scale = gLayer_DrawTest['zoom_container'].getScale();
                delta = cc.pMult(delta, 1 / container_scale);

                // 移动变换
                container_pos = gLayer_DrawTest['move_container'].getPosition();
                container_pos = cc.pAdd(container_pos, delta);
                gLayer_DrawTest['move_container'].setPosition(container_pos);

                this.setRelateNode(t0);
            }
        } else {
            this.makeTouchesMap(touches);
        }
    },

    // 设置 zoom_container 与 move_container 的相对位置关系
    setRelateNode: function (pos) {
        var diff_zoom_center = gLayer_DrawTest['static_container'].convertToNodeSpace(pos);
        cc.log('mid:' + JSON.stringify(diff_zoom_center));

        var static_container_pos = gLayer_DrawTest['static_container'].getPosition();
        var zoom_container_pos = gLayer_DrawTest['zoom_container'].getPosition();
        var diff_zoom = zoom_container_pos;
        zoom_container_pos = cc.pAdd(static_container_pos, diff_zoom_center);
        gLayer_DrawTest['zoom_container'].setPosition(zoom_container_pos);

        var container_scale = gLayer_DrawTest['zoom_container'].getScale();
        diff_zoom = cc.pSub(zoom_container_pos, diff_zoom);
        diff_zoom = cc.pMult(diff_zoom, 1 / container_scale);
        var move_container_pos = gLayer_DrawTest['move_container'].getPosition();
        move_container_pos = cc.pSub(move_container_pos, diff_zoom);
        gLayer_DrawTest['move_container'].setPosition(move_container_pos);
    },

    // 停止本次计算
    calcStop: function () {
        this.touch_ids = null;
    }

  1. 通过两次调用onTouchesXXX 获得的不同touches结果来确定有效命令,如果紧邻的两次 onTouchesXXX 得到的是不同的 touches 则判定为非法命令,不予理睬;
  2. 当判定为非法命令时,需要重建 touches 暂存;合法命令可省去此步骤,因为第一次一定为非法命令,其后的touches暂存也都不会变化;
  3. 缩放变换控制zoom_container,移动变换控制 move_container;
  4. 一点触摸时只控制移动,此时点距为0,触摸中心为唯一这个点;两点触摸是同时控制移动与缩放,点距为两点距离,触摸中心为两点中点;
  5. 通过对比触摸中心的上次与此次位置来移动 move_container, 通过对比两点距离的差异变换zoom_container的缩放系数;
  6. 每次触摸操作需要将触摸中心与基准坐标(static_container)的坐标做对比,将 zoom_container 的移动到这个这个点。这样做是为了将缩放时的锚点对准触摸中心;
  7. 对准触摸中心的函数是setRelateNode。当执行 zoom_container 进行对准时,其中的 move_container 必然会造成偏移的影响,需要对 move_container 进行反向偏移;
  8. 反向偏移时应考虑 zoom_container 的缩放系数影响;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值