三消類遊戲《萬聖大作戰》02:精靈的檢測與消除

1.結構大綱

先來捋下邏輯:

——進入遊戲界面,精靈開始落下

——精靈落完,檢測是否有可以消除的精靈

——如果有可以消除的精靈,先不急着消除,存放在一個List中

——掃描完全部精靈,再將List中的精靈消除

——所有精靈消除完畢後,再填補消除精靈留下的空

——填不完後,再次檢測是否有可以消除的精靈

然後就是循環判斷啦

這裏,我們用了一個 update函數,就是更新函數,要時時更新,形成一個循環,

找有沒有可以消除的精靈,這裏用到的是——scheduleUpdate定時器。

PS:這裏再插一句話,對於Cocos2d-x 中的定時器,有三種:schedule、scheduleUpdate、scheduleOnce。

>schedule有三種參數——(selector)、(selector,interval)、(selector、interval、repeat、delay)

這裏面各參數含義

selector—— 更新的目標函數,就是每次更新所要執行的函數

interval—— 更新的時間,就是每隔多長時間執行更新

repeat—— 更新的次數,就是更新多少次

delay—— 每次更新等待的時間

>scheduleUpdate 它沒有參數,它是Node類的成員函數,每個Node只要調用這個函數,就會每一幀都調用update函數。

>scheduleOnce 這個函數,有兩個參數 selector 和 delay,這兩個參數之前說schedule的時候講過,這裏意義也是一樣的,從函數名也可以看出來,這個函數只執行一次。

我們每幀都會掃描一次,所以要有一個變量,來控制,不能再掃描同時,又去消除,

所以,設定了兩個變量,isAction 和 isFillSprite 。

一個用來看是否在執行動作,一個用來看是否需要填補空缺位置。

檢測、移除、填補函數主要是這些:

/***** 檢查移除填補相關 *****/  
    // 檢測是否有可消除精靈  
    void checkAndRemoveSprite();  
    // 標記可以移除的精靈  
    void markRemove( SpriteShape* spr );  
    // 移除精靈  
    void removeSprite();  
    // 精靈的爆炸移除  
    void explodeSprite( SpriteShape* spr );  
    // 對移除的精靈進行的操作  
    void actionEndCallback(Node *node);  
    // 縱向檢查  
    void getColChain(SpriteShape *spr, std::list<SpriteShape *> &chainList);  
    // 橫向檢查  
    void getRowChain(SpriteShape *spr, std::list<SpriteShape *> &chainList);  
    // 填補空缺  
    void fillSprite();

有些看起來會有重複的感覺,其實是爲了後面一些特效和其他特性做的鋪墊,省的當時還要重新再整結構。


2.更新

我們調用了scheduleUpdate,所以自己重新寫update函數:

// 更新函數,每幀都執行  
void GameScene::update( float t )  
{  
     // 檢測是否在執行動作  
    if ( isAction ) {  
        // 設置爲false  
        isAction = false;  
        // 掃描一遍所有精靈,看有沒有可以消除的  
        for( int r = 0 ; r < ROWS ; ++r )    {  
            for( int c = 0  ; c < COLS ; ++c )   {  
                SpriteShape* spr = map[r][c];  
                if (spr && spr->getNumberOfRunningActions() > 0) {  
                    isAction = true;  
                    break;  
                }  
            }  
        }  
    }  
  
    // 如果沒有動作執行      
    if (!isAction) {  
        // 是否有精靈需要填補  
        if ( isFillSprite ) {  
            //爆炸效果結束後才掉落新壽司,增強打擊感  
            fillSprite();  
            isFillSprite = false;  
        }  
        else  
        {  
            checkAndRemoveSprite();  
        }  
    }  
  
}

這裏主要就兩個部分,

第一個部分先判斷是否在執行動作,

——如果正在執行動作,就再次掃描一遍,看是否還有動作正在執行

——如果沒執行動作,就判斷是否有精靈需要下落

————如果有,則執行填補空缺函數

————如果沒有,則開始檢測是否有精靈可以消除


3.移除一系列

主要就是 沒有執行動作,也不需要填補空缺所執行的 checkAndRemove系列

// 檢測是否有精靈可以移除  
void GameScene::checkAndRemoveSprite()  
{  
    SpriteShape *spr;  
    // 從頭遍歷,檢查是否有可以消除的精靈  
    for( int r = 0 ; r < ROWS ; ++r )    {  
        for( int c = 0 ; c < COLS ; ++c )    {  
            spr = map[r][c];  
            if( !spr )  {  
                continue;  
            }  
            // 如果該精靈已經被扔到 要刪除的List中,則不需要再檢測它  
            if( spr -> getIsNeedRemove() )   {  
                continue;  
            }  
  
            // 建立一個list 存儲在本精靈周圍(上下)與本精靈相同的精靈  
            std::list< SpriteShape *> colChainList;  
            getColChain( spr , colChainList );  
  
            // 建立一個list 存儲在本精靈周圍(左右)與本精靈相同的精靈  
            std::list< SpriteShape *> rowChainList;  
            getRowChain( spr , rowChainList );  
  
            // 將精靈個數多的list 賦值 給longerList  
            std::list< SpriteShape *> &longerList = colChainList.size() > rowChainList.size() ? colChainList : rowChainList;  
            // 如果相同精靈的個數小於3個 則跳過  
            if( longerList.size() < 3 )  {  
                continue;  
            }  
  
  
            std::list<SpriteShape *>::iterator itList;  
            for( itList = longerList.begin() ; itList != longerList.end() ; ++itList ) {  
                spr = ( SpriteShape * )* itList;  
                if( !spr )  {  
                    continue;  
                }  
                // 標記要消除的精靈  
                markRemove( spr );  
            }     
        }  
    }  
  
    // 消除標記了的精靈  
    removeSprite();  
}

就是遍歷,是否有精靈需要消除,若要消除,則標記精靈(並不是馬上消除,原因上面說過)。

標記精靈和移除精靈函數:

// 標記可以移除的精靈  
void GameScene::markRemove( SpriteShape* spr )  {  
      
    // 如果已經標記了要移除,就不需要再標記  
    if ( spr -> getIsNeedRemove()) {  
        return;  
    }  
    // 標記該精靈可以被移除  
    spr -> setIsNeedRemove(true);  
}  
  
// 移除精靈  
void GameScene::removeSprite()  
{  
    // 做一套移除的動作  
    isAction = true;  
      
    for( int r = 0 ; r < ROWS ; ++r )    {  
        for( int c = 0 ; c < COLS ; ++c )    {  
            SpriteShape* spr = map[r][c];  
            if( !spr )  {  
                continue;  
            }  
  
            if( spr -> getIsNeedRemove() )   {  
                isFillSprite = true;  
                explodeSprite( spr );  
            }  
        }  
    }  
}  
  
// 精靈的爆炸移除  
void GameScene::explodeSprite( SpriteShape* spr )   {  
      
    // 精靈的動作  
    spr->runAction(Sequence::create(  
                                      ScaleTo::create(0.2f, 0.0),  
                                      CallFuncN::create(CC_CALLBACK_1(GameScene::actionEndCallback, this)),  
                                      NULL));  
}  
  
// 對移除的精靈進行的操作  
void GameScene::actionEndCallback(Node *node)   {  
    SpriteShape *spr = (SpriteShape *)node;  
    map[spr->getRow()][spr->getCol()] = NULL;  
    spr -> removeFromParent();  
}

這裏精靈的移除和爆炸移除有些重複的感覺,但後面它們會各有不同的作用。


4.檢測精靈

主要就是兩部分,對某個位置的精靈,橫向檢測和縱向檢測:

// 縱向檢查  
void GameScene::getColChain(SpriteShape *spr, std::list<SpriteShape *> &chainList)    {  
    // 添加第一個精靈(自己)  
    chainList.push_back(spr);  
      
    // 向左查找  
    int neighborCol = spr->getCol() - 1;  
    while (neighborCol >= 0) {  
        SpriteShape *neighborSprite = map[spr->getRow()][neighborCol];  
        if (neighborSprite  
            && (neighborSprite->getImgIndex() == spr->getImgIndex())  
            && !neighborSprite->getIsNeedRemove()) {  
            chainList.push_back(neighborSprite);  
            neighborCol--;  
        } else {  
            break;  
        }  
    }  
      
    // 向右查找  
    neighborCol = spr->getCol() + 1;  
    while (neighborCol < COLS) {  
        SpriteShape *neighborSprite = map[spr->getRow()][neighborCol];  
        if (neighborSprite  
            && (neighborSprite->getImgIndex() == spr->getImgIndex())  
            && !neighborSprite->getIsNeedRemove()) {  
            chainList.push_back(neighborSprite);  
            neighborCol++;  
        } else {  
            break;  
        }  
    }  
}  
  
// 橫向檢查  
void GameScene::getRowChain(SpriteShape *spr, std::list<SpriteShape *> &chainList)    {  
    // 先將第一個精靈加入進去  
    chainList.push_back(spr);  
      
    // 向上查找  
    int neighborRow = spr->getRow() - 1;  
    while (neighborRow >= 0) {  
        SpriteShape *neighborSprite = map[neighborRow][spr->getCol()];  
        if (neighborSprite  
            && (neighborSprite->getImgIndex() == spr->getImgIndex())  
            && !neighborSprite->getIsNeedRemove()) {  
            chainList.push_back(neighborSprite);  
            neighborRow--;  
        } else {  
            break;  
        }  
    }  
      
    // 向下查找  
    neighborRow = spr->getRow() + 1;  
    while (neighborRow < ROWS) {  
        SpriteShape *neighborSprite = map[neighborRow][spr->getCol()];  
        if (neighborSprite  
            && (neighborSprite->getImgIndex() == spr->getImgIndex())  
            && !neighborSprite->getIsNeedRemove()) {  
            chainList.push_back(neighborSprite);  
            neighborRow++;  
        } else {  
            break;  
        }  
    }  
}

相對來說,還是比較好理解的,註釋都比較全。

就是一個while循環,從本精靈開始或向上(或向下、左、右)遍歷,判斷是否與本精靈相同。


5.填補空缺位置

這裏相對一些,首先有些精靈消除掉以後,

要讓被消除精靈上面的那部分沒有被消除的部分下落,

然後再下落新的精靈。

// 填補空缺位置  
void GameScene::fillSprite()    {  
  
     // 重置移動方向標誌  
    isAction = true;  
  
    int *colEmptyInfo = (int *)malloc(sizeof(int) * COLS);  
    memset((void *)colEmptyInfo, 0, sizeof(int) * COLS);  
      
    // 將存在的精靈降落下來  
    SpriteShape *spr = NULL;  
    for (int c = 0; c < COLS; c++) {  
        int removedSpriteOfCol = 0;  
        // 自底向上  
        for (int r = 0; r < ROWS; r++ ) {  
            spr = map[r][c];  
            if ( spr == NULL ) {  
                ++removedSpriteOfCol;  
            } else {  
                if ( removedSpriteOfCol > 0) {  
                    int newRow = r - removedSpriteOfCol;  
                    map[newRow][c] = spr;  
                    map[r][c] = NULL;  
                    
                    Point startPosition = spr->getPosition();  
                    Point endPosition = positionOfItem(newRow, c);  
                    float speed = (startPosition.y - endPosition.y) / GAME_SCREEN_HEIGHT*3;  
                    spr->stopAllActions();  
                    spr->runAction(CCMoveTo::create(speed, endPosition));  
                      
                    spr->setRow(newRow);  
                }  
            }  
        }  
          
        // 記錄相應列數移除的數量  
        colEmptyInfo[c] = removedSpriteOfCol;  
    }  
      
    // 新建新的精靈,並降落  
    for (int c = 0; c < COLS; ++c ) {  
        for (int r = ROWS - colEmptyInfo[c]; r < ROWS ; ++r ) {  
            createSprite(r,c);  
        }  
    }  
      
    free(colEmptyInfo);  
}

還要注意一點,如果按照這樣方式做了,但結果是所有的精靈都被消除,然後產生新的,再被消除。

這個問題的原因,可能就是沒有對每個精靈的 isNeedRemove初始化,

默認初始化是爲true的,其實應該初始化爲false喲,

可以設置個構造函數,很簡單:

SpriteShape::SpriteShape()  
: m_col(0)  
, m_row(0)  
, m_imgIndex(0)  
, m_isNeedRemove(false)  
{  
}

OK,本次的內容已經完成了,

簡簡單單的實現了檢測與消除,但還有很長的路要走:

—— 移動交換精靈

—— 消除的特效

—— 消除大於3個精靈後會產生特效的精靈

……等等

後面會慢慢更新,這次就到這裏啦~

本章資源和代碼:點擊下載


感謝本文筆者LT大樹_的分享,
Cocos引擎中文官網歡迎更多的開發者分享開發經驗,來稿請發送至[email protected]

來源網址:http://blog.csdn.net/lttree/article/details/43079653

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