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

cocos2d-x 图片平移缩放组件

2015-09-06 10:23 471 查看
最近需要实现游戏地图的平移缩放功能,想到这是一个常用的功能,应该有现成的组件,可是到处找也没找到合适完美的实现,干脆自己写一个,经过一些测试没有问题,共享出来,有什么问题欢迎大家提出来继续完善。基于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 init
auto 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);
#else
void 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 = g
4000
etDeltaPosition(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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cocos2d-x