山寨休闲游戏weseewe,用cocos2dx引擎编写的处女作

1.前言:
我刚毕业就来了北京,到现在已经三年了,一直从事着计算机安全软件的研发工作,和游戏不搭边。三年的时间不短了,完全可以让你知道喜不喜欢这一领域,经过反复考虑我觉得不适合干安全。淋漓尽致的发挥创新思维,迎合时代的潮流,面向亿万的消费群体,非游戏莫属了(呵呵这都是废话了,只是自己比较喜欢玩游戏而已,找个高大上的理由)。经过调查现在主流游戏有端游、页游、手游,端游过大自己搞不太现实,页游我对web又不熟,只有手油小巧易成型,加上这几年智能手机的普及手油发展趋势迅猛,嗯就是你了手油。3个月前经过调查发现了两款不错的游戏引擎cocos2d-x、Unity,经过了反复考量我明智的选择了cocos2dx(艹 Unity收费…)。三年的开发经验我用的第三方库有很多,如果你想快速上手我的经验就是找个实例去练习,在这之前当然要先看教程但不要看的太细 观其大略,因为看多了你根本记不住,在实例中你会遇到教程中有的和没有的问题,这样把问题一一解决你就会很快熟悉这套东西。学cocos2dx我也用了这个办法,把买的书和论坛上的教程观其大略,我选择的实例就是这款在AppStore上架的休闲游戏,用cocos2dx引擎把它实现一遍。在此声明山寨这个游戏只为学习目的,无任何商业用途。好废话有点多了,下面就是正题。

2.正文:
游戏截图:

游戏网盘下载:http://pan.baidu.com/s/1mg6yNxA,1

引擎版本:cocos2dx3.0rc
开发工具:Visual Studio 2012,CocoStudio v1.5.0
开发语言:C++
游戏规则简介:这个一个跳跃的游戏,玩家控制粉色小方块跳到不同颜色、不同高度的台阶上,而天上每隔一段时间就会弹出某个颜色的小圆球,只有和天上圆球颜色一致的台阶才能成为踏板,总共有10个颜色,再具体的玩一下就知道了不复杂。

游戏资源的获得,大家看到这款游戏的画风挺简单,那我也不会画,最简单的方法就是拿来主义,下载weseewe的ipa包,改成zip后缀就可以解压出来,资源全在里面,这招是网上学的,太万恶连我这么正直的人都被他教坏了。


大家可以看到资源中主要元素都有@2x版本,@2x版本的图片分辨率都很高,网上的解释这种高分辨率的图片是为大屏幕准备的,比如ipad(有更确切的解释可以偷偷告诉我),反正我是只用了@2x的图片。还有就是资源中一些图片都分为top、bottom俩个版本,听一个做美术设计的同事说两张图片要重叠放置,还有具体哪个图片要加一些透明,但我没这么干因为他不是做游戏的,给的意见不确切,我只用了top版的图片(有更确切的解释可以偷偷告诉我)。

这个调皮的粉色小方块就是我们的主角了,旋转的时候还点萌

这个又大又粗的就是配角了,要被主角踩,而且要排成排不停的向左移动,它一共有11个颜色,这些颜色的RGB值在资源里可是找不到的,我用了一个笨方法玩游戏截图,把11个颜色都弄到手了,再用画图工具把颜色的RGB都记录下来。

Title的weseewe字体在资源里也可以找到,是fnt格式的,注意是小写。

做为一个休闲游戏欢快的音乐音效是必不可少的,背景音乐、主角跳跃音效、放礼花音效,这些音乐音效文件都是wav格式的,cocos2dx可以支持。

还有其他跑龙套的资源比如开始按钮、声音按钮、排行按钮、背景里漂浮的云彩等,就不一一介绍了。

重要元素介绍:

1.漂浮的云彩

bool HelloWorld::init() {
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() ) {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();

    //1.设置背景颜色
    auto layer = CCLayerColor::create(ccc4(159, 213, 204, 0xff), visibleSize.width, visibleSize.height);
    this->addChild(layer, 0);

    //2.云彩动画
    this->schedule(schedule_selector(HelloWorld::CloudTimerCallBack), 9.0f, kRepeatForever, 0.1f);

    //3.初始化GameLayer
    InitGameLayer();

    return true;
}

void HelloWorld::CloudTimerCallBack( float dt ) {
    INIT_RANDOM_ENGINE;
    auto CloudSprite = CCSprite::create("cloudBottom@2x.png");
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Size CloudSize = CloudSprite->getContentSize();
    CloudSprite->setPosition(
        ccp(visibleSize.width + CloudSize.width / 2,
        visibleSize.height * (mf::rn(6, 8) / 10)));
    auto CloudSpriteTop = CCSprite::create("cloudTop@2x.png");
    CloudSpriteTop->setPosition(CloudSprite->getPosition());
    CloudSprite->addChild(CloudSpriteTop,1);


    this->addChild(CloudSprite, 1);
    auto CloudRotate = CCRotateBy::create(80,
                                          mf::rn(0, 1) ? 360 : -360);
    auto CloudRotateForever = CCRepeatForever::create(CloudRotate);
    auto CloudMove = CCMoveBy::create(30, ccp(-(visibleSize.width + CloudSize.width), 0));
    auto CloudActionSeque = CCSequence::create(CloudMove,
                            CCCallFuncN::create(this, callfuncN_selector(HelloWorld::CloudMoveDoneCallBack)), NULL);
    CloudSprite->runAction(CloudRotateForever);
    CloudSprite->runAction(CloudActionSeque);
}

void HelloWorld::CloudMoveDoneCallBack( cocos2d::Node *sprite ) {
    this->removeChild(sprite);
}


```

云彩的漂动我是用定时器来控制的,Timer被调用一次我就从屏幕右侧造一个云彩sprite,赋予云彩旋转和移动action,让它漂到屏幕左侧,其中旋转的方向和漂浮的高度是随机的,在这个游戏中随机以后还会被用到,下面是我的随机代码:
namespace MyFunc {
 
float GetRandomNumber(std::default_random_engine &e,int i1, int i2);
#define INIT_RANDOM_ENGINE std::default_random_engine e(time(0))
#define rn(a,b) GetRandomNumber(e,a,b)
}

#define NS_MF namespace mf = MyFunc
float MyFunc::GetRandomNumber(std::default_random_engine &e,int i1, int i2) {
    std::uniform_int_distribution u(i1, i2); 
    return float(u(e));
}


```


2.进场和开始UI动画
  
  
 上面看到的图是我从CocoStudio的UI编辑器截取的,真的非常方便简单,只需要拖拖拽拽设置下帧数就能做成这样的效果,
如果有哪位同学不会的可以参考站内大神的UI编辑器教学贴http://www.cocoachina.com/bbs/read.php?tid=192723,1
    //5.UI
    auto StartUpUI = dynamic_cast(cocostudio::GUIReader::shareReader()->widgetFromJsonFile("AnimationEditer/StartupUI_1/StartupUI_1.json"));
    this->addChild(StartUpUI,3,8);
    _uiIn = cocostudio::ActionManagerEx::getInstance()->playActionByName("StartupUI_1.json","In");
    //获取点击按钮
    auto doItButton = static_cast(StartUpUI->getChildByName("Begin"));
    auto helpButton = static_cast(StartUpUI->getChildByName("help"));
    auto soundButton = static_cast(StartUpUI->getChildByName("sound"));
    auto scoreButton = static_cast(StartUpUI->getChildByName("score"));
    //设置按钮点击事件响应
    doItButton->addTouchEventListener( this, toucheventselector(GameLayer::doItButtonCheck));
    helpButton->addTouchEventListener( this, toucheventselector(GameLayer::helpButtonCheck));
    soundButton->addTouchEventListener( this, toucheventselector(GameLayer::soundButtonCheck));
    scoreButton->addTouchEventListener( this, toucheventselector(GameLayer::scoreButtonCheck));


```

在这里说一下cocos2dx3.0以后cocos2d::ui::Layout是可以直接当成cocos2d::Node使用,而特别注意的是在使用UI编辑器导出的action后一定要手动stop,还要在场景结束前调用cocostudio::ActionManagerEx::destroyInstance(),不然会引发崩溃,这也是CocoStudio和cocos2dx接口中的BUG吧。

3.block台阶
  
这个是最麻烦的了,台阶和云彩一样也是从右向左移动,但台阶和台阶之间要无缝排列移动,如果用云彩那种方法实现你会发现台阶之间会出现不同大小的缝隙,我的办法是把台阶都放到一个Layer里,通过移动Layer来实现台阶一直向左移动。
#define BLOCKS_LAYER_TAG 61

class BlocksLayer:public cocos2d::Layer
{
#define LAST_BLOCK (Blocks_)
public:

    CREATE_FUNC(BlocksLayer);
    virtual bool init();
    Block * AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point);
    Block * AddBlockTail(BlockAttributes &blockAttri);
    const std::vector& getBlocks();
    void CleanupFirstBlock();
    void runBlocksMoveAction(float Duration);
    float getBlocksMoveDuration() {return _blockMoveDuration;}

    Colors _colors;
    int _colorIndex;

    virtual void setPosition(const cocos2d::Point &position){
        auto offsetPos = position - this->getPosition();
        for(int i=0;isetPosition(Blocks_->getPosition()+offsetPos);
        }
    }
protected:
    void AddColorTimer(float dt);

private:
    std::vector Blocks_;
    std::vector _blocksAttribute;
    int _blockIndex;

    float _blockMoveDuration;
    
    void InitBlocksAttribute();
    void BlockMoveDoneCallBack(cocos2d::Node *Block);
};


```

上面的BlocksLayer就是我的台阶层,为了实现动态向尾部添加台阶,我把台阶都放在一个vector中方便管理也就是类中成员Blocks_。
//让台阶层从右向左移动
void BlocksLayer::runBlocksMoveAction( float Duration )
{
    this->stopActionByTag(30);
    _blockMoveDuration = Duration;
       //每次移动204
    auto BlocksMove = CCMoveBy::create(_blockMoveDuration, ccp(-204,0));
    auto BlocksActionSeque = CCSequence::create(BlocksMove,
        CCCallFuncN::create(this, callfuncN_selector(BlocksLayer::BlockMoveDoneCallBack)), NULL);   //每次移动后调用BlockMoveDoneCallBack
        //无限循环移动
    auto BlocksMoveForever = CCRepeatForever::create(BlocksActionSeque);
    BlocksMoveForever->setTag(30);
    this->runAction(BlocksMoveForever);
}
void BlocksLayer::BlockMoveDoneCallBack( cocos2d::Node *Block )
{
    GameLayer *parent = (GameLayer *)this->getParent();
    if (parent->getGameState() == GS_READY) {
        this->AddBlockTail(_blocksAttribute);
    } else if (parent->getGameState() == GS_BEGIN) {
                //向队列的末尾添加台阶
        this->AddBlockTail(_blocksAttribute);
        if (_blockIndex < _blocksAttribute.size()) {
            if (_blockIndex % 15 == 0) {
                _colorIndex++;
                this->scheduleOnce(schedule_selector(BlocksLayer::AddColorTimer),1.2);
                
            }
        } else {
            parent->setGameState(GS_END);
        }
    } else if (parent->getGameState() == GS_END) {

    }
        //清理已经跑到屏幕外的台阶
    CleanupFirstBlock();
}

Block * BlocksLayer::AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point)
{
    auto BlockSprite = Block::create();
    auto BlockSize = BlockSprite->getContentSize();
    BlockSprite->setPosition(Point.x+BlockSize.width/2,Point.y);
    BlockSprite->setAttributes(blockAttri);
    if(blockAttri._allow){

        auto body = PhysicsBody::createEdgeBox(BlockSprite->getContentSize());
        body->setCategoryBitmask(1);    // 0001
        body->setContactTestBitmask(-1); // 0100
        body->setCollisionBitmask(-1);   // 0011
        BlockSprite->setPhysicsBody(body);
    }

    this->addChild(BlockSprite/*,--_order*/);
    return BlockSprite;
}

Block * BlocksLayer::AddBlockTail( BlockAttributes &blockAttri)
{
    Point TailBlockPoint = LAST_BLOCK->getPosition();
    auto BlockSize = LAST_BLOCK->getContentSize();
    Point BPoint = ccp(TailBlockPoint.x+BlockSize.width/2,blockAttri._y);
    auto BlockSprite = AddBlock(blockAttri,BPoint);
    Blocks_.push_back(BlockSprite);
    return BlockSprite;
}

void BlocksLayer::CleanupFirstBlock()
{
    auto Cnt = Blocks_.size();
    for (int i = 0; i < Cnt; i++) {
        auto Pt = convertToWorldSpace(Blocks_->getPosition());
        auto Sz = Blocks_->getContentSize();
        if (Pt.x < -Sz.width) {
            this->removeChild(Blocks_);
            Blocks_.erase(Blocks_.begin());
        } else {
            break;
        }
    }
}


```

    
当然因为游戏的规则台阶还被赋予了其它属性,高度、颜色、是否允许被踩,上面的图可以看到天上只有2种颜色,只有对应颜色的台阶主角才能跳上去,而没有的颜色主角跳上去会踩空,为此我重写了一个Sprite类赋予了它一些新的属性。
//台阶属性类
class BlockAttributes
{
public:
    BlockAttributes():_y(0),_color(cocos2d::ccc3(0,0,0)),_allow(false){

    }
    BlockAttributes(float y,cocos2d::Color3B color,bool allow):
        _y(y),_color(color),_allow(allow){

    }
    BlockAttributes& operator= (const BlockAttributes& other){
        _y = other._y;
        _color=other._color;
        _allow =other._allow;
        return *this;
    }
    bool isAllow(){return _allow;}

    float _y;  //高度
    cocos2d::Color3B _color;  //颜色
    bool _allow;  //是否允许被踩
protected:
private:
};

class Block:public cocos2d::Sprite,public BlockAttributes
{
public:
    Block(){
        this->setTag(BLOCK_TAG);
    }
    void setAttributes(const BlockAttributes &ba){
        _y=ba._y;
        _color=ba._color;
        _allow=ba._allow;
        this->setColor(_color);
    }

    virtual bool init(){
        this->initWithFile("blocktop@2x.png",cocos2d::CCRectMake(26,30,204,482));        
        return true;
    }

    CREATE_FUNC(Block);
protected:
private:
};


```

游戏中颜色只有11个,每跳过15个台阶就会新出现一种颜色,跳过10*15个台阶游戏就结束了,主角可以二次跳跃,但最多可以跳过2个台阶,也就是说在随机台阶属性的时候不能让3个连续的台阶都是不可踩的。每次游戏开始都会先初始化出所有台阶的属性,先把11种颜色重新随机排列,然后把属性也放到vector中,下面是台阶属性初始化的代码:
class Color {
public:
    Color(): _r(0), _b(0), _g(0) {}
    Color(GLubyte r, GLubyte g, GLubyte b): _r(r), _g(g), _b(b) {

    }
    Color& operator= (const Color& other) {
        _r = other._r;
        _g = other._g;
        _b = other._b;
        return *this;
    }

    const cocos2d::Color3B getCCColor() {
        return cocos2d::ccc3(_r, _g, _b);
    }

    GLubyte _r;
    GLubyte _g;
    GLubyte _b;
protected:
private:

};

class Colors {
public:
    Colors() {
        //辛苦收集的11种颜色RGB
        _allColors.push_back(Color(167, 175, 179));
        _allColors.push_back(Color(0, 204, 102));
        _allColors.push_back(Color(255, 153, 153));
        _allColors.push_back(Color(96, 98, 128));
        _allColors.push_back(Color(242, 48, 85));
        _allColors.push_back(Color(145, 57, 153));
        _allColors.push_back(Color(255, 102, 51));
        _allColors.push_back(Color(255, 255, 204));
        _allColors.push_back(Color(51, 204, 204));
        _allColors.push_back(Color(255, 204, 51));
        _allColors.push_back(Color(189, 116, 52));
    }
   //随机排列颜色
    void randomColors() {
        //random_shuffle(_allColors.begin(),_allColors.end(),MyRand());
        std::vector tmp;
        std::default_random_engine e(time(0));//生成随机无符号数
        while (_allColors.size() > 0) {
            std::uniform_int_distribution u(0, _allColors.size() - 1); 
            auto iter = _allColors.begin() + (int)u(e);
            tmp.push_back(*iter);
            _allColors.erase(iter);
        }
        _allColors = tmp;
    }
    ssize_t cnt() {
        return _allColors.size();
    }
    const cocos2d::Color3B operator](int index) {
        return _allColors.getCCColor();
    }
protected:

private:
    std::vector _allColors;
};


void BlocksLayer::InitBlocksAttribute()
{
    INIT_RANDOM_ENGINE;
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 15; j++) {
            Color3B color;
            float y = 0;
            bool allow;
            int blockIdx = i * 15 + j;
            if (blockIdx < 2) {
                                //开始的前两个台阶是初始颜色
                color = _colors;
                allow = true;
            } else {
                auto b1 = _blocksAttribute._allow;
                auto b2 = _blocksAttribute._allow;

                if (!b1 && !b2) {
                    allow = true;  //如果前俩个台阶都是不能踩的,那这个一定是能踩的
                } else if (b1 && b2) {
                    allow = mf::rn(0, 6) < 3;  //如果前俩个台阶都能踩,那这个台阶只有7分之3的几率能踩
                } else if ((b2 && !b1) || (!b2 && b1)) {
                    allow = mf::rn(0, 1);   //如果前俩个台阶只有一个能踩,那这个台阶有50%的几率能踩
                }


                if (allow) {
                    color = _colors;   //在能踩的颜色中随机出一个颜色
                } else {
                    color = _colors;    //在不能踩的颜色中随机出一个颜色
                }

                y=mf::rn(-120,120);     //台阶的高度要考虑到主角的跳跃高度
                //y=?
            }
            _blocksAttribute.push_back(BlockAttributes(y, color, allow));    //放入vector中
        }
    }
}


```


4.物理引擎和物理碰撞检测
  
Scene* HelloWorld::createScene() {
    // 'scene' is an autorelease object
    auto scene = Scene::createWithPhysics();    //既然是物理世界就要用物理场景
    //scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();
    
    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

bool GameLayer::init()
{
//………………………………
    auto contactListener = EventListenerPhysicsContact::create();  //创建一个物理接触监听事件
    contactListener->onContactBegin = CC_CALLBACK_1(GameLayer::onContactBegin,this);   //接触事件回调
    contactListener->onContactSeperate = CC_CALLBACK_1(GameLayer::onContactSeperate,this);   //接触分离回调
    dispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);   //注册事件

    //2.初始化player
    _player = Player::create();
    auto playerBody = PhysicsBody::createBox(_player->getContentSize(),PhysicsMaterial(0.99f,0.0f,0.0f));   //创建一个物理刚体
    playerBody->setCategoryBitmask(1);    // 0001
    playerBody->setContactTestBitmask(-1); // 0100
    playerBody->setCollisionBitmask(-1);   // 0011
    playerBody->setGravityEnable(false);      //关闭重力
    _player->setPhysicsBody(playerBody);    //赋予主角刚体
    _player->setPosition(300,850);
    this->addChild(_player,2);
    this->scheduleUpdate();
    this->scheduleOnce(schedule_selector(GameLayer::playerStartTimer),1);
//………………………………
}


```

上面我为主角player创建了一个动态刚体,设置了刚体的三个属性setCategoryBitmask、setContactTestBitmask、setCollisionBitmask,这三个属性必须设置不然物理接触监听事件不会检测到它的碰撞,再就是我把刚体的重力关了,这样主角就不受重力的影响,因为我要自己实现跳跃和下落。只有俩个sprite都有刚体结构才能检测到碰撞,所以我们也要赋予台阶刚体结构:
Block * BlocksLayer::AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point)
{
    auto BlockSprite = Block::create();
    auto BlockSize = BlockSprite->getContentSize();
    BlockSprite->setPosition(Point.x+BlockSize.width/2,Point.y);
    BlockSprite->setAttributes(blockAttri);
    if(blockAttri._allow){   //判断是否可以被主角踩

        auto body = PhysicsBody::createEdgeBox(BlockSprite->getContentSize());
        body->setCategoryBitmask(1);    // 0001
        body->setContactTestBitmask(-1); // 0100
        body->setCollisionBitmask(-1);   // 0011
        BlockSprite->setPhysicsBody(body);
    }

    this->addChild(BlockSprite/*,--_order*/);
    return BlockSprite;
}


```

上面给台阶加刚体结构,我只给能被主角踩的台阶加刚体,而加的是静态刚体EdgeBox,它的特性是不受重力影响,不会被其他物理刚体弹开。这样主角和配角都有刚体外衣了,下面就是物理碰撞监听事件:
bool GameLayer::onContactBegin( cocos2d::PhysicsContact& contact ) {
    //获取接触的俩个sprite
    auto nodeA = contact.getShapeA()->getBody()->getNode();
    auto nodeB = contact.getShapeB()->getBody()->getNode();
    //判断哪个是block台阶
    auto block = _player == nodeA ? nodeB : nodeA;

    auto contactData = contact.getContactData();
    //都转换成世界坐标
    auto blockPos = _blockLayer->convertToWorldSpace(block->getPosition());
    auto playerPos = this->convertToWorldSpace(_player->getPosition());

    _contactBlockCnt++;//递增接触台阶的数量
    if (contactData->normal.y > 0) {//判断是y轴接触,也就下主角下落碰到台阶
        if (!_player->stateConf(Player::PS_BACK)) {
            auto pos = this->convertToNodeSpace(ccp(0, blockPos.y + block->getContentSize().height / 2));
            _player->runWalk(pos.y);            //既然是下落有台阶接住主角,让主角停止下落
        }
        _treadBlockCnt++;//递增被踩台阶的数量
    } else if (contactData->normal.x < 0) {//判断是x轴接触,也就是主角被台阶阻挡
        //主角被台阶阻挡要后退
        auto pos = this->convertToNodeSpace(ccp(blockPos.x - block->getContentSize().width / 2 - _player->getContentSize().width / 2, playerPos.y));
        _player->setPosition(pos);
        _player->runMoveBackAction(_blockLayer->getBlocksMoveDuration(), ccp(-204, 0));
    }

    return true;
}

//接触分离事件回调
void GameLayer::onContactSeperate( cocos2d::PhysicsContact& contact ) {
    auto contactData = contact.getContactData();
    if(!contactData){
        return;
    }
    if (contactData->normal.y > 0) //判断是y轴接触,也就下主角下落碰到台阶
        _treadBlockCnt--; //递减被踩台阶的数量
    _contactBlockCnt--;//递减接触台阶的数量
    if (_treadBlockCnt == 0) {
        if (!_player->stateConf(Player::PS_JUMP))
            _player->runDownAction(); //如果被踩台阶数量是0,那么主角下落
    }
    if(_contactBlockCnt==0){
        if(_player->stateConf(Player::PS_BACK)){
            _player->stopMoveBackAction(); //如果接触的台阶数量是0,那么主角停止后退
        }
    }
}


```

上面注释写的很清晰,主要说下contactData这个数据,不管是接触回调或是分离回调,都会给这个数据,其中contactData->normal成员说明这次接触是Y轴接触还是X轴接触,上面的判断都是我试出来的,因为在网上没找到相关文档。
下面说下我用cocos2dx物理引擎碰到的问题和解决办法。
第一个是给player主角加上PhysicsBody,player再去做旋转action无效,这个网上其他人也碰到过,解决办法是重写sprite的setRotationSkewX和setRotationSkewY:
class Player: public cocos2d::Sprite {
//………………………………
protected:
    virtual void setRotationSkewX(float rotationX){
        
    }
    virtual void setRotationSkewY(float rotationY){
        cocos2d::Node::setRotation(rotationY);
        if (! _recursiveDirty) {            
            _recursiveDirty = true;         
            setDirty(true);                 
            if (!_children.empty())         
                setDirtyRecursively(true);  
        }                               
    }
//…………………………
    
};


```

第二个是移动Layer,Layer中的PhysicsBody不会随Layer移动,这应该是cocos2dx的BUG,解决办法是重写Layer的setPosition:
class BlocksLayer:public cocos2d::Layer
{
//………………………………
    virtual void setPosition(const cocos2d::Point &position){
        auto offsetPos = position - this->getPosition();  //计算位移
        for(int i=0;isetPosition(Blocks_->getPosition()+offsetPos);  //自己移动需要移动的sprite
        }
    }
//……………………
};


```


5.礼花
  
礼花是主角掉到场外gameover出的动画,实现还是比较简单的。
class FireWorks:public cocos2d::Sprite
{
public:
    void fire(const cocos2d::Rect &rect);
    CREATE_FUNC(FireWorks);
protected:
    virtual bool init();
private:
    
};

bool FireWorks::init()
{
    INIT_RANDOM_ENGINE;
    Colors color;

    this->initWithFile("particle.png");
    this->setPosition(-1000,0);
    for (int i=0;i<50;i++) //50个彩片
    {
        auto sprite = CCSprite::create("particle.png");
        sprite->setColor(color); //从10个颜色中随机
        this->addChild(sprite,1,i);
        sprite->setPosition(0,0);
        sprite->retain();
    }

    return true;
}

void FireWorks::fire( const cocos2d::Rect &rect )
{
    auto worldOrg = this->getParent()->convertToWorldSpace(rect.origin);
    auto org = this->convertToNodeSpace(worldOrg);
    INIT_RANDOM_ENGINE;
    for(int i=0;i<50;i++){
        auto movepos = ccp(org.x+mf::rn(0,rect.size.width),
            org.y+mf::rn(0,rect.size.height));
        auto moveAction = CCMoveTo::create(0.7,movepos);
        auto moveesi = CCEaseSineOut::create(moveAction);
        auto moveBack=CCMoveBy::create(6,ccp(0,-100));
        auto seq = CCSequence::create(moveesi,moveBack,NULL);
        auto rotate = CCRotateBy::create(mf::rn(2,6),mf::rn(0,1)?-360:360);
        auto rotateForever = CCRepeatForever::create(rotate);
        auto sprite = this->getChildByTag(i);
        sprite->runAction(rotateForever);
        sprite->runAction(seq);
    }
}


```

代码很简单就是把50个sprite灌入不同的颜色,fire函数的参数是一个rect,表示礼花要在这个矩形中绽放,彩片飞出去时是快出慢停所以用CCEaseSineOut,再给彩片加上正反随机的旋转,坐标是随机在参数rect里的。

6.颜色球
  
颜色球是提醒玩家现在哪些颜色的台阶可以踩,动画是我用action写的。
void GameLayer::AddColorDot(const cocos2d::Color3B &color )
{
    Size visibleSize = Director::getInstance()->getVisibleSize();
    auto colorDotSprite = CCSprite::create("colorDot@2x.png");
    colorDotSprite->setTag(200+_colorDotCnt);
    auto size = colorDotSprite->getContentSize();
    colorDotSprite->setColor(color);
    Point pt,movePt;
    if (_colorDotCnt < 5) {  //_colorDotCnt是当前实现的颜色计数
        //前5个颜色会显示在一排
        pt = ccp(visibleSize.width + size.width, visibleSize.height - 250);
        if (_colorDotCnt == 0) {
            movePt = ccp(visibleSize.width / 2, pt.y);
        } else {
            for (int i = 0; i < _colorDotCnt; i++) {
                auto preDotSprite = this->getChildByTag(200 + i);
                auto preMove = CCMoveBy::create(0.8, ccp(-(size.width/2), 0));
                auto easeElasticOut = CCEaseElasticOut::create(preMove);
                preDotSprite->runAction(easeElasticOut);
                if (i == _colorDotCnt - 1) {
                    movePt = ccp((preDotSprite->getPosition().x+ size.width/2), pt.y);
                }
            }
        }
    } else {
        //后5个颜色会显示在第二排
        pt = ccp(visibleSize.width + size.width, visibleSize.height - 250 - 20 - size.height);
        if (_colorDotCnt == 5) {
            movePt = ccp(visibleSize.width / 2, pt.y);
        } else {
            for (int i = 5; i < _colorDotCnt; i++) {
                auto preDotSprite = this->getChildByTag(200 + i);
                auto preMove = CCMoveBy::create(0.8, ccp(-(size.width/2), 0));
                auto easeElasticOut = CCEaseElasticOut::create(preMove);
                preDotSprite->runAction(easeElasticOut);
                if (i == _colorDotCnt - 1) {
                    movePt = ccp((preDotSprite->getPosition().x+ size.width/2), pt.y);
                }
            }
        }
    }

    colorDotSprite->setPosition(pt);
    this->addChild(colorDotSprite);
    auto moveAction = CCMoveTo::create(1.5, movePt);
    auto easeElasticOut = CCEaseElasticOut::create(moveAction);
    colorDotSprite->runAction(easeElasticOut);
    if (_soundOnoff)
        cd::SimpleAudioEngine::sharedEngine()->playEffect("sounds/color.wav");
    _colorDotCnt++;
}


```

是不是代码也很简单,因为要做到弹簧的效果就要用CCEaseElasticOut来移动sprite。


总结:
总的来说cocos2dx给我的感觉就是简单易学易实现,这也是引擎开发者的本意吧,虽然有些bug,但我相信这个引擎会优化的越来越好。分享写的有点乱,还有看不懂的可以参考我的源码就在下面,因为第一次用这个引擎肯定会有不足差强人意的地方,希望大家指正给出建议,我的qq是584592187有兴趣的朋友可以加我,大家可以一起讨论共同进步。好就写这些,多谢大家支持我。

源码:http://pan.baidu.com/s/1gdmqwPP,1
1赞

支持一下!!!!:2::2::2:

顶一个。。:2:

不错!前几天刚玩,没想到教程就出来了。

顶一下。。。 :2: :2: :2: :2:

顶一下,游戏更多的是上层逻辑的处理,更多的是一个团队去做一个游戏。毕竟一个人的能力是有限的。

楼主可否把整个工程开源呀,我最近也在搞cocos 3.0+ coostudio 1.5

感谢分享:2:

我想问下那个粒子效果怎么做的。。

— Begin quote from ____

引用第6楼zyx_cambridge于2014-07-24 22:44发表的 :
楼主可否把整个工程开源呀,我最近也在搞cocos 3.0+ coostudio 1.5 http://www.cocoachina.com/bbs/job.php?action=topost&tid=215306&pid=1012572

— End quote

这几天工作有点忙,周末我会补充其它逻辑实现,再把源码上传网盘共享出来。

— Begin quote from ____

引用第8楼masterk于2014-07-25 11:38发表的 :
我想问下那个粒子效果怎么做的。。 http://www.cocoachina.com/bbs/job.php?action=topost&tid=215306&pid=1012994

— End quote

工程中没有粒子效果的,如果你说的是那个礼花的效果,那是自己实现的,周末我把代码补充到帖子里。

不错,谢谢分享,学习一下。

DYA DYA UP !
这个必须顶:2:

一些东西都有可以用工具实现,比如cocostudio

亲爱的楼主,为什么我导入工程到vs2012出现这个情况

不错,支持一下。

不错~支持一下!

楼主文风优雅,甚得吾心啊。赞个

好贴!!好学习资源,谢谢楼主

为什么就是运行不了啊 显示2>libcocos2d.lib(CCPhysicsJointInfo_chipmunk.obj) : error LNK2019: 无法解析的外部符号 _cpConstraintFree,该符号在函数 “public: void __thiscall cocos2d::PhysicsJointInfo::remove(struct cpConstraint *)” (?remove@PhysicsJointInfo@cocos2d@@QAEXPAUcpConstraint@@@Z) 中被引用
2>F:\Android\cocos2d-x-3.0\cocos2d-x-3.0\cocos2d-x-3.0\tools\cocos2d-console\bin\Myaa\proj.win32\Debug.win32\weseewe.exe : fatal error LNK1120: 75 个无法解析的外部命令