天天看點

cocos2dx3.1flappy bird 簡單學習制作

原文位址  http://www.is17.com/40/

學習cocos2dx3.1中,網上教程不算特别多,參照各種教程,慢慢總結編寫,跟引用了一些優秀類,總算做出來了個超簡單的像素鳥遊戲。

現在講解一下核心内容跟,我遇到的一些問題的處理方法。

一、建立項目:

其實網上各種各樣的環境配置眼花缭亂的也不一定配置成功,我們要做的僅僅是建一個可以顯示helloworld的東西。

我這裡有個超簡單的方法。

原材料:vs2012,cocos2dx3.1.1  沒錯,隻需要這些。

打開某英文(中文将導緻編譯失敗)目錄下的D:\cocos2d-x-3.1.1\build\cocos2d-win32.vc2012.sln

右鍵解決方案,生成解決方案,等十幾分鐘吧大概。i3i5表示都是100%cpu,卡的一比。

完了之後,右鍵cpp-empty-test->設為啟動項目。---->F5。不出意外的話出現了helloworld,這麼簡單都失敗的話請百度。

我們的新項目就是改這個東西,我們僅僅是學習,現在不用看那些各種Python的東西來生成新項目====。

二、分析遊戲:

其實我寫的時候是倆眼一抹黑,先做核心,然後在添加各種功能,然後代碼各種改,對于一個沒有學過c++的人,各種問題各種出。

真正做東西我們要理清思路,設計類什麼的更好一些。思路清晰方能事半功倍。

小鳥,背景,水管,地面各做一個類,我寫的倉促,地面并沒有寫成類。

小鳥類核心代碼:Player

bool Player::init()
{
 //當時為了顯高端引用了别人的一個大圖類,所有資源打包的那種,其實并不好用,而且我并不知道他的包怎麼打,但是思路挺好,真正的官方用法是引用cocostudio打包
 playerSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bird0_0"));
 // 注釋掉這一句跟上句相同效果。
 // playerSp = Sprite::create("bird0_0.png");
 this->addChild(playerSp); 
 //小鳥飛行動畫
 auto animation = Animation::create();
 char szName[100] = {0};
 //将小鳥的三張圖檔做成動畫,
    for( int i=0;i<3;i++)
    {
        sprintf(szName, "bird0_%d.png", i);
        animation->addSpriteFrameWithFile(szName);
    }
    animation->setDelayPerUnit(1.8f / 14.0f);
    auto action = Animate::create(animation);
 // 小鳥循環飛行
    playerSp->runAction(RepeatForever::create(action));

 // 添加剛體,這個類中小鳥自動附加到剛體上
    auto body = PhysicsBody::createCircle(playerSp->getContentSize().width * 0.3f);
    body->getShape(0)->setFriction(0);
    body->getShape(0)->setRestitution(1.0f);
 //以下三行設定可以碰撞,具體參數不懂,反正這樣就觸發碰撞了。
    body->setCategoryBitmask(1);    // 0001
    body->setCollisionBitmask(1);   // 0001
    body->setContactTestBitmask(1); // 0001
    this->setPhysicsBody(body);   
    return true;
}
//小鳥死亡,僅作了停止播放動畫
void Player::die(){
  playerSp->stopAllActions();
}
           

背景類:bggroundlayer

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

    /* 生成兩張圖檔放在0和遊戲寬度位置 */
    m_bg1= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
    m_bg1->setPosition(Point(0, 0));
 m_bg1->setAnchorPoint(Point::ZERO);
 //設定抗鋸齒修正拼圖縫隙(這個很有用不加會有一個顯示bug可自己測試)
 m_bg1->getTexture()->setAliasTexParameters();
    this->addChild(m_bg1);
 m_bg2= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
    m_bg2->setPosition(Point(visibleSize.width, 0));
 m_bg2->setAnchorPoint(Point::ZERO);
 m_bg1->getTexture()->setAliasTexParameters();
    this->addChild(m_bg2);
//這個是根據幀頻率觸發的一個公共方法,來實作地圖的橫向滾動
void BackgroundLayer::logic(float dt)
{
    int posX1 = m_bg1->getPositionX(); // 背景地圖1的X坐标
    int posX2 = m_bg2->getPositionX(); // 背景地圖2的X坐标

    int iSpeed = 1;    // 地圖滾動速度,其實這個可以提出來的

    /* 兩張地圖向上滾動(兩張地圖是相鄰的,是以要一起滾動,否則會出現空隙) */
    posX1 -= iSpeed;
    posX2 -= iSpeed;

    /* 螢幕寬 */
    int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
    /* 當第1個地圖完全離開螢幕時,讓第2個地圖完全出現在螢幕上,同時讓第1個地圖緊貼在第2個地圖後面 */
    if (posX1 < -iVisibleWidth) {
        posX2 = 0;
        posX1 = iVisibleWidth;
    }
    /* 同理,當第2個地圖完全離開螢幕時,讓第1個地圖完全出現在螢幕上,同時讓第2個地圖緊貼在第1個地圖後面 */
    if (posX2 < -iVisibleWidth) {
        posX1 = 0;
        posX2 = iVisibleWidth;
    }
    m_bg1->setPositionX(posX1);
    m_bg2->setPositionX(posX2);
}
           

管道類:Conduit

寫這個遇到了很大問題,我并沒有接觸過cocos,隻是找例子來參考做,使用了跟小鳥一樣的生成方法,要麼隻能生成一個剛體,要麼生成之後變成了類似于單例的樣子,無法生成多個。後來終于看到了這個方法解決了問題。

int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
 int jianHeight = 100;
 auto pipeUpSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_up"));
 pipeUpSp->setPosition(Point(0, 0));
 this->addChild(pipeUpSp); 
 
 auto pipeDownSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_down"));
 pipeDownSp->setPosition(Point(0, pipeUpSp->getContentSize().height+jianHeight));
 this->addChild(pipeDownSp); 
 
 // 添加剛體
 auto body = PhysicsBody::create();
 body->setDynamic(false);
 //我們生成了一個剛體,向這裡放入兩個元素。這樣我們就生成了一堆可操作的管道了。
 body->addShape(PhysicsShapeBox::create(pipeUpSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, 0)));
 body->addShape(PhysicsShapeBox::create(pipeDownSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, pipeUpSp->getContentSize().height+jianHeight)));
    body->setCategoryBitmask(1);    // 0001
    body->setCollisionBitmask(1);   // 0001
    body->setContactTestBitmask(1); // 0001
    this->setPhysicsBody(body);
           

地面我并沒有寫到類裡邊,加載地面也并沒有新技術,就是建立一個Sprite,一個Body。其移動方法如下

ground->setPositionX(ground->getPositionX() - 1);
 if(ground->getPositionX() < ground->getContentSize().width/2-24) {
  ground->setPositionX(ground->getContentSize().width/2-1);
 }
           

然後我們将這些東西添加到場景類中,在這裡之前我們要修改自帶的類,使之能跳轉到我們的場景類。

我們修改AppDeleGate.cpp-->applicationDidFinishLaunching使其加載loadscene,

director->setOpenGLView(glview);
 /* 設定Win32螢幕大小為288X512, */
    glview->setFrameSize(288, 512);
    /* 簡單的螢幕适配,按比例拉伸,可能有黑邊 */
    glview->setDesignResolutionSize(288, 512, ResolutionPolicy::SHOW_ALL); 
    // turn on display FPS
    director->setDisplayStats(true);
    // set FPS. the default value is 1.0/60 if you don't call this
    director->setAnimationInterval(1.0 / 60);
    // 這裡添加load類
  //  auto scene = TollgateScene::scene();
 auto scene = LoadingScene::create();
    // run
    director->runWithScene(scene);
           

loadscene類:loadscene中實作了一個開頭切換場景動畫,也可用作實作load進度條之類。

void LoadingScene::onEnter(){
 // add background to current scene
  auto fileUtils = FileUtils::getInstance();
    std::vector<std::string> searchPaths;
   
    searchPaths.push_back("image");    
    fileUtils->setSearchPaths(searchPaths);

 Sprite *background = Sprite::create("splash.png");
 Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();
 background->setPosition(origin.x + visibleSize.width/2, origin.y + visibleSize.height/2);
 this->addChild(background);
 //加載1024的一張大圖
 Director::getInstance()->getTextureCache()->addImageAsync("atlas.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));
}

void LoadingScene::loadingCallBack(Texture2D *texture){
 //配合atlastxt
 AtlasLoader::getInstance()->loadAtlas("atlas.txt", texture);

 //切換場景,TollgateScene便是我們的主要遊戲場景類
 auto scene = TollgateScene::scene();
 TransitionScene *transition = TransitionFade::create(1, scene);
 Director::getInstance()->replaceScene(transition);
}
           

然後就是最重要的TollgateScene類了,這裡我們實作了遊戲邏輯。

首先我們添加重力環境,當時我順手把背景也寫在了這裡邊,背景不用重複加載也就沒改。

Scene* TollgateScene::scene()
{
    auto scene = Scene::createWithPhysics();
 /**設定重力*/
    Vect gravity(0, -300.1f);
    scene->getPhysicsWorld()->setGravity(gravity);

    /* 開啟測試模式  這将會給所有剛體加一個紅框 */
   // scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); 
 auto backgroundLayer = BackgroundLayer::create();
    //addChild方法添加到場景,第二個參數是層級,越大層級越高越靠前
    scene->addChild(backgroundLayer, 0);

    auto layer = TollgateScene::create();
    scene->addChild(layer, 10);
 
 layer->m_backgroundLayer = backgroundLayer;
    return scene;
}
[/cc]
然後我們在init函數裡邊添加一些初始顯示且不怎麼變動的東西。
[cc ]
bool TollgateScene::init()
{
    if (!Layer::init())
    {
        return false;
    }
//場景尺寸
 visibleSize = Director::getInstance()->getVisibleSize();
//初始化一個個性字型類
 Number::getInstance()->loadNumber(NUMBER_FONT.c_str(), "font_0%02d", 48);
//添加開始按鈕
 this->initGame(); 
//添加地面
 this->setGround();
    return true;
}
/**添加開始按鈕 按鈕點選會觸發遊戲開始事件。*/
void TollgateScene::initGame(){
 auto closeItem = MenuItemImage::create( "play_0.png", "play_1.png", CC_CALLBACK_1(TollgateScene::startGame,this));
    closeItem->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 ));
    // create menu, it's an autorelease object
    menu = Menu::create(closeItem, NULL);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);
}
           

最最重點的遊戲開始了:(其實并不複雜,我們設定了初始等級,隐藏開始按鈕,添加小鳥與管道,添加随幀事件,添加碰撞事件,邏輯很清晰)

void TollgateScene::startGame(Ref* sender){
 level = 0;
 showLevel(level);
 menu->setVisible(false);

 this->setConduit();
 this->setBird();
 
 this->schedule(schedule_selector(TollgateScene::logic));
 //監聽事件
 auto listener = EventListenerTouchOneByOne::create();
 listener->setSwallowTouches(true);
 listener->onTouchBegan = CC_CALLBACK_2(TollgateScene::onTouchBegan, this);
 _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

 auto contactListener = EventListenerPhysicsContact::create();
 contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBegin,this);
 _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
}
//加載小鳥
void TollgateScene::setBird(){
 player = Player::create();
 player->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 +100));
    this->addChild(player,5); 
}
//加載管道,我們把管道添加到一個數組裡邊,以遍來播放管道動畫,其實我們隻用到了兩組管道來重複播放。
void TollgateScene::setConduit(){
 for (int i = 0; i < 2; i++) { 
  conduit = Conduit::create(); 
  conduit->setPosition(Point(visibleSize.width + i*PIP_INTERVAL + PIP_WIDTH, this->getRandomHeight()));
  this->addChild(conduit,4);
        this->pips.push_back(conduit);
 }
}
//給管道一個随機高度
int TollgateScene::getRandomHeight() {
    Size visibleSize = Director::getInstance()->getVisibleSize();
    return rand()%(int)(2*PIP_HEIGHT + PIP_DISTANCE - visibleSize.height);
}
//點選螢幕  簡單的給小鳥添加一個向上的力量
bool TollgateScene::onTouchBegan(Touch* touch, Event* event) {
 player->getPhysicsBody()->setVelocity(Vect(0, 180));
 return true;
}
//碰撞觸發遊戲結束事件,顯示按鈕,遊戲結束,小鳥死亡。(好吧這個函數裡并沒寫)
bool TollgateScene::onContactBegin(const PhysicsContact& contact) {
 menu->setVisible(true);
 this->gameOver();
 return true;
}
//遊戲結束  停止幀事件,結束偵聽事件,清空數組,移除顯示元素。
void TollgateScene::gameOver(){
 this->unschedule(schedule_selector(TollgateScene::logic));
 _eventDispatcher->removeAllEventListeners();
 
 for (auto singlePip : this->pips) {
  this->removeChild(singlePip);
    } 
 pips.clear();

 player->die();
 this->removeChild(player);
 this->removeChild(menu);
 this->removeChild(scoreSprite);
 this->initGame();
}
           

還有一些動态的東西,并不多:

[cc ]
//背景移動
void TollgateScene::logic(float dt)
{ //調用背景移動
    m_backgroundLayer->logic(dt);
 //這裡好像介紹過了
 ground->setPositionX(ground->getPositionX() - 1);
 if(ground->getPositionX() < ground->getContentSize().width/2-24) {
  ground->setPositionX(ground->getContentSize().width/2-1);
 }

 for (auto singlePip : this->pips) {
//判斷過關,當管道x<小鳥x
        singlePip->setPositionX(singlePip->getPositionX() - 1);
  if(singlePip->getPositionX() == visibleSize.width/2-PIP_WIDTH) {
   level++;
   showLevel(level);
  }
//當管道從這個移出螢幕了再讓他從另一邊進來。
        if(singlePip->getPositionX() < -PIP_WIDTH) {
            
            singlePip->setPositionX(visibleSize.width+PIP_WIDTH/2);
            singlePip->setPositionY(this->getRandomHeight());
        }
    }
}
//顯示關卡等級 Number類會根據數字傳回一個精靈,也就是圖檔版的數字,比較好看而已。當過關了就刷一下。
void TollgateScene::showLevel(int lev){
 this->removeChild(scoreSprite);
 scoreSprite = (Sprite* )Number::getInstance()->convert(NUMBER_FONT.c_str(), lev);
 scoreSprite->setPosition(Point(visibleSize.width/2-scoreSprite->getContentSize().width/2,visibleSize.height*7/8));
 this->addChild(scoreSprite,20);
}
           

講解跟流程寫完了,可能還遺漏了一些東西大緻流程就是這樣了,請忽略排版,我的編輯器是最原始的。

繼續閱讀