【cocos2dx】卡牌記憶遊戲(2)——遊戲場景
首先,我們需要觸控事件,其次,我們要給遊戲計時,因為時間要重新整理,我們還需要update函式。計時功能我們寫了一個計時器的類,所以有個計時器成員。遊戲場景裡要裝有若干卡片,為了能夠獲取他們,我們需要一個成員容器來容納這些卡片。另外一些宣告看下述不完整程式碼。
using namespacecocos2d; class ActionScene: public Layer { public: bool onTouchBegan(Touch* touch, Event*event); void onTouchMoved(Touch* touch, Event*event); void onTouchEnded(Touch* touch, Event*event); void update(float dt); void playItem(Ref* pSender); void replay(Ref* pSender); static Scene* createScene(); virtual bool init(); CREATE_FUNC(ActionScene); private: Label* m_pointLabel;//分數標籤 int m_time;// baseCard* m_pPlay;//牌的指標,記錄前一張翻的牌 Vector<baseCard*> m_cardList;//一組牌 bool m_matching;//匹配兩張牌ing TimeCounter* m_ptimeCount;// int m_point;// };
一些按鈕,背景,標籤的初始化就不提了,卡牌初始化要怎麼來呢。我們的卡牌是要打亂了順序才能用的,那麼我們初始化的卡牌就先‘打亂順序’,再‘擺(setPosition)’到螢幕上
//卡牌初始化,裝進容器中 TextureCache::getInstance()->addImage("back.png"); for (int i = 1; i < 9; i++){ for (int j = 0; j < 2; j++) { m_pPlay = baseCard::create(); this->addChild(m_pPlay); auto pFront =Sprite::create(StringUtils::format("pic%d.png", i)); pFront->setPosition(Point(0, 0)); auto pBack =Sprite::createWithTexture(TextureCache::getInstance()->getTextureForKey("back.png")); //auto pBack =Sprite::create("back.png"); pBack->setPosition(Point(0, 0)); m_pPlay->bindBackSprite(pBack); m_pPlay->bindFrontSprite(pFront); m_pPlay->setName(StringUtils::format("pic%d.png",i)); m_pPlay->setPosition(Point(0,0)); m_pPlay->setDt(0.5f); m_cardList.pushBack(m_pPlay); } } m_pPlay = nullptr; ///////////////////////////////////////////////////////////// //洗牌 random_shuffle(m_cardList.begin(),m_cardList.end());
因為卡牌類是我們設計的,所以各種set操作(和bind)不要忘記。random_shuffle是c++提供的,具體原理自己查閱原始碼。
這時候卡牌順序打亂了,就可以擺了,很簡單的二重迴圈。
//需要更佳的螢幕自適應的話,baseX、baseY則需要根據實際螢幕解析度進行計算 int baseX = 70; int baseY = 150; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { m_cardList.at(i * 4 +j)->setPosition(Point(baseX + i * 60, baseY + j * 60)); } }
我們的卡牌,初始化是正面朝上的,我們給玩家一點點的時間去記憶之後,就要翻過來了,很明顯,我們需要scheduleOnce。
this->scheduleOnce
(
翻牌、計時、觸控事件監聽等
2,//延遲2秒
"listener"
);
這裡的實現方法我打算用lambda,畢竟能用C++11的話,還是用吧。
//延遲2秒後開始計時並監聽
this->scheduleOnce
(
[=](float dt){
for (auto x : m_cardList)x->scaleCard();
m_ptimeCount->startCounter();//計時
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan =CC_CALLBACK_2(ActionScene::onTouchBegan, this);
listener->onTouchMoved =CC_CALLBACK_2(ActionScene::onTouchMoved, this);
listener->onTouchEnded =CC_CALLBACK_2(ActionScene::onTouchEnded, this);
//listener->setSwallowTouches(true);//不擊穿
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
},
2,//延遲2秒
"listener"
);
初始化基本就完成了。接下來寫update函式。我直接丟程式碼了。也只有重新整理時間的工作而已。
voidActionScene::update(float dt)
{
if (m_time + 1 == m_ptimeCount->getTimeInt())
{
m_pointLabel->setString(StringUtils::format("%d",m_ptimeCount->getTimeInt()));
m_time++;
}
}
下面就是翻牌匹配了。這裡我先寫一點程式碼。
boolActionScene::onTouchBegan(Touch* touch, Event* event)
{
if(!m_matching){//沒有在匹配中
something1;
}
}
我們讓onTouchBegan在(!m_matching)條件下才進行something操作。那麼我們來考慮一下something該幹什麼。
大條件是‘沒有在匹配中’,那麼我們就得知道玩家摸了哪張牌,這張牌翻過沒有。如果沒摸到牌或者翻過了就不處理。
if(!m_matching){//沒有在匹配中
for (auto p : m_cardList){
if(p->getBoundingBox().containsPoint(Point(x, y)) &&p->getBackSprite()->getScaleX() == 1)
//點了這張牌 且 這張牌沒被翻
{
Something2;
}
}
那麼選中牌了之後呢,首先肯定要翻牌操作。
//點了這張牌 且 這張牌沒被翻
{
p->scaleCard();//翻牌
}
因為要翻兩張牌才能匹配,我們得知道這是第幾張牌。
//點了這張牌 且 這張牌沒被翻
{
p->scaleCard();//翻牌
if (m_pPlay == nullptr)//翻的第一張牌
m_pPlay = p;//m_pPlay標記第一張牌,用來匹配
else//翻的是第二張
something3;
}
翻的如果是第二張,那就要匹配了。
else//翻的是第二張
{
m_matching = true;//那麼開始匹配
if (m_pPlay != p &&m_pPlay->getName().compare(p->getName()) == 0)
//匹配成功:不是同一張牌且 牌面相同 , 得分
{
if(翻完了) 結束遊戲。
}
else//沒匹配成功
{
//把牌翻回去,重置相關變數
}
}
這裡用了很多lambda,我就不全列原始碼出來了。這裡講其中一個。
// 這裡是:if(翻完了) 結束遊戲。
if (8 ==m_point)//分數8分,遊戲結束
{
m_ptimeCount->stopCounter();//終止計時
this->unscheduleUpdate();
///////////////////////////////////////////////////////
//結束動畫初始化
auto endSprite =Sprite::create("MapleHighSchool.clear.1.png");
endSprite->setPosition(Point(160, 240));
endSprite->setVisible(false);
this->addChild(endSprite,10);
auto anime = Animation::create();
for (int i = 1; i < 8; i ++)
{
charszName[50] = { 0 };
sprintf(szName,"MapleHighSchool.clear.%d.png", i);
anime->addSpriteFrameWithFile(szName);
}
anime->setDelayPerUnit(1.0f / 7.0f);//因為一共7幀,設定播放1秒,則幀/秒 為 1/7
anime->setRestoreOriginalFrame(false);
auto act = Animate::create(anime);
act->retain();//為了讓下面的lambda捕捉到
///////////////////////////////////////////////////////
//遊戲結束2秒,播放通關動畫(持續1秒),聲音等
scheduleOnce(
[endSprite, act, this](float dt){
endSprite->setVisible(true);
endSprite->runAction(Repeat::create(act,1));
act->release();
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("win.mp3",false);
},
2,
"endedAnime"
);
//遊戲結束後3秒,剛好是通關動畫播放完畢
scheduleOnce(
[endSprite, act](float dt){
endSprite->setVisible(false);
},
3,
"hideEndedAnime"
);
//////////////////////////////////////////////////////////
}//End 分數8分,遊戲結束
act->retain();這一句。
Cocos2dx的智慧指標是用引用計數實現的。有點點像C++的shared_ptr。
程式碼有點多,我把其他程式碼全部刪掉。
if (8 == m_point)
{
auto act = Animate::create(anime);
act->retain();//為了讓下面的lambda捕捉到
///////////////////////////////////////////////////////
//遊戲結束2秒,播放通關動畫(持續1秒),聲音等
scheduleOnce(
[endSprite, act, this](float dt){
endSprite->runAction(Repeat::create(act,1));
act->release();
},
2,
"endedAnime");
///////////////////////////////////////////////////////
}
我們建立了一個指標act,這個指標最遲什麼時候用到呢,在這個scheduleOnce指出,2秒後會使用這個act指標,但是很明顯,2秒之後已經離開了這個程式碼塊(onTouchBegan)了,也就是引用計數為0了,act被釋放了。這是因為act被cocos的記憶體管理監控著,我們讓act不接受cocos的記憶體管理監控就可以了。也就是act->retain();。之後呢,用完就可以無情的拋棄了,act->release();
最後加上聲音,遊戲就做好了,我們跑起來是可以玩的,不過除錯的時候發現左下角有不和諧的資訊。
GL calls太多了。達到了38。當然這只是相對大小,38並不大,因為一些例子效果的GL calls本身就很大了。我們可以分析一下這些次數哪裡來。
//.h
#ifndef _ACTIONSCENE_
#define _ACTIONSCENE_
#include "cocos2d.h"
#include "baseCard.h"
#include "SimpleAudioEngine.h"
#include "timeCounter.h"
using namespace cocos2d;
class ActionScene : public Layer
{
public:
bool onTouchBegan(Touch* touch, Event* event);
void onTouchMoved(Touch* touch, Event* event);
void onTouchEnded(Touch* touch, Event* event);
void update(float dt);
void playItem(Ref* pSender);
void replay(Ref* pSender);
static Scene* createScene();
virtual bool init();
CREATE_FUNC(ActionScene);
private:
Label* m_pointLabel;//分數標籤
int m_time;//
baseCard* m_pPlay;//牌的指標,記錄前一張翻的牌
Vector<baseCard*> m_cardList;//一組牌
bool m_matching;//匹配兩張牌ing
TimeCounter* m_ptimeCount;//
int m_point;//
};
#endif
//.cpp
#include "ActionScene.h"
Scene* ActionScene::createScene()
{
auto s = Scene::create();
auto l = ActionScene::create();
s->addChild(l);
return s;
}
bool ActionScene::init()
{
if (!Layer::init())
{
return false;
}
/////////////////////////////////////////////////////////////
//普通變數初始化
m_point = 0;
m_matching = false;
m_time = 0;
auto size = Director::getInstance()->getWinSize();
/////////////////////////////////////////////////////////////
//靜態精靈初始化 call = 2
auto pBG = Sprite::create("bgLong.png");
pBG->setPosition(Point(160, 285));
this->addChild(pBG);
auto pLogo = Sprite::create("logo.png");
pLogo->setPosition(Point(160, 100));
pLogo->setScale(0.75f);
this->addChild(pLogo);
/////////////////////////////////////////////////////////////
//Label選單、按鈕初始化:replay、bye ; call = 2
Label* pLabel_BYE = Label::create("BYE", "Arial", 30);
pLabel_BYE->setColor(Color3B::BLACK);
MenuItemLabel* pLItem_BYE = MenuItemLabel::create(pLabel_BYE, CC_CALLBACK_1(ActionScene::playItem, this));
pLItem_BYE->setAnchorPoint(Point(1, 0.5f));
Label* pLabel_REPLAY = Label::create("REPLAY", "Arial", 30);
pLabel_REPLAY->setColor(Color3B::BLACK);
MenuItemLabel* pLItem_REPLAY = MenuItemLabel::create(pLabel_REPLAY, CC_CALLBACK_1(ActionScene::replay, this));
pLItem_REPLAY->setAnchorPoint(Point(1, 0.5f));
Menu* pMemu = Menu::create(pLItem_BYE, pLItem_REPLAY, nullptr);
pMemu->alignItemsVertically();
pMemu->setPosition(Point(320, size.height - 60));
this->addChild(pMemu);
/////////////////////////////////////////////////////////////
//普通label初始化:time ; call = 1
Label* pLabel_TIME = Label::create("TIME", "Arial", 30);
pLabel_TIME->setColor(Color3B::BLACK);
pLabel_TIME->setAnchorPoint(Point(0, 0.5f));
pLabel_TIME->setPosition(Point(0, size.height - 60));
this->addChild(pLabel_TIME);
/////////////////////////////////////////////////////////////
//計時器初始化
m_ptimeCount = TimeCounter::create();
this->addChild(m_ptimeCount);
this->scheduleUpdate();
/////////////////////////////////////////////////////////////
//計時用的label初始化 call = 1
m_pointLabel = Label::create(StringUtils::format("%d", m_time), "Arial", 60);
m_pointLabel->setColor(Color3B::BLACK);
if (m_pointLabel) {
m_pointLabel->setTextColor(Color4B::WHITE);
m_pointLabel->setPosition(Vec2(100, size.height - 60));
m_pointLabel->setAnchorPoint(Vec2(0, 0.5f));
this->addChild(m_pointLabel);
}
/////////////////////////////////////////////////////////////
//卡牌初始化,裝進容器中
TextureCache::getInstance()->addImage("back.png");
for (int i = 1; i < 9; i++){
for (int j = 0; j < 2; j++) {
m_pPlay = baseCard::create();
this->addChild(m_pPlay);
auto pFront = Sprite::create(StringUtils::format("pic%d.png", i));
pFront->setPosition(Point(0, 0));
auto pBack = Sprite::createWithTexture(TextureCache::getInstance()->getTextureForKey("back.png"));
//auto pBack = Sprite::create("back.png");
pBack->setPosition(Point(0, 0));
m_pPlay->bindBackSprite(pBack);
m_pPlay->bindFrontSprite(pFront);
m_pPlay->setName(StringUtils::format("pic%d.png", i));
m_pPlay->setPosition(Point(0, 0));
m_pPlay->setDt(0.5f);
m_cardList.pushBack(m_pPlay);
}
}
m_pPlay = nullptr;
/////////////////////////////////////////////////////////////
//洗牌
random_shuffle(m_cardList.begin(),m_cardList.end());
//for (int i = 0; i < 16; i++)
//{
// int a = static_cast<int>(CCRANDOM_0_1() * 100) % 16;
// int b = static_cast<int>(CCRANDOM_0_1() * 200) % 8;
// m_cardList.swap(m_cardList.at(a), m_cardList.at(b));
//}
/////////////////////////////////////////////////////////////
//佈局,需要更佳的螢幕自適應的話,baseX、baseY則需要根據實際螢幕解析度進行計算
int baseX = 70;
int baseY = 150;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
m_cardList.at(i * 4 + j)->setPosition(Point(baseX + i * 60, baseY + j * 60));
}
}
/////////////////////////////////////////////////////////////
//延遲2秒後開始計時並監聽
//遊戲機制,讓玩家儘量記住卡牌
this->scheduleOnce
(
[=](float dt){
for (auto x : m_cardList) x->scaleCard();
m_ptimeCount->startCounter();//計時
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(ActionScene::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(ActionScene::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(ActionScene::onTouchEnded, this);
//listener->setSwallowTouches(true);//不擊穿
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
},
2,//延遲2秒
"listener"
);
/////////////////////////////////////////////////////////////
return true;
}
void ActionScene::update(float dt)
{
if (m_time + 1 == m_ptimeCount->getTimeInt())
{
m_pointLabel->setString(StringUtils::format("%d", m_ptimeCount->getTimeInt()));
m_time++;
}
}
void ActionScene::playItem(Ref* pSender)
{
Director::getInstance()->end();
}
void ActionScene::replay(Ref* pSender)
{
Director::getInstance()->replaceScene(ActionScene::createScene());
}
bool ActionScene::onTouchBegan(Touch* touch, Event* event)
{
auto x = touch->getLocation().x;
auto y = touch->getLocation().y;
if(!m_matching){//沒有在匹配中
for (auto p : m_cardList){
if (p->getBoundingBox().containsPoint(Point(x, y)) && p->getBackSprite()->getScaleX() == 1)
//點了這張牌 且 這張牌沒被翻
{
p->scaleCard();//翻牌
//在appDelegate.cpp裡面preLoad音訊檔案
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("Wing.wav", false);
if (m_pPlay == nullptr) m_pPlay = p;//m_pPlay用來標記第一張牌,用來匹配
else//翻的是第二張
{
m_matching = true;//那麼開始匹配
if (m_pPlay != p && m_pPlay->getName().compare(p->getName()) == 0)
//匹配成功:不是同一張牌 且 牌面相同 , 得分
{
scheduleOnce(//延遲執行
[=](float dt){
//得分的相關操作
if(8 != m_point)CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("Point.wav", false);
auto a = Sprite::create("eff_pass.png");
auto b = Sprite::create("eff_pass.png");
a->setPosition(m_pPlay->getPosition());
b->setPosition(p->getPosition());
this->addChild(a);
this->addChild(b);
m_pPlay = nullptr;
m_matching = false;//匹配結束
},
p->getDt() * 3,//延遲時間為翻牌時間的3倍
"pointing"
);
m_point++;//加分
if (8 == m_point)//分數8分,遊戲結束
{
m_ptimeCount->stopCounter();//終止計時
this->unscheduleUpdate();
///////////////////////////////////////////////////////
//結束動畫初始化
auto endSprite = Sprite::create("MapleHighSchool.clear.1.png");
endSprite->setPosition(Point(160, 240));
endSprite->setVisible(false);
this->addChild(endSprite,10);
auto anime = Animation::create();
for (int i = 1; i < 8; i ++)
{
char szName[50] = { 0 };
sprintf(szName, "MapleHighSchool.clear.%d.png", i);
anime->addSpriteFrameWithFile(szName);
}
anime->setDelayPerUnit(1.0f / 7.0f);//因為一共7幀,設定播放1秒,則幀/秒 為 1/7
anime->setRestoreOriginalFrame(false);
auto act = Animate::create(anime);
act->retain();//為了讓下面的lambda捕捉到
///////////////////////////////////////////////////////
//遊戲結束2秒,播放通關動畫(持續1秒),聲音等
scheduleOnce(
[endSprite, act, this](float dt){
endSprite->setVisible(true);
endSprite->runAction(Repeat::create(act, 1));
act->release();
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("win.mp3", false);
},
2,
"endedAnime");
//遊戲結束後3秒,剛好是通關動畫播放完畢
scheduleOnce(
[endSprite, act](float dt){
endSprite->setVisible(false);
},
3,
"hideEndedAnime");
//////////////////////////////////////////////////////////
}//End 分數8分,遊戲結束
}//End 匹配成功
else//沒匹配成功
{
scheduleOnce(
//把牌翻回去
//重置相關變數
[=](float dt){
m_pPlay->scaleCard();
p->scaleCard();
m_pPlay = nullptr;
m_matching = false;
},
p->getDt() * 3,
"resetCard"
);
}//End 沒匹配成功
}
break;
}
}
}//沒有在匹配中
//匹配中
//other codes...
return true;
}
void ActionScene::onTouchMoved(Touch* touch, Event* event)
{
}
void ActionScene::onTouchEnded(Touch* touch, Event* event)
{
}