首页 > 代码库 > 【Cocos游戏实战】功夫小子第三课之过渡场景和開始菜单的实现

【Cocos游戏实战】功夫小子第三课之过渡场景和開始菜单的实现

本节课的视频教程地址是:第三课在此

假设本教程有帮助到您,希望您能点击进去观看一下,并且如今注冊成为极客学院的会员,验证手机号码和邮箱号码会赠送三天的会员时间,手机端首次也能够领取五天的会员时间哦(即使是购买年会员眼下也不过年费260),成为极客学院学习会员能够无限制的下载和观看全部的学院站点的视频,谢谢您的支持!


经过前面两节课的学习。我们已经知道我们要做的是一个什么样的游戏项目。而且对游戏的基本特点和当中的重难点有了一个主要的认识,而且完毕了项目环境的基本搭建。以及项目基础类等工作。

从这节课開始我们就将进入到实际的逻辑功能和场景开发中来,我们将依照第一节课分析的流程和游戏实际的执行流程,将我们的整个游戏项目的全部功能和场景各个击破,并逐步连接成一个完整的游戏项目。

这节课我们要学习的内容有:
技术分享

场景的UI图例如以下:
主開始菜单场景:
技术分享

秘籍场景:
技术分享

一、资源的异步载入和过渡场景的实现

这部分要学习的内容有三个方面:
  1. ?资源打包
  2. ?资源的异步载入
  3. ?过渡场景的分析和实现
首先是资源的打包,这里我用的打包工具是TexturePacker,关于此工具的详细用法,我这里不再描写叙述,您能够查看下官方文档或者本视频教程(用法很easy),怎样获取和激活该工具请參考本人之前的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: hu1020935219@gmail.com
 *
 * \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是专门用来承载菜单button的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)提供了基本button的状态:正常、选中、禁用。         (2)为button实现了主要的回调函数机制。当玩家点积button后,就会调用运行对应的回调函数。

        (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);

	// button初始化以及时间绑定
	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;
}

// button事件实现
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());
}

三、秘籍场景的实现

秘籍场景是一个非常easy的场景。效果图如上所看到的。其基本的一个逻辑就是点击两边的切换选项,或切换显示不同的图片。非常easy。
这里我直接贴出关键代码:
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);

	// 关闭button
	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);

	// 点击切换button
	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);

	// 点击切换button
	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),成为极客学院学习会员能够无限制的下载和观看全部的学院站点的视频,谢谢您的支持!



【Cocos游戏实战】功夫小子第三课之过渡场景和開始菜单的实现