【amazing cocos2d-x 3.0之十】使用touch事件來拖拽精靈

本篇文章,你將學習到如下知識點:

(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方法。


編譯並運行,現在,你可以通過拖拽來移動精靈和層啦!






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章