您的位置:首页 > 移动开发 > Cocos引擎

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

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

我们需要几个层次的 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;
}


通过两次调用onTouchesXXX 获得的不同touches结果来确定有效命令,如果紧邻的两次 onTouchesXXX 得到的是不同的 touches 则判定为非法命令,不予理睬;
当判定为非法命令时,需要重建 touches 暂存;合法命令可省去此步骤,因为第一次一定为非法命令,其后的touches暂存也都不会变化;
缩放变换控制zoom_container,移动变换控制 move_container;
一点触摸时只控制移动,此时点距为0,触摸中心为唯一这个点;两点触摸是同时控制移动与缩放,点距为两点距离,触摸中心为两点中点;
通过对比触摸中心的上次与此次位置来移动 move_container, 通过对比两点距离的差异变换zoom_container的缩放系数;
每次触摸操作需要将触摸中心与基准坐标(static_container)的坐标做对比,将 zoom_container 的移动到这个这个点。这样做是为了将缩放时的锚点对准触摸中心;
对准触摸中心的函数是setRelateNode。当执行 zoom_container 进行对准时,其中的 move_container 必然会造成偏移的影响,需要对 move_container 进行反向偏移;
反向偏移时应考虑 zoom_container 的缩放系数影响;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: