黃金礦工
好久沒有寫點東西了,最近工作有一點忙,生活上事情也比較繁瑣,趁著最近有點時間,寫個小游戲供大家娛樂下!隨便恭喜木木同學(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中布局的樣式圖
通過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)行布局的,效果如下

商店場景也比較簡單,賬戶余額通過上文中的`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é)點添加完畢,這里分三塊布局,如下圖所示三塊
游戲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)注維尼的小熊.