1. 程式人生 > >【cocos2dx】卡牌記憶遊戲(2)——遊戲場景

【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)
{
}