cocos2d-x 图片平移缩放组件

来源:互联网 发布:仿淘宝手机商品详情页 编辑:程序博客网 时间:2024/05/21 05:23

最近需要实现游戏地图的平移缩放功能,想到这是一个常用的功能,应该有现成的组件,可是到处找也没找到合适完美的实现,干脆自己写一个,经过一些测试没有问题,共享出来,有什么问题欢迎大家提出来继续完善。基于cocos2d-x 3.7.1,ide为vs2013。

使用方法:

PanZoomController panzoom;

panzoom.start(your node or layer or sprite);


PanZoomController.h

#ifndef __PAN_ZOOM_CONTROLLER_H__#define __PAN_ZOOM_CONTROLLER_H__#include "cocos2d.h"USING_NS_CC;// 当前拖动或缩放状态enum PanZoomState{None, // 无状态Pan, // 拖动平移Zoom, // 缩放Rebound // 弹回动画中};/** * 触控平移缩放操作控件 */class PanZoomController : public Ref{public:PanZoomController();~PanZoomController();/** * @param node 要控制的节点 * @param enableRebound 是否启用拖动到边缘时的弹回效果 */void start(Node* node, bool enableRebound = false);protected:// 要控制的节点Node* _node;// 一次连续缩放操作是否初始化bool _scaleInited = false;// 拖动到边缘时允许的黑边宽度 单位 像素点float _blackBorder = 50.0f;// 回弹动画播放时间 单位 秒float _reboundTime = 0.2f;// 当前拖动或缩放状态PanZoomState _state = PanZoomState::None;// 是否启用拖动到边缘时的弹回效果bool _enableRebound = false;// 最小缩放倍率float _minScale = 0.5;// 最大缩放倍率float _maxScale = 2;Director* _director;Vec2 _visibleOrigin;Size _visibleSize;void onTouchesBegan(const std::vector<Touch*>& touches, Event *event);void onTouchesMoved(const std::vector<Touch*>& touches, Event *event);void onTouchesEnded(const std::vector<Touch*>& touches, Event *event);void onTouchesCancelled(const std::vector<Touch*>& touches, Event *event);// 回弹动画结束void onReboundEnd();/** * 平移后有黑边则移动图片对齐屏幕,获取移动相对偏移值 * @param minXY maxXY node's boundingBox's minXY in opengl coordinate * @return delta position  */ Vec2 getDeltaPosition(Vec2 minXY, Vec2 maxXY);std::vector<long> times;};#endif // __PAN_ZOOM_CONTROLLER_H__

PanZoomController.cpp

#include "PanZoomController.h"PanZoomController::PanZoomController(){}PanZoomController::~PanZoomController(){}void PanZoomController::start(Node* node, bool enableRebound /* = false */){_node = node;_director = Director::getInstance();_visibleOrigin = _director->getVisibleOrigin(); // 注意应该使用visibleOrigin而不是0,0_visibleSize = _director->getVisibleSize(); // 这里应该使用visibleSize而不是winSize// Layer's ignoreAnchorPointForPosition is true by default,set it false to support zoom by anchorpoint // Layer默认是忽略锚点的,但是缩放时需要改变锚点if (_node->isIgnoreAnchorPointForPosition()){_node->ignoreAnchorPointForPosition(false);}// event initauto listener = EventListenerTouchAllAtOnce::create();listener->onTouchesBegan =CC_CALLBACK_2(PanZoomController::onTouchesBegan,this);listener->onTouchesMoved =CC_CALLBACK_2(PanZoomController::onTouchesMoved,this);if (enableRebound){listener->onTouchesEnded = CC_CALLBACK_2(PanZoomController::onTouchesEnded, this);listener->onTouchesCancelled = CC_CALLBACK_2(PanZoomController::onTouchesCancelled, this);}else{_blackBorder = 0;}_node->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, _node);}void PanZoomController::onTouchesBegan(const std::vector<Touch*>& touches, Event *event){_scaleInited = false;CCLOG("-------------------------------------------");Vec2 pt_gl = touches[0]->getLocation();Vec2 pt_node = _node->convertToNodeSpace(pt_gl);CCLOG("onTouchesBegan pt gl %f,%f", pt_gl.x, pt_gl.y);CCLOG("pt node %f,%f", pt_node.x, pt_node.y);if (times.size() > 0){long total = 0;for (auto it = times.begin(); it != times.end(); ++it){total += *it;}long avg = total / times.size();CCLOG("times %d avg time %d", times.size(), avg);}}#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)// win32下手动增加一个触点,模拟两点触摸void PanZoomController::onTouchesMoved(const std::vector<Touch*>& touches2, Event *event){std::vector<Touch*> touches;Touch* touch1 = touches2[0];Touch* touch2 = new Touch();Vec2 pt(400, 200);pt = Director::getInstance()->convertToUI(pt);touch2->setTouchInfo(touch1->getID() + 1, pt.x, pt.y);touches.push_back(touch1);touches.push_back(touch2);#elsevoid PanZoomController::onTouchesMoved(const std::vector<Touch*>& touches, Event *event){#endif // (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)struct timeval t1;gettimeofday(&t1, nullptr);if (_state == PanZoomState::Rebound){//CCLOG("rebounding skip");return;}if (touches.size() == 2) // 缩放{_state = PanZoomState::Zoom;// 获取当前两触摸点auto point1_gl = touches[0]->getLocation();auto point2_gl = touches[1]->getLocation();//CCLOG("gl p1 %f,%f p2 %f,%f", point1_gl.x, point1_gl.y, point2_gl.x, point2_gl.y);if (!_scaleInited) // 第一次初始化{_scaleInited = true;//CCLOG("scale init");// 计算两触摸点的中点,_gl=opengl坐标系,_node=节点坐标系auto midPoint_gl = (point1_gl + point2_gl) / 2;//CCLOG("midPoint_gl=%f,%f", midPoint_gl.x, midPoint_gl.y);auto midPoint_node = _node->convertToNodeSpace(midPoint_gl);// 计算新的锚点Vec2 newAnchor;newAnchor.x = midPoint_node.x / _node->getContentSize().width;newAnchor.y = midPoint_node.y / _node->getContentSize().height;//CCLOG("newAnchor = %f,%f", newAnchor.x, newAnchor.y);// 设置新的锚点并修正坐标,为了使图片不动,新的坐标与旧坐标刚好为前后两个锚点之差Vec2 prevAnchorPointInPoints = _node->getAnchorPointInPoints();_node->setAnchorPoint(newAnchor);_node->setPosition(_node->getPosition() + (_node->getAnchorPointInPoints() - prevAnchorPointInPoints) * _node->getScale());// 关键,必须要乘以scale,因为anchorPointInPoints是用contentSize原始大小(不会随着scale改变)乘以anchorPoint得到的,未考虑scale因素//CCLOG("new position=%f,%f", _layer->getPositionX(), _layer->getPositionY());}// 计算两点当前和上一次的距离auto currDistance = point1_gl.distance(point2_gl);auto prevDistance = touches[0]->getPreviousLocation().distance(touches[1]->getPreviousLocation());//CCLOG("currDistance=%f prevDistance=%f", currDistance, prevDistance);// 根据两点前后的距离计算缩放倍率auto curScale = _node->getScale();auto newScale = curScale * (currDistance / prevDistance);// 缩放极值控制newScale = MIN(_maxScale, MAX(_minScale, newScale));if (newScale == curScale){//CCLOG("same scale, return");return;}// 缩放_node->setScale(newScale);//CCLOG("new scale %f", newScale);if (newScale < curScale){// 判断缩“小”后是否会小于屏幕,getBoundingBox返回缩放后的实际大小,minXY=左下角坐标,maxXY=右上角坐标Rect box = _node->getBoundingBox();Vec2 minXY = _node->getParent()->convertToWorldSpace(box.origin);Vec2 maxXY = minXY + Vec2(box.size);//if (minXY.x > visOrigin.x + _blackBorder || minXY.y > visOrigin.y + _blackBorder ||//maxXY.x < (visOrigin.x + visSize.width - _blackBorder) || maxXY.y < (visOrigin.y + visSize.height - _blackBorder))if (box.size.width < _visibleSize.width || box.size.height < _visibleSize.height){//CCLOG("bounding box small than screen no scale");_node->setScale(curScale); // 恢复到原来的缩放值}else{// 缩小后大于屏幕但是有黑边则平移对齐屏幕边缘Vec2 posDelta = getDeltaPosition(minXY, maxXY);_node->setPosition(_node->getPosition() + posDelta);//CCLOG("zoom out, black border, pan delta %f,%f", posDelta.x, posDelta.y);}}}else if (touches.size() == 1) // 单点进行移动{_state = PanZoomState::Pan;//CCLOG("Pan state");auto newPos = _node->getPosition() + touches[0]->getDelta();Vec2 curPos = _node->getPosition();_node->setPosition(newPos);// 获取设置到新坐标后的实际大小及坐标,然后进行黑边判断和纠正Rect box = _node->getBoundingBox();Vec2 minXY = _node->getParent()->convertToWorldSpace(box.origin);Vec2 maxXY = minXY + Vec2(box.size);// x方向黑边判断if (minXY.x > _visibleOrigin.x + _blackBorder || maxXY.x < (_visibleOrigin.x + _visibleSize.width - _blackBorder)){//CCLOG("x black screen no move");newPos.x = curPos.x;}// y方向黑边判断if (minXY.y > _visibleOrigin.y + _blackBorder || maxXY.y < (_visibleOrigin.y + _visibleSize.height - _blackBorder)){//CCLOG("y black screen no move");newPos.y = curPos.y;}_node->setPosition(newPos);}struct timeval t2;gettimeofday(&t2, nullptr);long t3 = t2.tv_usec - t1.tv_usec;times.push_back(t3);CCLOG("sec %ld t3 %ld", t1.tv_sec, t3);}void PanZoomController::onTouchesEnded(const std::vector<Touch*>& touches, Event *event){CCLOG("onTouchesEnded");if (_state == PanZoomState::Rebound){CCLOG("rebounding, skip");return;}Rect box = _node->getBoundingBox();Vec2 minXY = _node->getParent()->convertToWorldSpace(box.origin);Vec2 maxXY = minXY + Vec2(box.size);float scaleStart = _node->getScale();float scaleEnd = 0; // 回弹到的缩放值Vec2 posDelta = Vec2::ZERO; // 回弹需要的偏移相对坐标值if (_state == PanZoomState::Zoom){// 缩放后小于屏幕则动画恢复到铺满屏幕if (box.size.width < _visibleSize.width){scaleEnd = _visibleSize.width / box.size.width * scaleStart;CCLOG("width < %f", scaleEnd);}if (box.size.height < _visibleSize.height){float scaleEndTmp = _visibleSize.height / box.size.height * scaleStart;scaleEnd = scaleEndTmp > scaleEnd ? scaleEndTmp : scaleEnd;CCLOG("height < %f", scaleEnd);}if (scaleEnd != 0){// 获取缩放后的大小及坐标供平移处理使用_node->setScale(scaleEnd);box = _node->getBoundingBox();minXY = _node->getParent()->convertToWorldSpace(box.origin);maxXY = minXY + Vec2(box.size);_node->setScale(scaleStart);}}// 平移黑边处理posDelta = getDeltaPosition(minXY, maxXY);// 构建回弹动画Vector<FiniteTimeAction*> actions;if (scaleEnd != 0){actions.pushBack(ScaleTo::create(_reboundTime, scaleEnd));CCLOG("ScaleTo %f", scaleEnd);}if (posDelta.x != 0 || posDelta.y != 0){actions.pushBack(MoveBy::create(_reboundTime, posDelta));CCLOG("MoveBy %f,%f", posDelta.x, posDelta.y);}if (actions.size() > 0){_node->runAction(Sequence::createWithTwoActions(Spawn::create(actions), CallFunc::create(this, CC_CALLFUNC_SELECTOR(PanZoomController::onReboundEnd))));_state = PanZoomState::Rebound;}}void PanZoomController::onTouchesCancelled(const std::vector<Touch*>& touches, Event *event){CCLOG("onTouchesCancelled");}void PanZoomController::onReboundEnd(){_state = PanZoomState::None;}Vec2 PanZoomController::getDeltaPosition(Vec2 minXY, Vec2 maxXY){Vec2 posDelta(0, 0);if (minXY.x > _visibleOrigin.x){posDelta.x = -(minXY.x - _visibleOrigin.x);//CCLOG("min x %f", posDelta.x);}else if (maxXY.x < _visibleOrigin.x + _visibleSize.width){posDelta.x = _visibleOrigin.x + _visibleSize.width - maxXY.x;//CCLOG("max x %f", posDelta.x);}if (minXY.y > _visibleOrigin.y){posDelta.y = -(minXY.y - _visibleOrigin.y);//CCLOG("min y %f", posDelta.y);}else if (maxXY.y < _visibleOrigin.y + _visibleSize.height){posDelta.y = _visibleOrigin.y + _visibleSize.height - maxXY.y;//CCLOG("max y %f", posDelta.y);}return posDelta;}

参考了这篇文章

怎样制作基于Cocos2d-x的SLG游戏-第2章(双指缩放,单指拖动的实现)

renshan

http://cn.cocos2d-x.org/tutorial/show?id=1479

0 0
原创粉丝点击