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

Người đăng: chisenhungsuutam on Thứ Năm, 12 tháng 6, 2014

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

More about

LỤC BÁT TẶNG: TẬP CẬN BÌNH

Người đăng: chisenhungsuutam




Tập ơi! Tớ bảo Tập này
Nghe nói bên mày mụn nhọt tứ tung:
Tân Cương hổ muốn về rừng
Tạng Tây nhóm lửa bập bùng đi xa
Điếu Ngư với Nhật bất hòa
Đường biên Ấn Độ can qua nhọc nhằn
Đài Loan sừng mọc bao năm
Phải lòng ông Mỹ nhở nhăn lộn chồng

More about

Tổng hợp các trang khảo sát trực tuyến kiếm tiền tại Việt Nam

Người đăng: chisenhungsuutam on Thứ Tư, 11 tháng 6, 2014


Trước hết bạn cần biết rằng, kiếm tiền bằng hình thức khảo sát trực tuyến là hình thức kiếm tiền có thu nhập nhiều nhất trong các hình thức kiếm tiền trên mạng. Việc trả lời khảo sát cũng rất đơn giản, thường là hỏi về các sản phẩn mà người tiêu dùng sử dụng và quan tâm. Trên internet cũng có rất nhiều trang Khảo sát kiếm tiền online, tuy nhiên các trang nước ngoài đều thanh toán qua Paypal và các ngân hàng trực tuyến quốc tế. Mà việc rút tiền từ các ngân hàng trực tuyến quốc tế vầ tài khoản ngân hàng Việt Nam cũng rất loằng ngoằng và trừ phí rất cao. Vì vậy, mình không thích tham gia khảo sát tại các ngân hàng này.

Dưới đây mình xin giới thiệu với các bạn các trang kiếm tiền tại Việt Nam, rất phù hợp với các thành viên Việt Nam. Các trang khảo sát này, ngoài việc thanh toán qua Paypal, qua các ví điện tử Ngân Lượng, Bảo Kim, họ còn có các hình thức thanh toán khác linh động và phù hợp hơn cho thành viên Việt Nam như: đổi quà tặng (họ sẽ gửi quà về tận nhà theo địa chỉ thành viên yêu cầu), đổi thẻ nạp điện thoại các mạng (có trang gửi thẻ nạp về tận nhà, có trang gửi mã thẻ cào qua email, có trang gửi mã thẻ vào số điện thoại mà thành viên yêu cầu). Cũng chính vì lí do đó mà khi đăng kí tham gia các trang này, các bạn cần lưu ý là phải khai địa chỉ nơi mình ở, cũng như số điện thoại và email thật của mình để họ tiện liên lạc. Việc đăng kí bạn đầu ở các trang khảo sát này cũng rất chi tiết nên bạn có thể mất đến 15 phút để khai báo. Hãy đừng vội sốt ruột, vì đây chỉ là lần khai báo duy nhất, bạn sẽ được hưởng nhiều khảo sát từ chính những thông tin bạn mà bạn đã cung cấp.

Mỗi trang có các mức điểm thưởng và số lượng khảo sát khác nhau. Ở đây, mình chỉ xin giới thiệu sơ lược để bạn tiện nắm sơ qua, bạn hãy bấm vào link để đến với trang đăng kí của từng trang, sau đó sẽ tìm hiểu cụ thể tiếp sau nhé ( trang nào tâm đắc mình sẽ giới thiệu trước):

1. Vn.ipanelonline



Có lẽ đây là trang khảo sát trực tuyến béo bở nhất mà mình biết. Nó hấp dẫn không chỉ bởi số lượng khảo sát hàng tuần tương đối nhiều mà còn vì điểm thưởng tương đối cao. Mỗi khảo sát họ thưởng từ 100điểm - 600điểm ( 1điểm = 50VND), có khảo sát điểm thưởng lên tới gần 1000 điểm ( tương đương với gần 50.000đồng).
Trước đây mình không mặn mà với trang khảo sát này lắm do nó chỉ cho thanh toán qua Paypal, nhưng hiện nay họ đã cập nhật thêm 2 hình thức thanh toán mới là đổi quà tặng (họ sẽ gởi quà về tận nhà theo địa chỉ thành viên cung cấp), và hình thức khác đơn giản hơn là đổi bằng thẻ cào điện thoại trả trước ( họ sẽ gởi mã thẻ cào điện thoại vào số điện thoại do mình cung cấp cho họ. Thật dễ dàng phải không nào?
 Bạn đã sẵn sàng tham gia chưa? Nếu đã sẵn sàng, hãy bấm vào link đăng kí bên dưới để ủng hộ mình nhé!
Đăng kí tham gia TẠI ĐÂY


2. Vn.viewfruit

Đây là trang khảo sát dành cho các thành viên đến từ Châu Á. Vn.viewfruit mang đến cho bạn nhiều cơ hội kiếm tiền chuyên nghiệp với nhiều hạng mục khảo sát với mức điểm thưởng tương đối cao. Giá trị quy đổi là 500 điểm = 1 $. Ngoài số điểm bạn tích lũy được từ chính các khảo sát mình tham gia, bạn còn có cơ hội nhận được thêm 10% số điểm từ những người bạn bạn thân khi họ tham gia khảo sát (đây là người bạn do bạn giới thiệu tham gia). 

Đăng kí tham gia TẠI ĐÂY

3. Infoq.vn



Trang này mình mới tham gia nhưng đã rút được tiền và thấy cũng khá hấp dẫn với số lượng khảo sát cũng tương đối. Tuy nhiên, mình thấy điểm thưởng của mỗi khảo sát không được cao ( thường là chỉ vài chục đến 100-200 điểm thưởng (mỗi điểm tương đương với 100đ, tức là mỗi khảo sát bạn cũng kiếm được một vài nghìn đến 1-2 chục nghìn đồng). Trang này thanh toán qua ví điện tử bảo Kim hoặc thẻ cào điện thoại trả trước.
Đăng kí tham gia TẠI ĐÂY

4. vn.ann-kate

Đây là công ty khảo sát trực tuyến đến từ Nhật Bản, có đại diện chi nhánh tại Việt Nam. vn.ann-kate mang đến cho bạn nhiều cơ hội khảo sát với số điểm thưởng hấp dẫn (1điểm = 100 VNĐ). Thanh toán qua PayPal hoặc thẻ cào điện thoại các mạng Viettel, Vinafon, mobifon.
Đăng kí tham gia TẠI ĐÂY.

5. Vinaresearch



Trang này cũng có tương đối nhiều khảo sát, có khảo sát chỉ vài điểm ( 1 điểm = 100 VND), có khảo sát được vài chục điểm , nhưng cũng có những khảo sát lên tới 400 điểm ( tương đương với 40.000 đồng). Nếu tham gia trang này, bạn lưu ý đừng bỏ qua các khảo sát nhanh hàng ngày để lấy 500đ ( tương đương với 5 khảo sát được phép tham gia hàng ngày). Trang này thanh toán qua việc đổi quà tặng hoặc thẻ cào điện thoại trả trước của các mạng ( họ gửi mã thẻ cào điện thoại qua email mà mình cung cấp).
Đăng kí tham gia TẠI ĐÂY


Trang này cũng có khảo sát hàng tuần , mỗi khảo sát thường được thưởng từ vài chục điểm đến vài trăm điểm (1 điểm = 100 vnd), cũng có những khảo sát được đến 400 - 500 điểm ( tương đương với 40.000- 50.000đồng). Trang này họ thanh toán qua thẻ cào điện thoại trả trước ( họ để thẻ vào phong bì và gửi qua đường bưu điện (chuyển phát nhanh) đến tận địa chỉ nhà ở của thành viên)
Đăng kí tham gia TẠI ĐÂY

7. Globaltestmarket:
Đây là trang khảo sát trực tuyến cực kì uy tín đến từ Hoa Kì. Globaltestmarket sẽ mạng đến cho bạn những khảo sát hấp dẫn có giá trị điểm thưởng cao, thường là từ 30 - 50 điểm ( tương đương với 30.000 - 50.00 VNĐ. Trước khi đến với các bản khảo sát hấp dẫn đó, bạn phải hoàn thành 9 cuộc khảo sát khai về hồ sơ và sở thích của bản thân, giúp bạn có nhiều cơ hội đến với các cuộc khảo sát lấy điểm thưởng của công ty. Khi bạn đã đạt được 1000 điểm ( tương đương với 1000.000 VNĐ, bạn có thể yêu cầu thanh toán qua Paypal hoặc chuyển séc về địa chỉ nhà bạn.
Đăng kí tham gia: TẠI ĐÂY

8. qand.me:
Trang khảo sát kiếm tiền mới của VN cực dễ chịu đây: 
- Chỉ có từ 3 --> 6 câu hỏi ngắn / 1 khảo sát nhanh với điểm thưởng từ 10đ đến 30 đ (tương đương với 1000 --> 3000 VNĐ).
-Thưởng điềm ngay sau khi khảo sát xong.
- Ngày nào cũng có vài khảo sát nhanh cho bạn tham gia. 
- Với những bản khảo sát dài thì điểm thưởng hấp dẫn hơn nhiều.
- Tăng điểm cực nhanh.
- Đổi điểm lấy thẻ cào điện thoại khi bạn đạt 500 đ (tương đương với 50.000 VNĐ)
Ai muốn tham gia thì hãy vào trang này để đăng kí thành viên nhé: https://qand.me/panelist/home
* Lưu ý:  khi tham gia trang này bạn phải nhập email của người giới thiệu thì mới tham gia được. Hãy nhập email giới thiệu của mình là : ngangiang369@gmail.com 

Chúc bạn thành công !...

Ngoài những trang khảo sát chuyên nghiệp trên, bạn cũng có thể tham gia khảo sát hoặc bình chọn sản phẩm để đổi lấy quà tặng hoặc thẻ cào điện thoại từ các trang sau:
- 6sao.vn
- Cimigolive.com

Chúc các bạn kiếm được nhiều tiền với các trang khảo sát trên !


More about

Nguyên Nhân Thất bại của 1 Blogger

Người đăng: chisenhungsuutam on Thứ Ba, 10 tháng 6, 2014

Tham gia để viết blog thì dễ nhưng duy trì nó thì là việc không hề dễ chút nào. Hiện nay đã có khoảng 90% tham gia viết blog đã bỏ cuộc. Ai trong số họ đều bắt đầu rất hào hứng với một ý tưởng nào đó nhưng rồi lại bỏ rơi ý tưởng đó một cách nhanh chóng. Nguyên nhân nào dẫn đến những thất bại đó? 
1. Không tìm hiểu rõ về ngách đang tham gia 
Ý tưởng để viết blog về một chủ đề nào đó bất chợp đến với bạn, bạn nghĩ rằng đó là một ý tưởng tuyệt vời nhất. Ngay lập tức bạn lao vào làm một cách điên cuồng, rồi bỗng nhiên nhận ra ý tưởng đó thật tầm thườn, dường như nó không khả quan cho lắm, bạn nghi ngờ về hiệu quả nó mang lại. Khi này nguy cơ bỏ cuộc là rất cao. 
Nguyên nhân chính của việc này là bạn chưa xác định được chỗ đứng của ý tưởng đó trong ngách thị trường chứa ý tưởng đó. Bạn cần xác định ý tưởng của bạn đã có người làm hay chưa? nếu làm rồi thì họ đang ở công đoạn nào rồi? mình có cơ hội hợp tác hay cạnh tranh với họ không? 
Tìm hiểu càng kỹ về ý tưởng đó thì bạn sẽ có cơ hội chiến thắng và đi tiếp sẽ cao hơn. Khi đó bạn mới biết ý tưởng của mình đang đứng ở vị trí nào trong ngách thị trường và cơ hội để thực hiện nó là bao nhiêu phần trăm. 
Bạn cần phải biết mình đang ở đâu, cần làm những gì để chiến thắng trên con đường đi đã chọn.
2. Không có chiến lược hợp tác và cạnh tranh với đối thủ
Chỉ có hợp tác mới giúp ta tiến nhanh hơn tới đích mong muốn. Hãy tìm hiểu những người bạn trong cùng ngách đó để giao lưu, học hỏi và hợp tác với họ khi cần. 
Tuy nhiên, sẽ có những đối thủ trực tiếp với ta,vì vậy cần phải tìm ra những điểm yếu của đối thủ, tận dụng sức mạnh của bản thân để chiến thắng đối thủ đó. Hãy là người không ngừng sáng tạo ra những bài viết mới, chiến lược mới để đối thủ của bạn không kịp chạy đua với bạn. 
Thiên tài luôn ẩn giật ở khắp nơi, vì vậy hãy thật khiêm tốn để học hỏi và hợp tác với họ. 
Ví dụ
Khi bạn muốn chiếm lĩnh từ khóa "Kiếm tiền với youtube" thì bạn phải xem trên google đang có đối thủ nào đứng đầu. Từ khóa của họ có được nhờ quảng cáo, viết bài, đăng video hay slide? Họ đã viết, đăng trên những trang nào? Số lượng bài về từ khóa đó là bao nhiêu? 
Khi ấy bạn sẽ biết được, để chiến thắng đối thủ này thì cần phải làm hơn họ những gì? 
3. Mong cầu thu được hiệu quả sớm
Chỉ mới thực hiện được một thời gian ngắn nhưng đã mong là thu được kết quả. Khi kết quả không được như mong muốn trong tưởng tượng ban đầu của bạn thì sẽ làm cho bạn sớm bỏ cuộc. 
Vẫn phải nhắc lại với bạn về sai lầm đầu tiên là: Phải xác định rõ mình đang ở đâu rồi? 
Bạn đã làm, đã chiến đấu với các đối thủ nhưng hãy xem mình đang ở trong giai đoạn nào để đạt được kết quả rồi? Mình đã chiến thắng hoàn toàn đối thủ hay chưa? Mình đã có được một ví trí top 3 hay top 5 trên google để thu hút người quan tâm hay chưa?,...
Hãy kiên nhẫn, sáng tạo để đè bẹp đối thủ nào muốn cản đường và tiến lên vị trí đầu nhé. 
Ví dụ: Nếu bạn muốn đăng ký thành công chương trình kiếm tiền với google adsense thông qua webbsite thì hãy tìm hiểu xem đối thủ của mình đang kiếm tiền như thế nào? Khi nào mình mới có được hiệu quả như họ? Khi nào mình có thể vượt họ? 
nguyên nhân thất bại của 1 blogger

More about

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

Người đăng: chisenhungsuutam on Thứ Ba, 3 tháng 6, 2014

Hi mọi người!

Chúng ta cùng tiếp tục bài học về game Sushi Crush nhé. Tổng kết lại bài trước 1 chút:
+ Tạo Class mới ( Sushi )
+ Tạo ma trận Sushi
+ Tạo Action rơi các sushi xuống ở màn chơi

Trong bài này chúng ta sẽ tập trung nghiên cứu 1 số vấn đề sau đây:

+ Kiểm tra và "ăn" 1 dãy ( 3-4-5 ) Sushi cùng loại
+ Tạo hiệu ứng "nổ" khi ăn Sushi
+ Lấp đầy khoảng trống do các Sushi đã bị ăn để lại trên ma trận

Tất cả các vấn đề trên đây đều xảy ra trong lớp PlayLayer nhé các bạn, lớp SushiSprite tạm thời sẽ không phải động tới Code.

(Bài này khá dài do code cũng dài, nên chỗ nào sai chính tả thì bỏ qua vậy)

Bắt đầu thôi!

Mở file PlayLayer.h. thêm code như sau

Phần Public:

    virtual void update(float dt) override; // Hàm này update game Scene theo thời gian dt ( 1/60 ở file AppDelegate.cpp đó)

Phần Private:

    bool m_isAnimationing; // biến kiểm tra việc đang ăn, rơi, hay hành động khác của Sushi hay không
    void checkAndRemoveChain(); // Kiểm tra và ăn dãy Sushi
    void getColChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList); // Kiểm tra tồn tại dãy Sushi theo cột hay không? - Lấy ra 1 List Sushi giống nhau ( kiểu &chainList là kiểu tham chiếu trong C+, dùng để thay đổi tham số truyền vào hàm thông qua việc lấy địa chỉ. Tuy giống con trỏ, nhưng nó có điểm khác con trỏ là ko phải dùng dấu *tên biến  để thao tác mà dùng trực tiếp tên biến )
    void getRowChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList); // Kiểm tra tồn tại dãy Sushi theo hàng hay không, Lấy ra bởi List
    void removeSushi(std::list<SushiSprite *> &sushiList);  // Xóa bỏ List Sushi, Ăn chuỗi Sushi
    void explodeSushi(SushiSprite *sushi); // Hiệu ứng nổ khi ăn Sushi
    void fillVacancies(); // Điền đầy khoảng trống do dãy Sushi đã bị ăn mất

B1 - Kiểm tra và ăn dãy Sushi cùng loại theo hàng và cột

Trước tiên, Mở file PlayLayer.cpp, thay đổi một số đoạn Code sau

// Kích thước ma trận 7x9
#define MATRIX_WIDTH (7)
#define MATRIX_HEIGHT (9)

PlayLayer::PlayLayer()
: spriteSheet(NULL)
, m_matrix(NULL)
, m_width(0)
, m_height(0)
, m_matrixLeftBottomX(0)
, m_matrixLeftBottomY(0)
, m_isAnimationing(true)  // Thêm vào hàm tạo
{
}

Trong hàm init(), thêm lệnh 

    scheduleUpdate(); // Update Scene theo thời gian

ở cuối trước return true;

Có lệnh scheduleUpdate(); thì phải có Hàm này để update theo dt


void PlayLayer::update(float dt)
    // Kiểm tra giá trị lần đầu của m_isAnimationing, mỗi bước thời gian dt, sẽ lại kiểm tra m_isAnimationing là true hay flase
    if (m_isAnimationing) { // nếu True
        // Gán = false
        m_isAnimationing = false;
        
        // Duyệt trong toàn ma trận 
        for (int i = 0; i < m_height * m_width; i++) {
            SushiSprite *sushi = m_matrix[i];

            // Nếu tồn tại 1 Sushi mà đang có "Action" thì  m_isAnimationing = true, và thoát vòng lặp
            if (sushi && sushi->getNumberOfRunningActions() > 0) {
                m_isAnimationing = true;
                break;
            }
        }
    }

    // Đến khi không có Action nào của Sushi tại bước thời gian dt nào đó, thì kiểm tra việc "Ăn" dãy Sushi nếu tồn tại

    if (!m_isAnimationing) { 
        checkAndRemoveChain();
    }
}

Kiểm tra việc ăn dãy Sushi giống nhau

void PlayLayer::checkAndRemoveChain()
{

    // Duyệt ma trận
    for (int i = 0; i < m_height * m_width; i++) {
        SushiSprite *sushi = m_matrix[i];

        if (!sushi) { // Rỗng thì bỏ qua
            continue;
        }
        
        // Đếm số lượng Sushi tạo thành chuỗi
        
        // Tạo 1 List để chứa các Sushi giống nhau
        std::list<SushiSprite *> colChainList; // Chuỗi Sushi theo cột
        getColChain(sushi, colChainList); // Lấy ra Chuỗi Sushi giống nhau theo cột, chú ý chỗ này ko còn dấu & giống như ở phần khai báo trong file .h, đây là cách dùng biến tham chiếu trong C++
        
        std::list<SushiSprite *> rowChainList; // Chuỗi Sushi theo hàng
        getRowChain(sushi, rowChainList); // Lấy ra Chuỗi Sushi giống nhau theo hàng

        // &longerList = biến tham chiếu
        // So sánh chuỗi dọc và chuỗi ngang, gán chuỗi lớn hơn cho longerList, tại sao lại có chỗ này, vì chỗ này vẫn trong vòng lặp với thứ tự Sushi thứ i có thể sẽ tồn tại 1 dấu CỘNG tạo bởi 2 chuỗi dọc và ngang cùng chứa Sushi i . Chơi candy, bejewer là biết
        std::list<SushiSprite *> &longerList = colChainList.size() > rowChainList.size() ? colChainList : rowChainList;

        // Chuỗi longer có 3 Sushi thì xóa bỏ chuỗi đó
        if (longerList.size() == 3) {
            removeSushi(longerList); // Ăn thôi
            return;
        }
        if (longerList.size() > 3) {
            // Tạo 1 Sushi Đặc biệt ở đây
            removeSushi(longerList);
            return;
        }
    }
}


Ta cùng tìm và kiểm tra sự tồn tại của Chuỗi các Sushi giống nhau theo hàng và cột trong ma trận qua 2 hàm sau đây

void PlayLayer::getColChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
    chainList.push_back(sushi); // Thêm vào dãy Sushi đầu tiên, tại vị trí thứ i đang xét trong vòng lặp FOR của hàm checkAndRemoveChain
 
    int neighborCol = sushi->getCol() - 1; // Xét cột bên trái
    while (neighborCol >= 0) { // Tồn tại cột bên trái

        // Tạo 1 pointer Sushi "bên trái" trỏ vào Sushi tại vị trí  (Hàng * width + neighborCol ), đây là cách quy ma trận cấp 2  về mảng 1 chiều nhé
        SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];

        // Nếu tồn tại sushi bên trái và cùng imgIndex (cùng loại Sushi) với sushi đang xét thì..
        if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
            // Thêm sushi trái này vào list
            chainList.push_back(neighborSushi);
            neighborCol--; // Xét tiếp Sushi bên trái đến khi ko còn Sushi nào, cột 0
        } else {
            break;  // Ko thỏa mãn đk if ở trên, Phá vòng while
        }
    }
 
    neighborCol = sushi->getCol() + 1; // Xét Sushi bên phải
    while (neighborCol < m_width) { // Xét đến cột cuối cùng, cột cuối = m_width - nhé
        // Tương tự trên tìm ông sushi cùng loại bên trái
        SushiSprite *neighborSushi = m_matrix[sushi->getRow() * m_width + neighborCol];
        if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
            chainList.push_back(neighborSushi); // Nhét vào List
            neighborCol++;
        } else {
            break; // Phá vòng while
        }
    }
}


// Giải thích Tương tự getColChain nhỉ
void PlayLayer::getRowChain(SushiSprite *sushi, std::list<SushiSprite *> &chainList)
{
    chainList.push_back(sushi);
 
    int neighborRow = sushi->getRow() - 1; // Xét sushi bên dưới
    while (neighborRow >= 0) {
        SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
        if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
            chainList.push_back(neighborSushi);
            neighborRow--;
        } else {
            break;
        }
    }
 
    neighborRow = sushi->getRow() + 1; // Xét sushi bên trên
    while (neighborRow < m_height) {
        SushiSprite *neighborSushi = m_matrix[neighborRow * m_width + sushi->getCol()];
        if (neighborSushi && (neighborSushi->getImgIndex() == sushi->getImgIndex())) {
            chainList.push_back(neighborSushi);
            neighborRow++;
        } else {
            break;
        }
    }
}


Và đây là hàm ĂN Sushi ( đọc lại hàm checkAndRemoveChain() để hiểu rõ cơ chế)

void PlayLayer::removeSushi(std::list<SushiSprite *> &sushiList)
{

    m_isAnimationing = true;
 
    std::list<SushiSprite *>::iterator itList; // Con trỏ duyệt trong List

    // Cú pháp vòng For duyệt 1 List trong C++ nâng cao
    for (itList = sushiList.begin(); itList != sushiList.end(); itList++) {

        // Chỗ này có vẻ hơi khó hiểu do nhiều dấu * nhỉ, Thế này nhé SushiSprite *sushi là tạo ra 1 con trỏ kiểu SushiSprite, (SushiSprite *) là ép kiểu con trỏ, *itList là giá trị chứa trong con trỏ, vì giá trị này lại là 1 con trỏ nên mới có việc ép kiểu con trỏ (SushiSprite *). iList là 1 con trỏ lại duyệt 1 mảng con trỏ nên có vẻ phức tạp thế này. Bạn hãy đọc lại về mảng con trỏ trong C++ là có thể hiểu

        SushiSprite *sushi = (SushiSprite *)*itList;
        // Loại bỏ sushi i ra khỏi ma trận
        m_matrix[sushi->getRow() * m_width + sushi->getCol()] = NULL;
        explodeSushi(sushi); // Tạo hiệu ứng nổ
    }
 
    // Rơi xuống để lấp đầy chỗ trống tạo bởi Sushi đã bị ăn
    fillVacancies();
}

Các hạm này sử dụng để kiểm tra và ăn các chuỗi Sushi giống nhau ( >3 ). Ta hãy cùng nghiên cứu 2 phần nhỏ sau

B2 - Tạo hiệu ứng nổ khi ăn Sushi

Vẫn trong PlayLayer.cpp, Bạn thêm 1 hàm này

void PlayLayer::explodeSushi(SushiSprite *sushi)
{

    // Thời gian hiệu ứng 0,3 giây
    float time = 0.3;

    // Thực hiện 2 hành động tuần tự, Co Sushi về kích thước, 0, 0, sau đó tự remove khỏi Contener cha
    sushi->runAction(Sequence::create(
                                      ScaleTo::create(time, 0.0), // Co kích thước về 0 trong thời gian 0.3
                                      CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, sushi)),
                                      NULL));
 
     // Action của Sprite tròn, mô phỏng vụ nổ

     auto circleSprite = Sprite::create("circle.png"); // Tạo mới sprite tròn
     addChild(circleSprite, 10);
     circleSprite->setPosition(sushi->getPosition()); // Vị trí = vị trí Sushi
     circleSprite->setScale(0); // Kích thước đầu =0
     // Thực hiện hành động tuần tự sau, Tăng kích thước lên tỷ lệ 1.0 trong thời gian 0,3 giây, sau đó xóa khỏi Layer
     circleSprite->runAction(Sequence::create(ScaleTo::create(time, 1.0),
                                             CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, circleSprite)),
                                             NULL));

     // 3. Tạo hiệu ứng particleStars, CHÚ Ý

     auto particleStars = ParticleSystemQuad::create("stars.plist"); // Tạo mới
     particleStars->setAutoRemoveOnFinish(true); // Tự động remove khi xong việc
     particleStars->setBlendAdditive(false); // Thiết lập sự pha trộn thêm vào = false

     particleStars->setPosition(sushi->getPosition()); // Đặt vị trí tại Sushi nổ
     particleStars->setScale(0.3);  //  Thiết lập tỉ lệ 0.3
     addChild(particleStars, 20); // Thêm vào Layer Play 
}

Vâng hiệu ứng nổ và 1 chút màu mè đẹp mắt chỉ có vậy thôi. Tiếp theo là phần cho các Sushi rơi xuống điền đầy vào chỗ trống của các Sushi đã bị ăn

B3 - Lấp đầy khoảng trống do Sushi bị ăn để lại trên Ma trận

void PlayLayer::fillVacancies()
{
    Size size = CCDirector::getInstance()->getWinSize();
    // Chỗ này nhìn có vẻ phức tạp nhưng chẳng có gì đâu, chỉ là khai báo con trỏ, cấp phát bộ nhớ cho nó thôi, dùng như mảng 1 chiều
    int *colEmptyInfo = (int *)malloc(sizeof(int) * m_width);
    memset((void *)colEmptyInfo, 0, sizeof(int) * m_width); // set giá trị là 0 hết
 
    // Rơi Sushi đang có xuống khoảng trống
    SushiSprite *sushi = NULL; // Tạo 1 con trỏ Sushi = Null, 

    // Duyệt ma trận. Lưu ý ở đây 1 chút, chúng ta thường duyệt mảng 2 chiều theo thứ tự hàng, rồi đến cột, nhưng ở đây, hơi ngược 1 tý là cột rồi đến hàng. Và lưu ý rằng Cột 0, và Hàng 0 nằm ở vị trí bên Dưới phía Trái nhé. khi tạo ma trận ta cho viên Sushi 0,0 rơi xuống trước tiên mà

    for (int col = 0; col < m_width; col++) { // Duyệt theo cột, từ trái sang phải
        int removedSushiOfCol = 0;

        // Duyệt theo hàng, từ dưới lên trên
        for (int row = 0; row < m_height; row++) {
            sushi = m_matrix[row * m_width + col]; // Sushi tại vị trí hàng, cột
            if (NULL == sushi) { // Nếu rỗng
                removedSushiOfCol++; // Đếm số Sushi đã bị "ăn"
            } else { // Nếu ko rỗng
                if (removedSushiOfCol > 0) { // Nếu bên dưới nó có ô trống = số Sushi bị ăn
                    // Làm rơi xuống
                    int newRow = row - removedSushiOfCol; //Vị trí hàng mới ( giảm xuống )
                    // Trong ma trận ta bỏ sushi ở hàng row, và chuyển nó xuống dưới qua removedSushiOfCol ô rỗng
                    m_matrix[newRow * m_width + col] = sushi;
                    m_matrix[row * m_width + col] = NULL;
                    //Di chuyển
                    Point startPosition = sushi->getPosition();
                    Point endPosition = positionOfItem(newRow, col);
                    float speed = (startPosition.y - endPosition.y) / size.height; // Tốc độ
                    sushi->stopAllActions(); // Dừng mọi chuyển động trước đó của Sushi
                    sushi->runAction(MoveTo::create(speed, endPosition)); // Di chuyển = rơi xuống
                    // set hàng mới cho Sushi tại vị trí mới này
                    sushi->setRow(newRow);
                }
            }
        }
     
        // Mảng lưu trữ số lượng Sushi bị ăn tại vị trí Cột xác định
        colEmptyInfo[col] = removedSushiOfCol;
    }
 
    // 2. Tạo mới và làm rơi các Sushi xuống khoảng trống , lấp đầy ma trận
    for (int col = 0; col < m_width; col++) { // Duyệt cột từ trái sang phải

        // Duyệt hàng, chỉ xét từ vị trí rỗng trở lên
        for (int row = m_height - colEmptyInfo[col]; row < m_height; row++) {
            createAndDropSushi(row, col); // Tạo Sushi và rơi xuống vị trí Row, Col
        }
    }
 
    free(colEmptyInfo); // Giải phóng con trỏ 
}

Để dễ hình dung về việc rơi và điền đầy khoảng trống, hãy xem hình ảnh sau đây


Xong rồi, giờ bạn có thể Build và run code được rồi đó.

Tuy nhiên, không có điều gì xảy ra cả, kết quả ra vẫn gần giống bài trước thôi ( trừ trường hợp nào vào màn chơi mà đã ăn được Sushi tự nhiên )

Tổng kết lại trong bài này chúng ta học được 1 số điều thú vị sau đây:

+ Kiểm tra dãy Sushi cùng loại
+ Ăn khi thỏa mãn điều kiện dãy đó >=3 Sushi
+ Tạo hiệu ứng nổ khi ăn
+ Rơi các Sushi lấp đầy khoảng trống
+ Làm việc với List
+ Ma trận 2 chiều, 1 chiều, cách quy đổi 2 chiều sang 1 chiều
+ Làm quen với hệ thống trang trí particle trong game, sẽ tìm hiểu ở các bài sau

Download


Ở bài 3 chúng ta sẽ học cách di chuyển các Sushi, khi đó việc ăn Sushi, hiệu ứng nổ, rơi Sushi sẽ dễ dàng nhìn thấy hơn. Bài này chỉ là bài chuẩn bị cho bài sau thôi mà.

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


More about

Các trang Kiếm tiền từ việc xem quảng cáo và làm nhiệm vụ tại site Việt Nam

Người đăng: chisenhungsuutam on Thứ Hai, 2 tháng 6, 2014

1. Trang PTC.Vietlike:
Trước hết phải nói rằng đây là 1 trang kiếm tiền của Việt Nam. Khi mình đã nhận được thanh toán rồi mình mới giới thiệu với mọi người. Ưu điểm nổi bật của trang này là trong khi chạy quảng cáo, bạn vẫn có thể lướt web hoặc làm việc khác bình thường, không như các trang PTC khác, phải mở trang QC thì nó mới chạy.

 PTC.vietLike giúp các thành viên kiếm tiền trên mạng nhờ vào việc chơi game, lướt web, làm nhiệm vụ,... Với thiết kế sáng tạo, linh hoạt, admin hướng đến sự tiện lợi cho các thành viên giúp họ có được thu nhập cao nhất. 
Việc kiếm tiền trên mạng của các thành viên PTC.vietLike được cụ thể như sau: 
  • - Kiếm được lên tới 0.02$ cho mỗi nhấp chuột.
  • - Kiếm tiền hấp dẫn từ việt làm nhiệm vụ, tiền thưởng có thể lên tới 1$.
  • - Nhận được 20% từ người chơi bạn đã giới thiệu. 
  • - Thống kê chi tiết số tiền bạn kiếm được sau mỗi lần xem quảng cáo
  • - Thống kê chi tiết số tiền kiếm được từ giới thiệu
  • - Thanh toán ngay khi tiền trong tài khoản bạn đạt 1$
  • - Các hình thức thanh toán linh hoạt, thuận tiện nhất như: Thẻ cào Viettel, chuyển khoản ngân hàng, Paypal, Ngân Lượng, ...
Đăng kí tha gia TẠI ĐÂY.

Chúc bạn kiếm được nhiều tiền với PTC.vietlike !

2. Trang gdnho.com :

Trang này cũng là trang của Việt Nam là trang anh em với trang PTC.Viet.like. Mọi hình thức kiếm tiền và thanh toán cũng tương tự như PTC.Vietlike.

Mời bạn đăng kí tham gia Gdnho.com TẠI ĐÂY

Chúc bạn kiếm được nhiều tiền với ghnho.com !




More about