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

时间:2022-09-16 20:04:28

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

如果本教程有帮助到您,希望您能点击进去观看一下,而且现在注册成为极客学院的会员,验证手机号码和邮箱号码会赠送三天的会员时间,手机端首次也可以领取五天的会员时间哦(即使是购买年会员目前也仅仅是年费260),成为极客学院学习会员可以无限制的下载和观看所有的学院网站的视频,谢谢您的支持!

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

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

这节课我们要学习的内容有:
【Cocos游戏实战】功夫小子第三课之过渡场景和开始菜单的实现

场景的UI图如下:
主开始菜单场景:
【Cocos游戏实战】功夫小子第三课之过渡场景和开始菜单的实现

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

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

这部分要学习的内容有三个方面:
  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: 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类图:
【Cocos游戏实战】功夫小子第三课之过渡场景和开始菜单的实现
他们的关系就如名字一样,一个是容器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),成为极客学院学习会员可以无限制的下载和观看所有的学院网站的视频,谢谢您的支持!