手游<<黃金礦工>>

黃金礦工

好久沒有寫點東西了,最近工作有一點忙,生活上事情也比較繁瑣,趁著最近有點時間,寫個小游戲供大家娛樂下!隨便恭喜木木同學(xué)被挖去了新公司,祝工作順利~

關(guān)于項目(代碼下載地址在文章最下面點擊GitHub鏈接)

項目說明:采用cocos2d-X3.12游戲引擎,基于C++開發(fā),支持Android,iOS以及wp系統(tǒng)

開發(fā)工具:支持Xcode,eclipse,visual studio都可以,提前在機(jī)器上創(chuàng)建一個cocos2d-x的空工程,將Class文件刪除,把下載好的代碼Class文件拖入到工程,把res文件放入到項目的Resoures目錄下運行就OK了~

輔助工具:Cocostudio

項目比較簡單,適合有一定編程經(jīng)驗對游戲有興趣想入門的同學(xué).

游戲截圖
游戲截圖
游戲截圖
粒子效果

首頁場景

本游戲的布局基本都是cocostudio布局的,放一張在cocostudio中布局的樣式圖

cocostudio布局樣式

通過CSLoder加載csb文件添加到對應(yīng)的場景中展示,具體代碼如下

auto mainCsb = CSLoader::createNode("csb文件名");
this->addChild(mainCsb);

Logo放大出現(xiàn)動畫也在cocostudio中創(chuàng)建,通過CSLoader獲取Timeline對象,播放指定的幀動畫

    animation = CSLoader::createTimeline("Layer.csb");
    mainCsb->runAction(animation);
    // 播放指定的動畫
    animation->gotoFrameAndPlay(0, 25, false);

云飄動動畫以及人物吹口哨抖腿的動畫都是通過代碼實現(xiàn)的,當(dāng)然也可以在cocostudio中制作骨骼動畫,通過在工程中加載導(dǎo)出的js文件播放動畫也可以,這里的動畫比較簡單,就直接通過代碼實現(xiàn)了,如果項目中用到比較復(fù)雜的動畫推薦采用加載js動畫的方式.這里由于沒有用到骨骼動畫,就不做相應(yīng)的介紹了,有興趣的同學(xué)可以自己研究下~

由于游戲需要用到存儲的數(shù)據(jù)比較小,這里對用戶游戲數(shù)據(jù)持久化存儲是采用UserDefault來進(jìn)行.如果項目中需要存儲的數(shù)據(jù)量比較龐大,建議使用數(shù)據(jù)庫建表來進(jìn)行存儲,這樣更方便數(shù)據(jù)的查詢與管理.

在工程中通過一個單例UserDataManager來管理用戶的數(shù)據(jù),提供背景音樂是否靜音,音效是否靜音,用戶賬戶金幣以及當(dāng)前游戲的關(guān)數(shù)四個成員變量.

UserDataManager *UserDataManager::getInstance()
{
   if (s_SharedUserDataManager == nullptr) {
       s_SharedUserDataManager = new UserDataManager();

       s_SharedUserDataManager->_musicMute = UserDefault::getInstance()->getBoolForKey(musicMuteKey, false);
       s_SharedUserDataManager->_soundMute = UserDefault::getInstance()->getBoolForKey(soundMuteKey, false);
       s_SharedUserDataManager->_allMoney = UserDefault::getInstance()->getIntegerForKey(userAllMoneyKey, 0);
       s_SharedUserDataManager->_stageNum = UserDefault::getInstance()->getIntegerForKey(userStageNumKey, 1);
   }

   return s_SharedUserDataManager;
}

監(jiān)聽按鈕的點擊事件,可以在cocostudio中對節(jié)點進(jìn)行命名,然后在代碼中通過下面方法獲取對應(yīng)name的節(jié)點

    // 獲取startButton節(jié)點
    auto startBtn = static_cast<Button *>(Helper::seekWidgetByName(static_cast<Widget *>(mainCsb), "startButton"));

需要注意的是,按鈕點擊回調(diào)函數(shù)需要在.h文件提前聲明,或者可以采用lambda表達(dá)式也可

  • 采用函數(shù)的回調(diào)寫法
    // 添加按鈕的事件

tartBtn->addTouchEventListener(CC_CALLBACK_2(MainLayer::startButtonTouch, this));

 - 采用lambda表達(dá)式寫法
    ```c++
       startBtn->addTouchEventListener([=](Ref *sender, Widget::TouchEventType touchType){
        // button click callBack
    });
    ```
StartButton點擊后會有兩個邏輯:通過`UserDataManager`獲取用戶游戲關(guān)數(shù)
  - 用戶沒有游戲記錄或者當(dāng)前記錄是游戲是第一關(guān),直接進(jìn)入游戲場景,開始游戲.
  - 用戶有游戲記錄并且關(guān)數(shù)大于1,直接進(jìn)入商店場景.

###商店場景
同樣也是通過采用cocostudio來進(jìn)行布局的,效果如下
![商店場景cocostudio效果](http://upload-images.jianshu.io/upload_images/575247-208604c801777723.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

商店場景也比較簡單,賬戶余額通過上文中的`UserDataManager`可以獲取用戶的金幣數(shù).

UserDataManager::getInstance()->getUserAllMoney();


商品采用Button來展示,這樣可以獲取玩家選擇的當(dāng)前商品,每一個商品只能購買一次,如果購買了商品后,對應(yīng)商品上顯示1的圖標(biāo).商品描述通過點擊商品,展示對應(yīng)的商品作用描述.

```c++
auto csb = CSLoader::createNode("ShopScene.csb");
    this->addChild(csb);

    // 添加商品描述容器
    goodsDesVec.push_back(Value("炸藥.購買以后,當(dāng)抓到較重且金額不多的物品時,按下上方炸藥即可炸毀物品,以便節(jié)省時間.功效為下一關(guān)"));
    goodsDesVec.push_back(Value("力量藥水.購買以后,在下一關(guān)力量會增加,抓到物品后拉回速度會增加20%.功效為下一關(guān)"));
    goodsDesVec.push_back(Value("優(yōu)質(zhì)礦石.購買后在下一關(guān)中收購鉆石的價格將變成原價格的3倍,但不保證下一關(guān)一定會有鉆石~其效果為下一關(guān)"));
    goodsDesVec.push_back(Value("礦石收藏書.購買后下一關(guān)的礦石的價格將會是原有價格的3倍,其功效為下一關(guān)"));

    // 初始化商品描述Text
    goodsDesText = static_cast<Text *>(Helper::seekWidgetByName(static_cast<Widget *>(csb), "shopDetail"));
    Text *userMoney = static_cast<Text *>(Helper::seekWidgetByName(static_cast<Widget *>(csb), "userMoney"));
    userMoney->setString("$" + to_string(UserDataManager::getInstance()->getAllMoney()));

    // 獲取購買按鈕.并且添加按鈕的點擊事件
    Button *buyButton = static_cast<Button *>(Helper::seekWidgetByName(static_cast<Widget *>(csb), "buyButton"));
    buyButton->addTouchEventListener([=](Ref *sender, Widget::TouchEventType type){
        if (type == Widget::TouchEventType::ENDED) {
            int index = lastSelected->getTag() - 1;

            auto oneIV = buyOnes.at(index);
            if (oneIV->isVisible()) {
                return;
            }
            // 獲取商品價格
            int price = 0;
            switch (index) {
                case 0:
                    price = kBombPrice;
                    break;
                case 1:
                    price = kPotionPrice;
                    break;
                case 2:
                    price = kDiamondsPrice;
                    break;
                case 3:
                    price = kStoneBookPrice;
                    break;
            }

            if (UserDataManager::getInstance()->getAllMoney() - payMoneyCount - price >= 0) {
                payMoneyCount += price;
                userMoney->setString("$" + to_string(UserDataManager::getInstance()->getAllMoney() - payMoneyCount));
                oneIV->setVisible(true);
            }
        }
    });

點擊下一關(guān),切換到游戲場景.

游戲場景

一樣也是在cocostudio中布局,這里需要注意的是并不是采用一個csb文件就全部將節(jié)點添加完畢,這里分三塊布局,如下圖所示三塊


頂部

level

鉤子

游戲Layer提供一個快速創(chuàng)建場景的方法

// 參數(shù)分別為 是否購買了炸彈.力量藥水.磚石升值書.石頭收藏書.在商店的花銷
static Scene *createScene(bool isBuyBomb, bool isBuyPotion, bool isBuyDiamonds, bool isStoneBook, int payMoney);

在上面函數(shù)中,創(chuàng)建游戲場景,給游戲場景添加剛體世界,用戶后期進(jìn)行碰撞的響應(yīng),代碼如下

   Scene *scene = Scene::createWithPhysics();

    auto world = scene->getPhysicsWorld();
    world->setGravity(Vec2::ZERO);

    auto gameLayer = Game::create(isBuyBomb, isBuyPotion, isBuyDiamonds, isStoneBook, payMoney);
    scene->addChild(gameLayer);

    PhysicsBody *body = PhysicsBody::createEdgeBox(Size(kWinSizeWidth, kWinSizeHeight));
    body->setCategoryBitmask(10);
    body->setCollisionBitmask(10);
    body->setContactTestBitmask(10);

    Node *node = Node::create();
    node->setPosition(kWinSize * 0.5);
    node->addComponent(body);
    node->setColor(Color3B::RED);
    node->setTag(kWorldTag);
    scene->addChild(node);

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("res/Resources/level-sheet.plist");

    return scene;

在游戲Layer的init初始化方法中,通關(guān)用戶當(dāng)前關(guān)數(shù)獲取對應(yīng)的level.csb文件,然后添加到游戲的layer中.獲取level.csb中的所有礦石節(jié)點,并且給每個礦石添加剛體body,以便后期進(jìn)行與鉤子的碰撞事件.

bool Game::init(bool isBuyBomb, bool isBuyPotion, bool isBuyDiamonds, bool isStoneBook, int payMoney)
{
    if (!Layer::init()) return false;

    auto csb = CSLoader::createNode("GameLayer.csb");
    this->addChild(csb, 10);

    this->isBuyPotion = isBuyPotion;
    this->isBuyDiamonds = isBuyDiamonds;
    this->isBuyStoneBook = isStoneBook;

    bompButton = static_cast<Button *>(Helper::seekWidgetByName(static_cast<Widget *>(csb), "BompButton"));
    bompButton->setVisible(isBuyBomb);
    bompButton->addTouchEventListener([=](Ref *ref, Widget::TouchEventType type){
        if (type == Widget::TouchEventType::ENDED) {
            // click bomp
            if (isOpenHook) {
                bompButton->setVisible(false);
                // 炸毀物品
                backSpeed = 10;

                isOpenHook = false;
                leftHook->setRotation(0);
                rightHook->setRotation(0);

                goldSprite->removeFromParent();
            }
        }
    });

    // 獲取序列幀動畫
    minerTimeLine = CSLoader::createTimeline("GameLayer.csb");
    this->runAction(minerTimeLine);

    auto hookCsb = CSLoader::createNode("Hook.csb");
    hookCsb->setPosition(kWinSizeWidth * 0.48, kWinSizeHeight * 0.856);
    this->addChild(hookCsb, 11);

    rope = static_cast<ImageView *>(Helper::seekWidgetByName(static_cast<Widget *>(hookCsb), "rope"));
    middleCircle = static_cast<Sprite *>(rope->getChildByTag(59));
    leftHook = static_cast<Sprite *>(middleCircle->getChildByTag(60));
    rightHook = static_cast<Sprite *>(middleCircle->getChildByTag(61));
    curPayMoney = payMoney;

    // 添加鉤子剛體
    PhysicsBody *hookBody = PhysicsBody::createCircle(20);
    hookBody->setContactTestBitmask(10);
    hookBody->setCollisionBitmask(10);
    hookBody->setCategoryBitmask(10);
    middleCircle->addComponent(hookBody);
    circlePosition = middleCircle->getPosition();
    this->addButtonAction(csb);

    setUpText(static_cast<Widget *>(csb));

    timeCount = 60;

    // 添加碰撞事件
    auto physicsListener = EventListenerPhysicsContact::create();
    physicsListener->onContactBegin = CC_CALLBACK_1(Game::physicsBegin, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(physicsListener, this);

    loadStageInfo();

    return true;
}

進(jìn)入游戲場景后,首先展示關(guān)卡過關(guān)提示,通過用戶當(dāng)前關(guān)卡數(shù)計算出過關(guān)需要的金額.展示完畢后,正式開始游戲.

  • 添加點擊屏幕點擊事件
    // 添加點擊事件
   auto listener = EventListenerTouchOneByOne::create();
   listener->onTouchBegan = CC_CALLBACK_2(Game::touchCallBack, this);
   _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
  • 開始鉤子左右擺動動畫
void Game::startShakeHookAnimation()
{
   float duration = 1;
   float angle = 65;
   rope->runAction(RepeatForever::create(Sequence::create(RotateTo::create(duration, angle), RotateTo::create(duration, 0), RotateTo::create(duration, -angle), RotateTo::create(duration, 0), NULL)));
}
  • 開啟倒計時
schedule(CC_SCHEDULE_SELECTOR(Game::updateTime), 1, 59, 0);

當(dāng)屏幕點擊時,如果鉤子是在搖擺階段,停止鉤子搖擺動畫,開始伸長鉤子動畫

bool Game::touchCallBack(cocos2d::Touch *touch, cocos2d::Event *event)
{
    if (!ropeChangeing) {
        rope->pause();
        ropeChangeing = true;
        minerTimeLine->gotoFrameAndPlay(0, 105, true);
        schedule(CC_SCHEDULE_SELECTOR(Game::addRopeHeight), 0.025);
    }

    return false;
}

等待鉤子碰撞的回調(diào),如果碰撞后,不是場景的邊緣則代表鉤到了礦石

bool Game::physicsBegin(cocos2d::PhysicsContact &contact)
{
    if (contact.getShapeB()->getBody()->getNode()->getTag() != kWorldTag) {
        // 碰到金塊, 打開鉤子
        if (!isOpenHook) {
            this->pullGold(contact);
        }
    } else {
        this->backSpeed = 10;
    }

    this->unschedule(CC_SCHEDULE_SELECTOR(Game::addRopeHeight));
    this->schedule(CC_SCHEDULE_SELECTOR(Game::subRopeHeight), 0.025);

    return true;
}

碰撞后停止鉤子伸長動畫,執(zhí)行鉤子拉回函數(shù)

void Game::subRopeHeight(float dt)
{
    middleCircle->setPosition(circlePosition);

    ropeHeight -= backSpeed;
    if (ropeHeight <= 20) {
        ropeHeight = 20;
        minerTimeLine->pause();
        // 恢復(fù)原樣, 繼續(xù)搖擺
        rope->resume();
        this->unschedule(CC_SCHEDULE_SELECTOR(Game::subRopeHeight));
        ropeChangeing = false;

        if (isOpenHook) {
            isOpenHook = false;
            leftHook->setRotation(0);
            rightHook->setRotation(0);

            if (goldSprite != nullptr) {
                // 加分動畫
                Label *scoreLabel = Label::create();
                scoreLabel->setColor(Color3B(50, 200, 0));
                scoreLabel->setSystemFontSize(25);
                scoreLabel->setString(to_string(goldSprite->score));
                scoreLabel->setPosition(rope->convertToWorldSpace(middleCircle->getPosition()));
                this->addChild(scoreLabel, 1000);

                curStageScore += goldSprite->score;
                auto spawn = Spawn::create(MoveTo::create(0.5, Vec2(allMoney->getPosition().x + 10, allMoney->getPosition().y)), Sequence::create(ScaleTo::create(0.25, 3), ScaleTo::create(0.25, 0.1), NULL), NULL);
                auto seque = Sequence::create(spawn, CallFuncN::create([=](Node *node){

                    scoreLabel->removeFromParent();
                    allMoney->setString(to_string(curStageScore + UserDataManager::getInstance()->getAllMoney() - curPayMoney));

                }),NULL);
                scoreLabel->runAction(seque);

                // 加分
                goldSprite->removeFromParent();
                goldSprite = nullptr;
            }
        }
    }

    //爆炸效果
    CCParticleSystem* particleSystem = CCParticleExplosion::create();
    particleSystem->setTexture(CCTextureCache::sharedTextureCache()->addImage("stars.png"));
    addChild(particleSystem);

    rope->setSize(Size(3, ropeHeight));
}

項目總結(jié)

項目寫的比較匆忙,并且cocos2d-X更新到3.0+版本后,好多函數(shù)都棄用了...框架內(nèi)部還是有許多Bug,當(dāng)然我相信這難不倒大家的~

感覺光靠文字來講述一個項目實在是太困難.希望大家還是參考工程代碼,當(dāng)遇到無法看懂或者不理解的時候參考下我寫的Blog應(yīng)該會更好一些.這個游戲項目說實話還是非常簡單的,相信大家仔細(xì)研究下都可以實現(xiàn)的.

好久沒用C++了T_T,有什么問題和不足之處大家同樣還是可以留言.

以后我會分享一些有意思的小項目.希望朋友繼續(xù)關(guān)注維尼的小熊.

代碼下載地址(如果覺得有幫助,請點擊Star★)

代碼下載地址,記得Star★和Follow

小熊的技術(shù)博客

點擊鏈接我的博客,歡迎關(guān)注

小熊的新浪微博

我的新浪微博,歡迎關(guān)注

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,029評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,198評論 4 61
  • hhhhh??
    syeaneko_5bd0閱讀 113評論 0 0
  • 雜亂的工地?zé)o序骯臟, 從旁飄出淡淡清香。 下班的姑娘心事重重, 不禁駐足抬頭張望。 白色的玉蘭亭亭玉立, 已然開成...
    Yumi玉米粒閱讀 892評論 2 6
  • 君27歲了,大事雖不成,但小事也算干成了幾件。 今年是君媽最高興也最煩悶的一年。老房子等著翻新,兒子說定了媳婦,現(xiàn)...
    月影伊人閱讀 282評論 0 0