三消类游戏《万圣大作战》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引擎中文官网欢迎更多的开发者分享开发经验,来稿请发送至support@cocos.org。

来源网址:http://blog.csdn.net/lttree/article/details/43079653

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值