【Cocos遊戲實戰】功夫小子第三課之過渡場景和開始菜單的實現

本節課的視頻教程地址是:第三課在此

如果本教程有幫助到您,希望您能點擊進去觀看一下,而且現在註冊成爲極客學院的會員,驗證手機號碼和郵箱號碼會贈送三天的會員時間,手機端首次也可以領取五天的會員時間哦(即使是購買年會員目前也僅僅是年費260),成爲極客學院學習會員可以無限制的下載和觀看所有的學院網站的視頻,謝謝您的支持!

經過前面兩節課的學習,我們已經知道我們要做的是一個什麼樣的遊戲項目,並且對遊戲的基本特點和其中的重難點有了一個基本的認識,並且完成了項目環境的基本搭建,以及項目基礎類等工作。

從這節課開始我們就將進入到實際的邏輯功能和場景開發中來,我們將按照第一節課分析的流程和遊戲實際的運行流程,將我們的整個遊戲項目的所有功能和場景各個擊破,並逐步連接成一個完整的遊戲項目。

這節課我們要學習的內容有:


場景的UI圖如下:
主開始菜單場景:


祕籍場景:


一、資源的異步加載和過渡場景的實現

這部分要學習的內容有三個方面:
  1. •資源打包
  2. •資源的異步加載
  3. •過渡場景的分析和實現
首先是資源的打包,這裏我用的打包工具是TexturePacker,關於此工具的具體用法,我這裏不再描述,您可以查看下官方文檔或者本視頻教程(使用方法非常簡單),如何獲取和激活該工具請參考本人之前的TexturePacker博文

其次是關於資源的異步加載方法,這裏使用的是:
資源的異步加載方法(TextureCache類)

voidTextureCache::addImageAsync(conststd::string &path, conststd::function<void(Texture2D*)>&callback)

這個方法的參數:

第一個是大圖文件路徑,第二個是一個回調函數。

下面是過渡場景的頭文件代碼:

/*!
 * \file SplashLayer.h
 *
 * \author SuooL_振生
 * \date 五月 2015
 *
 * 工作室Logo Splash界面
 */

#ifndef __SplashScene__H__
#define __SplashScene__H__

#include "cocos2d.h"
#include "SimpleAudioEngine.h"

USING_NS_CC;

class SplashLayer : public Layer
{
public:
	virtual bool init();
	static Scene* createScene();
	CREATE_FUNC(SplashLayer);

private:
	Sprite* logoSprite;
	// 資源加載
	void loadingTextureCallBack(Texture2D * texture);
	void loadingAudio();

	// 場景切換
	void nextScene(float dt);

	void onExit();
	// 初始化用戶數據
	void initUserData();

	int m_iNumOfLoad;
	std::thread* _loadingAudioThread;
};

#endif

下面是實現的CPP文件代碼:

/*!
 * \file SplashLayer.cpp
 * \date 2015/05/17 21:59
 *
 * \author SuooL
 * Contact: [email protected]
 *
 * \brief 過渡場景
 *
 * TODO: long description
 *
 * \note
*/

#include "SimpleAudioEngine.h"
#include "GlobalDefine.h"
#include "SplashLayer.h"
#include "StartLayer.h"

USING_NS_CC;
using namespace CocosDenshion;

Scene* SplashLayer::createScene()
{
	Scene* splashScene = Scene::create();
	SplashLayer* layer = SplashLayer::create();
	splashScene->addChild(layer);
	return splashScene;
}

bool SplashLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}

	//  初始化logo精靈
	logoSprite = Sprite::create("logo.png");
	logoSprite->setPosition(WINSIZE.width/2, WINSIZE.height/2);
	this->addChild(logoSprite);

	// 首次運行初始化用戶數據
	if (!getBoolFromXML("_IS_EXISTED"))
	{
		initUserData();
		setBoolToXML("_IS_EXISTED", true);
		UserDefault::getInstance()->flush();
	}

	setFloatToXML(SOUNDVOL, 0.80f);
	setFloatToXML(MUSICVOL, 0.35f);
	UserDefault::getInstance()->flush();

	m_iNumOfLoad = 0;
	// 圖片和聲音的異步加載
 	// 主界面
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/startGame.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	// 圖籍
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/gameLayer.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	// 設置
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/setLayer.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	// 祕籍
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/cheatsLayer.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	// 選關
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/gateMap.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	// 暫停
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/pauseLayer.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));

	// 英雄
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/hero.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/heroComobo.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));
	Director::getInstance()->getTextureCache()->addImageAsync("pnglist/heroGun.png", CC_CALLBACK_1(SplashLayer::loadingTextureCallBack, this));

	_loadingAudioThread = new std::thread(&SplashLayer::loadingAudio, this);

	return true;
}

void SplashLayer::loadingTextureCallBack(Texture2D * texture)
{
	switch (m_iNumOfLoad++)
	{
	case 0:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/startGame.plist", texture);
		break;
	case 1:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/gameLayer.plist", texture);
		break;
	case 2:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/setLayer.plist", texture);
		break;
	case 3:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/cheatsLayer.plist", texture);
		break;
	case 4:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/gateMap.plist", texture);
		break;
	case 5:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/pauseLayer.plist", texture);
		break;
	case 6:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/hero.plist", texture);
		break;
	case 7:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/heroComobo.plist", texture);
		break;
	case 8:
		SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/heroGun.plist", texture);
		this->schedule(schedule_selector(SplashLayer::nextScene), 1, 1, 1);
		break;
	default:
		break;
	}
}

void SplashLayer::loadingAudio()
{
	log("loadAudio");
	//初始化 音樂
    SimpleAudioEngine::getInstance()->preloadBackgroundMusic("Sound/startBGM.mp3");
	//初始化音效  
	SimpleAudioEngine::getInstance()->preloadEffect("Sound/button.wav");
}

void SplashLayer::initUserData()
{
	setIntToXML(GAMELEVEL_KEY, 1);        // 初始化關卡
	setIntToXML(HEROENERGY_KEY, 10);  // 初始化體力
	setIntToXML(HEROCOIN_KEY, 1000);    // 初始化金幣
	setBoolToXML(SOUND_KEY, true);
	setBoolToXML(MUSIC_KEY, true);
	// 刷新
	UserDefault::getInstance()->flush();
}

void SplashLayer::nextScene(float dt)
{
	Director::getInstance()->replaceScene(TransitionFade::create(2.0f, StartLayer::createScene()));
}

void SplashLayer::onExit()
{
	Layer::onExit();
	_loadingAudioThread->join();
	CC_SAFE_DELETE(_loadingAudioThread);
	this->unschedule(schedule_selector(SplashLayer::nextScene));
}

從上面的代碼中可以看出在圖片資源的預加載中,顯示加載紋理大圖,然後調用回調函數加載對應的plist配置文件,而在最後一個紋理完成加載之後,便會進行一個場景的切換的回調。

而聲音資源的預加載則是通過多線程實現的,新開一個線程,並使用Cocos的

//初始化 音樂
    SimpleAudioEngine::getInstance()->preloadBackgroundMusic("Sound/startBGM.mp3");
	//初始化音效  
	SimpleAudioEngine::getInstance()->preloadEffect("Sound/button.wav");

方法完成聲音資源的預加載,在場景退出的時候注意回收線程資源。

二、Menu家族的學習和菜單場景的實現

這一部分的主要內容也是三點:
Menu家族及其成員構成
Menu及各個成員的特點
開始菜單場景的分析和實現
下面是菜單Menu和菜單項MenuItem類圖:

他們的關係就如名字一樣,一個是容器Menu,一個是內容Item。
下面引用一段話:
菜單Menu是專門用來承載菜單按鈕的Layer圖層,圖層中的子節點只能夠是菜單項MenuItem或其子類。通常先創建菜單項MenuItem,然後使用一個或多個菜單項生成菜單Menu,最後把Menu加入當前Layer圖層。
如果直接在層中添加MenuItem也可以正常顯示,但是無法響應回調函數,因爲Menu是繼承至Layer,也就繼承了觸摸的相關事件,而MenuItem只是從Node繼承而來,並不響應觸摸,因此無法調用回調函數。
由於CCMenu的父類爲Layer,所以錨點爲(0,0),且無法設置錨點。Menu的默認原點座標爲屏幕正中心(winSize.width/2, winSize.height/2)。
而對於MenuItem是添加在Menu層中的,所以MenuItem的位置是相對於Menu層的偏移位置。MenuItem相對於Menu的偏移量默認爲(0,0),且菜單項的錨點默認爲(0.5,0.5)。
值得注意的是:Menu包含了多個子菜單項,每個子菜單項的位置都不一樣,如果定義了Menu的位置,那它作爲父節點會影響到所有的子菜單項的位置,所以一般我們都是吧Menu的位置設置在PointZero,然後設置MenuItem的位置(也就是相對父節點的偏移量)來定位整個菜單。

關於菜單項:

MenuItem繼承自Node,所以它的子類菜單項都可以使用Node的相關操作。
MenuItem是所有菜單項的父類,建議不要直接使用該類,因爲它並不包含具體顯示的功能。
作爲其它菜單項的父類,主要提供了一下三個功能:
        (1)提供了基本按鈕的狀態:正常、選中、禁用。
        (2)爲按鈕實現了基本的回調函數機制。當玩家點積按鈕後,就會調用執行相應的回調函數。
        (3)觸碰菜單項,附有自動放大效果。
菜單項的子類可以分成三類,總共六個:
        (1)文字菜單項:MenuItemLabel、MenuItemAtlasFont、MenuItemFont;
        (2)圖片菜單項:MenuItemSprite、MenuItemImage;
        (3)切換菜單項:MenuItemToggle。

而關於各個菜單項之前的區別這裏因爲內容過多不再贅述,大家可以去查看他的源碼和官方的文檔獲取相關知識,源碼是最好的學習資料。


下面就是主開始菜單場景的一部分關鍵代碼:

Scene* StartLayer::createScene()
{
	Scene* startScene = Scene::create();
	StartLayer* layer = StartLayer::create();
	startScene->addChild(layer);

	return startScene;
}

bool StartLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}

	// 加載遊戲圖片資源緩存
	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/galleryLayer.plist");
	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/monster.plist");
	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/resultLayer.plist");
	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/mapBg.plist");
	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pnglist/mapMid.plist");

	// 根據音樂的開關來播放背景音樂
	if (getBoolFromXML(MUSIC_KEY))
	{
		float music = getFloatFromXML(MUSICVOL)*100.0f;
		aduioEngine->setBackgroundMusicVolume(getFloatFromXML(MUSICVOL));
		if (SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
		{
			aduioEngine->pauseBackgroundMusic();
			aduioEngine->playBackgroundMusic("Sound/startBGM.mp3", true);
		}
		else
			aduioEngine->playBackgroundMusic("Sound/startBGM.mp3", true);
	}
	else
		aduioEngine->pauseBackgroundMusic();

	// 精靈初始化及位置設定
	title = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("Title.png"));
	title->setPosition(WINSIZE.width / 2 - 222, WINSIZE.height / 2 + 186);
	bgPic = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("MainMenuBackground.png"));
	bgPic->setPosition(WINSIZE.width / 2, WINSIZE.height / 2);

	this->addChild(bgPic);
	this->addChild(title);

	// 按鈕初始化以及時間綁定
	auto helpItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("HelpNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("HelpSelected.png")),
		CC_CALLBACK_1(StartLayer::touchHelp, this)); // 幫助

	auto tujiItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PhotoGalleryNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PhotoGallerySelected.png")),
		CC_CALLBACK_1(StartLayer::touchLib, this)); // 圖籍

	auto setItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("SetNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("SetSelected.png")),
		CC_CALLBACK_1(StartLayer::touchSet, this)); // 設置


	auto mijiItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("CheatsNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("CheatsSelected.png")),
		CC_CALLBACK_1(StartLayer::touchMiJi, this)); // 祕籍

	auto chuangguanItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("EmigratedNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("EmigratedSelected.png")),
		CC_CALLBACK_1(StartLayer::touchCG, this)); // 闖關

	auto tiaozhanItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("ChallengeNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("ChallengeSelected.png")),
		CC_CALLBACK_1(StartLayer::touchTZ, this)); // 挑戰
	
	tujiItem->setPosition(WINSIZE.width - 62, WINSIZE.height - 73);
	mijiItem->setPosition(WINSIZE.width - 62, WINSIZE.height - 209);
	setItem->setPosition(WINSIZE.width - 62, WINSIZE.height - 346);
	helpItem->setPosition(WINSIZE.width - 62, WINSIZE.height - 473);
	chuangguanItem->setPosition(WINSIZE.width / 2 - 240, WINSIZE.height / 2 - 86);
	tiaozhanItem->setPosition(WINSIZE.width / 2 - 240, WINSIZE.height / 2 - 250);


	auto menu = Menu::create(tujiItem,mijiItem, setItem, helpItem, chuangguanItem, tiaozhanItem, NULL);
	menu->setPosition(Point::ZERO);
	this->addChild(menu, 2);


	return true;
}

// 按鈕事件實現
void StartLayer::touchSet(Ref* pSender)
{
	PLAYEFFECT;
   Director::getInstance()->replaceScene(SetLayer::createScene());
}

void StartLayer::touchLib(Ref* pSender)
{
	PLAYEFFECT;
	Director::getInstance()->replaceScene(TujiLayer::createScene());
}

void StartLayer::touchMiJi(Ref* pSender)
{
	PLAYEFFECT;
	Director::getInstance()->replaceScene(MijiLayer::createScene());
}

void StartLayer::touchCG(Ref* pSender)
{
	if (getBoolFromXML(SOUND_KEY))
	{
		aduioEngine->setEffectsVolume(getFloatFromXML(SOUNDVOL));
		aduioEngine->playEffect("Sound/button.mp3");
	}
	Director::getInstance()->replaceScene(GateMapLayer::createScene());
}

void StartLayer::touchTZ(Ref* pSender)
{
	PLAYEFFECT;
//	Director::getInstance()->replaceScene(GateMapLayer::createScene());
}

void StartLayer::touchHelp(Ref* pSender)
{
	PLAYEFFECT;
	Director::getInstance()->replaceScene(HelpLayer::createScene());
}

三、祕籍場景的實現

祕籍場景是一個很簡單的場景,效果圖如上所示,其主要的一個邏輯就是點擊兩邊的切換選項,或切換顯示不同的圖片。非常簡單。
這裏我直接貼出關鍵代碼:
Scene* MijiLayer::createScene()
{
	Scene* scene = Scene::create();
	MijiLayer* layer = MijiLayer::create();
	scene->addChild(layer);
	return scene;
}

bool MijiLayer::init()
{
	if (!Layer::init())
	{
		return false;
	}
	flag = true;
	// 背景
	spriteBG = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("CheatsBackground.png"));
	spriteBG->setPosition(WINSIZE.width / 2, WINSIZE.height / 2);
	this->addChild(spriteBG);
	
	// 祕籍技能界面
	interface_1 = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("CheatsInterface1.png"));
	interface_2 = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("CheatsInterface2.png"));
	interface_1->setPosition(WINSIZE.width / 2, WINSIZE.height / 2 - 10);
	interface_1->setVisible(true);
	interface_2->setPosition(WINSIZE.width / 2, WINSIZE.height / 2 - 10);
	interface_2->setVisible(false);
	spriteBG->addChild(interface_1);
	spriteBG->addChild(interface_2);

	// 關閉按鈕
	auto closeItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("OffNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("offSelected.png")),
		[](Ref * ref){
		// 切換主界面場景
		PLAYEFFECT;
		Director::getInstance()->replaceScene(StartLayer::createScene()); });
	closeItem->setPosition(WINSIZE.width-164, WINSIZE.height-132);

	// 點擊切換按鈕
	auto nextRightItem = MenuItemSprite::create(
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PageTurnNormal.png")),
		Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PageTurnSelected.png")),
		[&](Ref * ref){
		PLAYEFFECT;
		// 切換祕籍
		if (flag)
		{
			interface_2->setVisible(true);
			flag = false;
		}
		else
		{
			interface_2->setVisible(false);
			flag = true;
		}
		 });
	nextRightItem->setPosition(WINSIZE.width - 55, WINSIZE.height / 2 - 14);

	// 點擊切換按鈕
	auto nor = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PageTurnNormal.png"));
	nor->setFlippedX(true);
	auto sel = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("PageTurnSelected.png"));
	sel->setFlippedX(true);
	auto nextLeftItem = MenuItemSprite::create(nor, sel,
		[&](Ref * ref){
		PLAYEFFECT;
		// 切換祕籍
		if (flag)
		{
			interface_2->setVisible(true);
			flag = false;
		}
		else
		{
			interface_2->setVisible(false);
			flag = true;
		}
	});
	nextLeftItem->setPosition(55, WINSIZE.height / 2 - 14);

	auto menu = Menu::create(closeItem, nextRightItem, nextLeftItem, NULL);
	menu->setPosition(Point::ZERO);

	spriteBG->addChild(menu);

	return true;
}

這就是本節課的主要內容,主要就是一個資源的預加載學習和Menu家族組件的學習。

轉載請註明出處,謝謝合作!

本節課的視頻教程地址是:第三課在此

如果本教程有幫助到您,希望您能點擊進去觀看一下,而且現在註冊成爲極客學院的會員,驗證手機號碼和郵箱號碼會贈送三天的會員時間,手機端首次也可以領取五天的會員時間哦(即使是購買年會員目前也僅僅是年費260),成爲極客學院學習會員可以無限制的下載和觀看所有的學院網站的視頻,謝謝您的支持!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章