本篇文章,你将学习到如下知识点:
(1)如何使用touch事件拖拽精灵的基本方法
(2)如何通过touch事件来滚动视图本身
(3)如何方便地计算坐标
新建一个工程,名为“DragSprite”。打开HelloWorldScene.h,增加三个成员变量:
public: cocos2d::Sprite* background; //背景图片 cocos2d::Sprite* selSprite; //当前选中的精灵 cocos2d::Vector<cocos2d::Sprite*> movableSprites; //一个在处理touch事件时需要移动的精灵的数组
bool HelloWorld::init() { if ( !Layer::init() ) { return false; } Size winSize = Director::getInstance()->getWinSize(); Texture2D::setDefaultAlphaPixelFormat(Texture2D::PixelFormat::RGB565); background = Sprite::create("blue-shooting-stars.png"); background->setAnchorPoint(Point(0, 0)); this->addChild(background); Texture2D::setDefaultAlphaPixelFormat(Texture2D::PixelFormat::DEFAULT); std::string images[] = {"bird.png", "cat.png", "dog.png", "turtle.png"}; int images_length = 4; for(int i =0; i < images_length; ++i) { std::string image = images[i]; Sprite *sprite = Sprite::create(image); float offsetFraction = ((float)(i+1))/(images_length+1); sprite->setPosition(winSize.width*offsetFraction, winSize.height/2); this->addChild(sprite); movableSprites.pushBack(sprite); } selSprite = NULL; return true; }
这段代码由两部分组成:
(1)加载背景
代码的第一部分加载了一张背景图片(blue-shooting-stars.png)。要注意的是,这里把图片的锚点(anchor point)设置为(0,0),即图片的左下角。之前我们也介绍过关于锚点的知识。这里我们针对具体的实例再说明一下。当你设置一个精灵的位置的时候,实际上,你设置的是这个精灵的锚点的位置。默认情况下,图片的锚点就是图片的中心。因此,通过把精灵锚点设置为左下角,而这个方法并没有设置背景的位置,即背景的位置默认情况下是(0,0),所以图片的左下角就位于屏幕的左下角。这个图片有1600个像素宽,那么超过的部分就在屏幕之外了。
还有一点需要注意,就是在加载图片之前,转换了一下像素格式。在默认情况下,cocos2d里面加载图片,它们是作为32位的图片加载进来的。这意味着每个像素占4个字节的内存空间。当你需要非常高质量的显示效果时,就很合适。但是,有时候你并不需要,因为以前的设备内存很有限,如果全部使用32的像素格式来加载图片的话,会造成内存消耗过多。当你加载大的图片的时候(比如背景图片),最佳实践是使用16位的像素格式来加载,也就是牺牲一点质量来减少内存开销。cocos2d里面有很多不同的像素格式,这里我们选择16位的像素格式,RGB565,因为背景一般不需要透明效果。(少了Alpha,RGBA就是有Alpha)
(2)加载图片
init方法的另外一部分,就是循环遍历一个图片数组,然后创建精灵并且计算精灵放置的坐标。这些精灵会按顺序排开,显示在屏幕上。同时把这些精灵的引用保存在movableSprites数组里面,后续会用到。
编译并运行,就可以看到一排可爱的小动物啦,在等待你touch。
打开HelloWorldScene.cpp在init方法的最后添加如下代码:
auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true); listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this); listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this); listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
接下来我们来实现这些函数
bool HelloWorld::onTouchBegan(Touch* touch, Event* event) { Point touchLocation = this->convertTouchToNodeSpace(touch); this->selectSpriteForTouch(touchLocation); return true; } void HelloWorld::onTouchMoved(Touch* touch, Event* event) { } void HelloWorld::onTouchEnded(Touch* touch, Event* event) { } void HelloWorld::selectSpriteForTouch(Point touchLocation) { Sprite * newSprite = NULL; for (Sprite *sprite : movableSprites) { if ( sprite->getBoundingBox().containsPoint(touchLocation) ) { newSprite = sprite; break; } } if (newSprite != selSprite && NULL != newSprite) { if (NULL != selSprite) { selSprite->stopAllActions(); selSprite->runAction(RotateTo::create(0.1, 0)); } RotateBy * rotLeft = RotateBy::create(0.1, -14.0); RotateBy * rotCenter = RotateBy::create(0.1, 0.0); RotateBy * rotRight = RotateBy::create(0.1, 14.0); Sequence * rotSeq = Sequence::create(rotLeft, rotCenter, rotRight, rotCenter, NULL); newSprite->runAction(RepeatForever::create(rotSeq)); selSprite = newSprite; } }
这里的selectSpriteForTouch方法,遍历movableSprites数组中的所有精灵,查找第一个精灵位置为touch点位置相交的精灵。使用getBoundingBox函数和containsPoint,前一个函数可以返回精灵的边界矩形,后一个函数判断点击的点是否包含在矩形中。
如果找到一个匹配的精灵,那么就让这个精灵执行一些动画,这样用户就知道哪个精灵被选中了。如果动画还没执行完,又选中另一个精灵了,那么就终端前一个精灵的动画。这里的动画效果,使用了一系列的Action来实现的。
最后,onTouchBegan方法基于用户的touch事件调用上面的方法。注意,这里把touch坐标点转换成了结点坐标系。为了实现这个目的,通过调用Node的一个辅助函数,convertTouchToNodeSpace。这个方法做了以下两件事情:
(1)得到touch视图(也就是屏幕)的touch点位置为OpenGL坐标点(使用getLocation方法)
(2)转换OpenGL坐标系为指定结点的坐标系(使用convertToNodeSpace方法)
这是一个非常常用的转换方法,所以提供这个方法可以节约很多事件。
编译并运行,并且用手触摸这些动物。当你点中一个精灵的时候,它就会以一种非常可爱的方式旋转,表明它被你选中了。
是时候让小动物移动了!基本的思想就是实现onTouchMoved回调函数,然后计算本次touch点到上一次touch点之间的距离。如果一个动物被选中,将按照计算出来的touch偏移量来移动它。如果动物没有被选中,那么就移动整个层,因此用户可以从左至右滚动层。
在编写代码之前,我们先花点时间来讨论一下如何滚动一个层。
先看下面的两张图片:
如我们上面所将的,背景的锚点在左下角(0,0),那么背景的其他部分就在屏幕之外。黑色框框表示当前可见的区域,也就是屏幕范围。
因此,当你将图片往右边滚动100个像素时,你就可以把整个cocos2d-x层往左移动100个像素。如第二张图所示。
要注意的是,你不能移太多,因为你把层往右移动太多的话,左边就是空白的了。
添加如下代码:
void HelloWorld::onTouchMoved(Touch* touch, Event* event) { Point touchLocation = this->convertTouchToNodeSpace(touch); Point oldTouchLocation = touch->getPreviousLocation(); oldTouchLocation = this->convertToNodeSpace(oldTouchLocation); Point translation = touchLocation - oldTouchLocation; this->panForTranslation(translation); } Point HelloWorld::boundLayerPos(Point newPos) { Size winSize = Director::getInstance()->getWinSize(); Point retval = newPos; /* if(retval.x > 0) retval.x = 0; if(retval.x < -background->getContentSize().width+winSize.width) retval.x = -background->getContentSize().width+winSize.width; */ retval.x = MIN(retval.x, 0); retval.x = MAX(retval.x, -background->getContentSize().width+winSize.width); retval.y = this->getPosition().y; return retval; } void HelloWorld::panForTranslation(Point translation) { if (selSprite) { Point newPos = selSprite->getPosition() + translation; selSprite->setPosition(newPos); } else { Point newPos = this->getPosition() + translation; this->setPosition( this->boundLayerPos(newPos) ); } }
方法(panForTranslation)基于传入的目标点位置移动精灵(如果有精灵被选中就移动之,否则移动整个层)。具体的做法就是设置精灵或者层的位置。
方法(onTouchMoved)是一个回调函数,它在你拖动屏幕上的手指时调用。像之前一样,把touch坐标转换成局部node坐标。因为没有一个辅助方法可以把前一个层的touch坐标转换成node坐标,因此,我们需要手工地调用那2个方法来执行这个操作。
然后,计算touch偏移量,通过把当前的点坐标减去上一个点坐标,然后调用panForTranslation方法。
编译并运行,现在,你可以通过拖拽来移动精灵和层啦!