触摸控制移动与缩放算法 - 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 的中心,调试时可以以此看出缩放中心的位置。
下面这三个函数是触摸触发的开始,注意里面的调用函数:
通过两次调用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 的缩放系数影响;
我们需要几个层次的 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 的缩放系数影响;
相关文章推荐
- 【cocos2d-js教程】cocos2d-js中使用cocosbuilder绑定变量和Selector
- cocos2d-x js binding tips 1 使用cocosbuilder导入jsb后无法响应触摸的问题
- 在cocos2d-js 中 手动播放cocos builder 编辑的动画
- cocos2d-x&cocosbuilder折腾记
- cocos2d-x与CocosBuilder笔记:HelloCocosBuilder
- [ IOS-Cocos2d-x 游戏开发] - cocosBuilder 开发之三
- cocos2d-x 将cocosbuilder输出文件映射成对象的原理
- cocos2d-x绑定这个狗屎的cocosbuilder的方法!!!
- cocos2d-x-2.0.4中使用CocosBuilder创建动画注意的问题
- Cocos2d-x CocosBuilder使用教程(二)连接自定义类
- 关于Cocos2d-x2.1.x与CocosBuilder3.0a 的自动资源目录摆放
- cocos2d-x2.1 + cocosbuilder3.0 的自动生成C++类的修改
- Cocos2d场景编辑器CocosBuilder使用教程
- CCBAnimationManager的使用:使用cocos2d-x程序控制,由cocosBuilder生成cbbi中的动画
- cocos2d-x+lua+cocosbuilder+luaproxy开发中遇到的读取子node以及事件绑定问题
- cocos2d-x tips(二)关于cocosbuilder中的动画和2dx的action同时正常运行的问题
- 使用CocosBuilder2.1结合cocos2d-x2.0.3创建动画场景
- 使用CocosBuilder2.1结合cocos2d-x2.0.3创建动画场景
- Cocos2d-x CocosBuilder使用教程(一)HelloCocosBuilder
- cocos2d-x 将cocosbuilder输出文件映射成对象的原理