本篇文章,你將學習到如下知識點:
(1)如何使用touch事件拖拽精靈的基本方法
(2)如何通過touch事件來滾動視圖本身
(3)如何方便地計算座標
1. 加載背景和圖片
新建一個工程,名爲“DragSprite”。打開HelloWorldScene.h,增加三個成員變量:
public:
cocos2d::Sprite* background; //背景圖片
cocos2d::Sprite* selSprite; //當前選中的精靈
cocos2d::Vector<cocos2d::Sprite*> movableSprites; //一個在處理touch事件時需要移動的精靈的數組
打開HelloWorldScene.ccp,把init方法的代碼替換成如下:
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。
2. 基於touch事件選取精靈,使用3.0新的事件監聽
打開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);
分析:首先我們創建了事件監聽器listener,並綁定onTouchBegan,onTouchMoved,onTouchEnded函數。
接下來我們來實現這些函數
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方法)
這是一個非常常用的轉換方法,所以提供這個方法可以節約很多事件。
編譯並運行,並且用手觸摸這些動物。當你點中一個精靈的時候,它就會以一種非常可愛的方式旋轉,表明它被你選中了。
3. 基於touch事件移動精靈和背景層
是時候讓小動物移動了!基本的思想就是實現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) );
}
}
方法(boundLayerPos),用來確保你在滾動層的時候不會超出背景圖片的邊界。你傳遞一個目標點座標,然後相應地修改x值,保證不會超出邊界。如果你不是很理解的話,可以拿出紙和筆,結合上面給出的圖片,自己動手畫一畫。那兩句MAX和MIN代碼等價於註釋的代碼。
方法(panForTranslation)基於傳入的目標點位置移動精靈(如果有精靈被選中就移動之,否則移動整個層)。具體的做法就是設置精靈或者層的位置。
方法(onTouchMoved)是一個回調函數,它在你拖動屏幕上的手指時調用。像之前一樣,把touch座標轉換成局部node座標。因爲沒有一個輔助方法可以把前一個層的touch座標轉換成node座標,因此,我們需要手工地調用那2個方法來執行這個操作。
然後,計算touch偏移量,通過把當前的點座標減去上一個點座標,然後調用panForTranslation方法。
編譯並運行,現在,你可以通過拖拽來移動精靈和層啦!