Friday, June 13, 2014

Bài 22: Học làm game thứ 3: Sushi Crush - Like Candy Crush or Bejewer ( Part 3 )

Hi mọi người!

Cả tuần nay bận quá chả viết được bài. Nhưng mình chắc chắn rằng sau khi đọc xong 21 bài TUT trong này, các bạn có thể tự đọc các bài Tut khác trên mạng tại những trang nước ngoài rồi đó. Có mấy bạn đã Build xong game Sushi này rồi đấy, Hi. Nay mình sẽ hướng dẫn nốt cho những người chưa xong game này nha.


Qua 2 bài trước chúng ta đã từng bước học được cách làm game Sushi Crush giống game Candy Crush nổi tiếng nhé. Tuy nhiên bạn vẫn chưa thể di chuyển các Sushi, cũng như ăn các chuỗi Sushi được. Ở trong bài này, chúng ta sẽ cùng nhau đi nốt phần còn lại của game này, với các công việc cụ thể sau đây:

+ Xây dựng mode chơi, Bổ sung thêm các loại Sushi đặc biệt
+ Di chuyển Sushi nhờ sự kiện Touch
+ Tạo các hiệu ứng nổ đặc biệt khi ăn các Sushi đặc biệt

Công việc chỉ có vậy thôi, nhưng chắc code sẽ hơi dài nên có thể mình sẽ chia bài này ra làm 2 phần để mọi người tiện theo dõi.

Bắt đầu nhé! Trình bày nhiều quá.

B1 - Thêm mode chơi, bổ sung thêm các loại Sushi đặc biệt

Các bạn mởi file SushiSprite.h, ta khai báo thêm 1 loại dữ liệu sau, ngay trước Class nhé

typedef enum {
    DISPLAY_MODE_NORMAL = 0,
    DISPLAY_MODE_HORIZONTAL,
    DISPLAY_MODE_VERTICAL,
} DisplayMode;

Và trong phần Public của Class SushiSprite, thêm đoạn code sau

    CC_SYNTHESIZE(bool, m_isNeedRemove, IsNeedRemove); // Cờ đánh dấu cần loại bỏ
    CC_SYNTHESIZE(bool, m_ignoreCheck, IgnoreCheck); //  Cờ bỏ qua kiểm tra
    CC_SYNTHESIZE_READONLY(DisplayMode, m_displayMode, DisplayMode); // Mode hiển thị
    void setDisplayMode(DisplayMode mode); // Thiết lập mode

Các bạn mởi file SushiSprite.cpp, ta khai báo thêm 2 mảng SushiSprite nữa cho các loại Sushi đặc biệt ( Sọc dọc, Sọc ngang)

static const char *sushiVertical[TOTAL_SUSHI] = {
"sushi_1v.png",
"sushi_2v.png",
"sushi_3v.png",
"sushi_4v.png",
"sushi_5v.png",
"sushi_6v.png"
};

static const char *sushiHorizontal[TOTAL_SUSHI] = {
"sushi_1h.png",
"sushi_2h.png",
"sushi_3h.png",
"sushi_4h.png",
"sushi_5h.png",
"sushi_6h.png"
};

Hàm tạo thay đổi lại 1 chút ( do có thêm biến mới)

SushiSprite::SushiSprite()
: m_col(0)
, m_row(0)
, m_imgIndex(0)
, m_isNeedRemove(false)
, m_ignoreCheck(false)
, m_displayMode(DISPLAY_MODE_NORMAL)
{
}

Xây dựng hàm setDisplayMode

void SushiSprite::setDisplayMode(DisplayMode mode)
{
    m_displayMode = mode;
    
    SpriteFrame *frame;

    // Tùy theo các trường hợp của mode, mà tạo ra loại Sushi tương ứng mode đó
    switch (mode) {
        case DISPLAY_MODE_VERTICAL:
            frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sushiVertical[m_imgIndex]);
            break;
        case DISPLAY_MODE_HORIZONTAL:
            frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sushiHorizontal[m_imgIndex]);
            break;
        default:
            return;
    }
    setDisplayFrame(frame); // Hàm của lớp Sprite
}

Lớp SushiSprite chỉ có thêm 1 số bổ sung nhỏ vậy thôi, bây giờ chúng ta sẽ đi vào lớp chính của game là lớp PlayLayer

B2 - Di chuyển Sushi bằng Sự kiện Touch

Bạn mở file PlayLayer.h, bổ sung thêm code như sau

Phần Public:

    virtual bool onTouchBegan(Touch *touch, Event *unused) override;
    virtual void onTouchMoved(Touch *touch, Event *unused) override;

Phần Private:

    bool m_isTouchEnable; // Cờ cho phép Touch hoặc ko
    SushiSprite *m_srcSushi; // Pointer: Sushi nguồn
    SushiSprite *m_destSushi; // Pointer: Sushi đích
    bool m_isNeedFillVacancies; // Cờ điền đầy khoảng trống
    bool m_movingVertical; // Cờ di chuyển theo chiều dọc

    void actionEndCallback(Node *node);     // Dừng Action ?
    void explodeSpecialH(Point point); // Nổ theo chiều ngang
    void explodeSpecialV(Point point); // Nổ theo chiều dọc
    SushiSprite *sushiOfPoint(Point *point); // Sushi ở vị trí tọa độ Point
    void swapSushi(); // Đảo 2 Sushi
    void markRemove(SushiSprite *sushi); // Đánh dấu loại bỏ

Bạn mở file PlayLayer.cpp, bổ sung thêm code như sau

+ Sửa lại hàm tạo ( do thêm thuộc tính mới )

PlayLayer::PlayLayer()
: spriteSheet(NULL)
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
, m_isNeedFillVacancies(false)
, m_isAnimationing(true) // Đặt cờ cho Animate
, m_isTouchEnable(true) // Cho phép Touch
, m_srcSushi(NULL)
, m_destSushi(NULL)
, m_movingVertical(true)  // Rơi Sushi
{
}

+ Trong hàm init() thêm code bắt sự kiện Touch

    auto touchListener = EventListenerTouchOneByOne::create();
    touchListener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
    touchListener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

+ Dựng hàm phụ , trả về SushiSprite tại 1 vị trí Point

SushiSprite *PlayLayer::sushiOfPoint(Point *point)
{
    SushiSprite *sushi = NULL;
    Rect rect = Rect(0, 0, 0, 0); // Hình chữ nhật kích thước 0,0 tại Point 0,0
    
    // Duyệt ma trận Sushi
    for (int i = 0; i < m_height * m_width; i++) {
        sushi = m_matrix[i];

        // Tính kích thước hình chữ nhật bao quanh Sushi
        if (sushi) {
            rect.origin.x = sushi->getPositionX() - (sushi->getContentSize().width / 2);
            rect.origin.y = sushi->getPositionY() - (sushi->getContentSize().height / 2);
            rect.size = sushi->getContentSize();

            // Nếu hình chữ nhật đó chứa Point ( chắc là point của điểm Touch )
            if (rect.containsPoint(*point)) {
                return sushi; // trả lại Sushi
            }
        }
    }
 
    return NULL; // Trả lại Null nếu Touch ra ngoài ma trận, điểm Touch ko thuộc 1 Sushi nào
}

+ Hàm bắt sự kiện Touch

bool PlayLayer::onTouchBegan(Touch *touch, Event *unused)
{
    m_srcSushi = NULL; // Sushi nguồn
    m_destSushi = NULL; // Sushi dích, dùng để Swap cho nhau
    if (m_isTouchEnable) { // cho phép Touch, khi chưa ăn thì cho phép Touch
        auto location = touch->getLocation(); // lấy điểm Touch
        m_srcSushi = sushiOfPoint(&location); // Trả về Sushi tại điểm Touch
    }
    return m_isTouchEnable;
}

// Di chuyển Sushi
void PlayLayer::onTouchMoved(Touch *touch, Event *unused)
{
    if (!m_srcSushi || !m_isTouchEnable) { // Nếu Touch ra ngoài ( ko chứa Sushi nào ) hoặc ko được phép Touch
        return;
    }
 
    // Lấy vị trí Row, Col của Sushi của Sushi nguồn
    int row = m_srcSushi->getRow();
    int col = m_srcSushi->getCol();
 
    auto location = touch->getLocation();

    // 1/2 Chiều rộng và 1/2 chiều cao
    auto halfSushiWidth = m_srcSushi->getContentSize().width / 2;
    auto halfSushiHeight = m_srcSushi->getContentSize().height / 2;
 

    // Hướng di chuyển

    // Khung chữ nhật "phía trên Sushi nguồn"
    auto  upRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
                        m_srcSushi->getPositionY() + halfSushiHeight,
                        m_srcSushi->getContentSize().width,
                        m_srcSushi->getContentSize().height);
 
    // Nếu khung này chứa điểm Touch, nghĩa là ta sẽ di chuyển 1 Sushi đi lên trên
    if (upRect.containsPoint(location)) {
        row++; // Hàng trên của Sushi Nguồn
        if (row < m_height) {
            m_destSushi = m_matrix[row * m_width + col]; // Lấy Sushi đích
        }
        m_movingVertical = true; // Di chuyển dọc = true
        swapSushi(); // Đảo 2 Sushi nguồn và đích cho ngau
        return; // Kết thúc hàm
    }
 
    // Khung chữ nhật "phía dưới Sushi nguồn", vì sao có halfSushiHeight * 3, bạn hãy vẽ hình ra cho dễ hình dung là nhớ là tọa độ gốc của hình Rectang là điểm Left - Bottom nhé, chiều cao + rộng sẽ dựng lên theo trục X ( sang phải ), và trục Y ( lên trên ). OK??
    auto  downRect = Rect(m_srcSushi->getPositionX() - halfSushiWidth,
                        m_srcSushi->getPositionY() - (halfSushiHeight * 3),
                        m_srcSushi->getContentSize().width,
                        m_srcSushi->getContentSize().height);

   // Chứa Touch
    if (downRect.containsPoint(location)) {
        row--; // Hàng dưới
        if (row >= 0) {
            m_destSushi = m_matrix[row * m_width + col];
        }
        m_movingVertical = true;
        swapSushi();
        return;
    }
 
    // Các bước di chuyển sang trái, sang phải, ở đoạn code bên dưới cũng giải thích như trên các bạn nhé
    auto  leftRect = Rect(m_srcSushi->getPositionX() - (halfSushiWidth * 3),
                          m_srcSushi->getPositionY() - halfSushiHeight,
                          m_srcSushi->getContentSize().width,
                          m_srcSushi->getContentSize().height);
 
    if (leftRect.containsPoint(location)) {
        col--;
        if (col >= 0) {
            m_destSushi = m_matrix[row * m_width + col];
        }
        m_movingVertical = false;
        swapSushi();
        return;
    }
 
    auto  rightRect = Rect(m_srcSushi->getPositionX() + halfSushiWidth,
                          m_srcSushi->getPositionY() - halfSushiHeight,
                          m_srcSushi->getContentSize().width,
                          m_srcSushi->getContentSize().height);
 
    if (rightRect.containsPoint(location)) {
        col++;
        if (col < m_width) {
            m_destSushi = m_matrix[row * m_width + col];
        }
        m_movingVertical = false;
        swapSushi();
        return;
    }

}


+ Hàm đảo 2 Sushi

void PlayLayer::swapSushi()
{
    m_isAnimationing = true; // cho phép Animation
    m_isTouchEnable = false; // Dừng Touch

    if (!m_srcSushi || !m_destSushi) { // Ko tồn tại 1 trong 2 Sushi để đảo nhau
        m_movingVertical = true;
        return;
    }
    // Lấy tọa độ Point của 2 loại Sushi được đảo
    Point posOfSrc = m_srcSushi->getPosition();
    Point posOfDest = m_destSushi->getPosition();
    float time = 0.2;
 
    // 1.Hoán vị hàng, cột 2 Sushi trong ma trận, tham số quan trọng nhất là Row và Col của Sushi
    m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
    m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
    int tmpRow = m_srcSushi->getRow();
    int tmpCol = m_srcSushi->getCol();
    m_srcSushi->setRow(m_destSushi->getRow());
    m_srcSushi->setCol(m_destSushi->getCol());
    m_destSushi->setRow(tmpRow);
    m_destSushi->setCol(tmpCol);
 
    // 2.Kiểm tra xem có dãy >= 3 Sushi giống nhau được tạo ra bởi 2 Sushi sau hoán đổi này ko
    std::list<SushiSprite *> colChainListOfFirst;
    getColChain(m_srcSushi, colChainListOfFirst);
 
    std::list<SushiSprite *> rowChainListOfFirst;
    getRowChain(m_srcSushi, rowChainListOfFirst);
 
    std::list<SushiSprite *> colChainListOfSecond;
    getColChain(m_destSushi, colChainListOfSecond);
 
    std::list<SushiSprite *> rowChainListOfSecond;
    getRowChain(m_destSushi, rowChainListOfSecond);
 
    if (colChainListOfFirst.size() >= 3
        || rowChainListOfFirst.size() >= 3
        || colChainListOfSecond.size() >= 3
        || rowChainListOfSecond.size() >= 3) {

        // Animation đảo vị trí cho nhau
        m_srcSushi->runAction(MoveTo::create(time, posOfDest));
        m_destSushi->runAction(MoveTo::create(time, posOfSrc));
        return;
    }
 
    // 3.Không tạo được chuỗi, Đảo trở lại vị trí cũ
    m_matrix[m_srcSushi->getRow() * m_width + m_srcSushi->getCol()] = m_destSushi;
    m_matrix[m_destSushi->getRow() * m_width + m_destSushi->getCol()] = m_srcSushi;
    tmpRow = m_srcSushi->getRow();
    tmpCol = m_srcSushi->getCol();
    m_srcSushi->setRow(m_destSushi->getRow());
    m_srcSushi->setCol(m_destSushi->getCol());
    m_destSushi->setRow(tmpRow);
    m_destSushi->setCol(tmpCol);
 
    // Di chuyển 2 bước, đảo vị trí, rồi trở lại vị trí cũ
    m_srcSushi->runAction(Sequence::create(
                                      MoveTo::create(time, posOfDest),
                                      MoveTo::create(time, posOfSrc),
                                      NULL));
    m_destSushi->runAction(Sequence::create(
                                      MoveTo::create(time, posOfSrc),
                                      MoveTo::create(time, posOfDest),
                                      NULL));
}


B3 - Tạo các hiệu ứng nổ đặc biệt

Trước hết ta cần sửa đổi lại một sô hàm sau

+ Hàm Update

Thay đoạn code

   if (!m_isAnimationing) {
        checkAndRemoveChain();
    }

Thành =>

    // Thiết lập cờ cho phép Touch khi không còn chuyển động, và ngược lại
    m_isTouchEnable = !m_isAnimationing;
 
    //Nếu ko có chuyển động
    if (!m_isAnimationing) {
        // Xét xem phải điền đầy ô trống không
        if (m_isNeedFillVacancies) {
            fillVacancies(); // điền đầy
            m_isNeedFillVacancies = false;
        } else {
            checkAndRemoveChain(); // Kiểm tra và ăn các chuỗi
        }
    }

+ Hàm getColChain, và getRowChain bạn bổ sung code như sau

Tìm các đoạn code

if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex()))

Sửa thành =>

        // Tồn tại Sushi đứng cạnh : cùng loại + chưa gắn cờ Remove và cờ Ignorecheck
        if (neighborSushi
            && (neighborSushi->getImgIndex() == sushi->getImgIndex())
            && !neighborSushi->getIsNeedRemove()
            && !neighborSushi->getIgnoreCheck())

+ Hàm fillVacancies, bổ sung thêm 

    // Cho phép Animation và rơi
    m_movingVertical = true;
    m_isAnimationing = true;

+ Hàm removeSushi, loại bỏ tham số truyền, các bạn nhớ sửa trong PlayLayer.h

void PlayLayer::removeSushi() // Không cần truyền tham số
{

    m_isAnimationing = true;
 
    // Duyệt toàn ma trận
    for (int i = 0; i < m_height * m_width; i++) {
        SushiSprite *sushi = m_matrix[i];
        if (!sushi) { // Bỏ qua Sushi rỗng
            continue;
        }
     
        if (sushi->getIsNeedRemove()) { // Sushi cần xóa bỏ
            m_isNeedFillVacancies = true; // Cần điền đầy
         
           // Nổ các Sushi đặc biệt
            if(sushi->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) // Loại Sushi sọc ngang
            {
                explodeSpecialH(sushi->getPosition()); // Gọi hàm nổ theo chiều ngang
            }
            else if (sushi->getDisplayMode() == DISPLAY_MODE_VERTICAL) // Loại Sushi sọc dọc
            {
                explodeSpecialV(sushi->getPosition()); // Gọi hàm nổ theo chiều dọc
            }

            explodeSushi(sushi); // Nổ sushi bình thường
         
        }
    }

}

+ Hàm explodeSushi, sửa 1 chút

    sushi->runAction(Sequence::create(
                                      ScaleTo::create(time, 0.0),
                                      CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),

                                      NULL));


Thành

    sushi->runAction(Sequence::create(
                                      ScaleTo::create(time, 0.0),
                                      CallFuncN::create(CC_CALLBACK_1(PlayLayer::actionEndCallback, this)),

                                      NULL));


+ Hàm actionEndCallback như này

void PlayLayer::actionEndCallback(Node *node)
{
// Loại bỏ Sushi khỏi ma trận và Layer
    SushiSprite *sushi = (SushiSprite *)node;
    m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
    sushi->removeFromParent();
}


+ 2 Hàm tạo hiệu ứng nổ đặc biệt theo chiều ngang và dọc

// Nổ theo chiều ngangvoid PlayLayer::explodeSpecialH(Point point)
{
    Size size = Director::getInstance()->getWinSize();

// Tham số để tạo hiệu ứng thôi
    float scaleX = 4 ;
    float scaleY = 0.7 ;
    float time = 0.3;
    Point startPosition = point; // điểm đầu
    float speed = 0.6f;
    
    auto colorSpriteRight = Sprite::create("colorHRight.png");
addChild(colorSpriteRight, 10);
    Point endPosition1 = Point(point.x - size.width, point.y); // Điểm cuối
    colorSpriteRight->setPosition(startPosition);

    // Chỗ này thực hiện 3 hành động, kéo dãn theo X + co lại theo Y, - >chạy sang trái -> xóa khỏi layer
    colorSpriteRight->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
                                             MoveTo::create(speed, endPosition1),
                                             CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteRight)),
                                             NULL));
    
    // Giải thích như trên
    auto colorSpriteLeft = Sprite::create("colorHLeft.png");
addChild(colorSpriteLeft, 10);
    Point endPosition2 = Point(point.x + size.width, point.y);
    colorSpriteLeft->setPosition(startPosition);
    colorSpriteLeft->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
                                             MoveTo::create(speed, endPosition2),
                                             CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteLeft)),
                                             NULL));
    

}

// Giống hệt hàm explodeSpecialH, chỉ thay đổi phương X thành Y, quá dễ hiểu
void PlayLayer::explodeSpecialV(Point point)
{
    Size size = Director::getInstance()->getWinSize();
    float scaleY = 4 ;
    float scaleX = 0.7 ;
    float time = 0.3;
    Point startPosition = point;
    float speed = 0.6f;

    auto colorSpriteDown = Sprite::create("colorVDown.png");
addChild(colorSpriteDown, 10);
    Point endPosition1 = Point(point.x , point.y - size.height);
    colorSpriteDown->setPosition(startPosition);
    colorSpriteDown->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
                                             MoveTo::create(speed, endPosition1),
                                             CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteDown)),
                                             NULL));
    
    auto colorSpriteUp = Sprite::create("colorVUp.png");
addChild(colorSpriteUp, 10);
    Point endPosition2 = Point(point.x , point.y + size.height);
    colorSpriteUp->setPosition(startPosition);
    colorSpriteUp->runAction(Sequence::create(ScaleTo::create(time, scaleX, scaleY),
                                             MoveTo::create(speed, endPosition2),
                                             CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteUp)),
                                             NULL));
}

Xong các bước chính, giờ chúng ta chỉnh sửa thêm và chỉnh sửa nốt 1-2 hàm nữa là chạy được

+ Hàm markRemove

void PlayLayer::markRemove(SushiSprite *sushi)
{
    if (sushi->getIsNeedRemove()) {
        return;
    }
    if (sushi->getIgnoreCheck()) {
        return;
    }
    
    // Set true
    sushi->setIsNeedRemove(true);
    // Các sushi loại sọc dọc
    if (sushi->getDisplayMode() == DISPLAY_MODE_VERTICAL) {
        for (int row = 0; row < m_height; row++) {
            SushiSprite *tmp = m_matrix[row * m_width + sushi->getCol()];
            if (!tmp || tmp == sushi) {
                continue; //Bỏ qua loại sọc dọc
            }
            
            if (tmp->getDisplayMode() == DISPLAY_MODE_NORMAL) {
                tmp->setIsNeedRemove(true); // Đánh dấu loại Sushi thường
            } else {
                markRemove(tmp); // Đệ quy,
            }
        }
    // Các sushi loại sọc ngang, tương tự
    } else if (sushi->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) {
        for (int col = 0; col < m_width; col++) {
            SushiSprite *tmp = m_matrix[sushi->getRow() * m_width + col];
            if (!tmp || tmp == sushi) {
                continue;
            }
            
            if (tmp->getDisplayMode() == DISPLAY_MODE_NORMAL) {
                tmp->setIsNeedRemove(true);
            } else {
                markRemove(tmp);
            }
        }
    }
}

+ Sửa lại hàm Ăn Sushi checkAndRemoveChain

void PlayLayer::checkAndRemoveChain()
{
    SushiSprite *sushi;
    // Thiết lập cờ IgnoreCheck = false
    for (int i = 0; i < m_height * m_width; i++) {
        sushi = m_matrix[i];
        if (!sushi) {
            continue;
        }
        sushi->setIgnoreCheck(false);
    }
    
    // 2. Kiểm lại
    for (int i = 0; i < m_height * m_width; i++) {
        sushi = m_matrix[i];
        if (!sushi) {
            continue;
        }
        
        if (sushi->getIsNeedRemove()) {
            continue; // Bỏ qua Sushi đã gắn cờ "cần loại bỏ"
        }
        if (sushi->getIgnoreCheck()) {
            continue; // Bỏ qua Sushi đã gắn cờ "bỏ qua kiểm tra"
        }
        
        // Đếm cuỗi
        std::list<SushiSprite *> colChainList;
        getColChain(sushi, colChainList);
        
        std::list<SushiSprite *> rowChainList;
        getRowChain(sushi, rowChainList);
        
        std::list<SushiSprite *> &longerList = colChainList.size() > rowChainList.size() ? colChainList : rowChainList;
        if (longerList.size() < 3) {
            continue;// Bỏ qua
        }
        
        std::list<SushiSprite *>::iterator itList;
        bool isSetedIgnoreCheck = false;
        for (itList = longerList.begin(); itList != longerList.end(); itList++) {
            sushi = (SushiSprite *)*itList;
            if (!sushi) {
                continue;
            }
            
            if (longerList.size() > 3) {
                // Sushi đặc biệt khi chuỗi có 4 hoặc 5 Sushi
                if (sushi == m_srcSushi || sushi == m_destSushi) {
                    isSetedIgnoreCheck = true;
                    sushi->setIgnoreCheck(true);
                    sushi->setIsNeedRemove(false);

// Tùy theo hướng di chuyển mà tạo ra loại Sushi sọc dọc hay ngang
                    sushi->setDisplayMode(m_movingVertical ? DISPLAY_MODE_VERTICAL : DISPLAY_MODE_HORIZONTAL);
                    continue;
                }
            }
            
            markRemove(sushi); // Đánh dấu cần loại bỏ sushi
        }
        
        // Chuỗi đặc biệt, khi Sushi rơi, sinh ra tự nhiên
        if (!isSetedIgnoreCheck && longerList.size() > 3) {
            sushi->setIgnoreCheck(true);
            sushi->setIsNeedRemove(false);
            sushi->setDisplayMode(m_movingVertical ? DISPLAY_MODE_VERTICAL : DISPLAY_MODE_HORIZONTAL);
        }
    }
    
    // 3.Loại bỏ
    removeSushi();
}


Vậy là Xong, chạy và build thử xem thế nào nhé

OK, chạy ngon lành cành đào. 



Mình xin kết thúc bài này ở đây nhé. Tổng kết lại trong bài này ta học được 1 số thứ sau:
+ Đảo 2 Sushi bằng sự kiện Touch
+ Tạo ra các Sushi đặc biệt
+ Hiệu ứng khi ăn các Sushi đặc biệt

Hãy cài vào điện thoại và chơi thử nhé.

P/S: Vì đây chỉ là Demo cho 1 dạng game kiểu Candy Crush, nên chắc chắn sẽ còn nhiều thiếu sót lớn như:

+ Màn chơi
+ Gợi ý + Check hết nước đi
+ không ăn được nước kép dạng 2 chuỗi vuông góc
+ Khi tạo ra Sushi đặc biệt, 1 trong 2 Sushi swap sẽ trở lại vị trí cũ mà ko bị remove đi.
+ Khi ăn liên tiếp các chuỗi, có delay, không mượt cho lắm
+ Tính điểm, âm thanh, 
v..v...

Game Demo chỉ vậy thôi là ổn rồi. để phát triển nó cần phải có nhiều thời gian hơn. Trong khuôn khổ bài học, thì như vậy cũng chấp nhận được. Ai có tâm huyết game kiểu này có thể phát triển tiếp nhé. Bài này code cũng khá dài. Để hiểu kỹ, sâu, hoặc sửa lỗi, bổ sung, phát triển tiếp, các bạn nên đọc lại nhiều lần cho thấm nhé

Chào và hẹn gặp lại các bạn ở bài sau!

Download 

Code and Resource

14 comments:

  1. Bạn cho mình hỏi làm sao để chuyển màn hình landscape sang portrait vậy.
    thanks & regards

    ReplyDelete
    Replies
    1. Bạn thử làm theo Hướng dẫn trong này có OK ko?

      http://www.cocos2d-x.org/wiki/Device_Orientation

      Delete
  2. cảm ơn vì những chia sẽ của bạn nhé :)

    ReplyDelete
  3. Cảm ơn nhiều nha

    ReplyDelete
  4. chạy trên điện thoại hơi có vấn đề
    link ảnh
    https://drive.google.com/file/d/0B-oinVGxaTX4VGRsMVZNZWY1Uzg/edit?usp=sharing
    -hiển thị backgound có vấn đề
    -hiển thị không đầy đủ số hàng và số cột
    -các hiệu ứng đặc biệt nổ ngang va nổ dọc làm mất luôn ảnh
    https://drive.google.com/file/d/0B-oinVGxaTX4LUdfTXd0OE9YQVE/edit?usp=sharing
    các vấn đề này giải quyết như thế nào?

    ReplyDelete
  5. bác admin ơi, em là newbie, nhìu cái còn gà quá.
    Bac clone giúp e cái game này ra cocos2dx giúp e nha.
    https://github.com/lizhi5753186/MyFlappyBird

    ReplyDelete
    Replies
    1. Đây chả là Cocos2d-x là gì, nhưng bản 2.x

      Game này nhiều code 3.x lắm, bạn tìm trong github.com, rất nhiều

      Delete
    2. các bác bít cách port version 2. sang 3. khong các bác ? chỉ giáo e với

      Delete
    3. Here: http://laptrinhgamecocos2dx.blogspot.com/2014/07/huong-dan-cach-port-code-tu-cocos2d-x-2-sang-cocos2d-x-3x.html

      Tuy ko phải là tất cả nhưng rất hữu ích

      Delete
    4. thank bác nhìu nha, e thử ngay

      Delete
  6. sao hàm removeSushi của mình lại không xóa được sushi nhỉ ? mình dùng v2.2.3

    ReplyDelete
  7. đoạn code đảo vị trí khi không tạo thành 3 sushi giống nhau t thấy thua cho gán lại vị trí cho vị trí gốc và vị trí đích

    ReplyDelete
  8. Thanks so much.
    Sau mấy tháng làm viêc. Đã có game:
    https://play.google.com/store/apps/details?id=com.yellow.ant.FunnyJewel

    ReplyDelete