植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/xjvbb
一、类介绍
游戏启动后就会立即切换到游戏加载场景中。只有游戏资源文件加载完成后,才能进入游戏。Loadingscene类继承Cocos2d-x中的Scene父类,表明Loadingscene是一个场景类。切换到LoadingScene场景中,首先会创建该场景,会调用init函数,在该函数中调用自己定义的方法实现指定功能。
代码文件的位置如下图所示。
LoadingScene.h
/**
*Copyright (c) 2019 LZ. All Right Reserved
*Author : LZ
*Date: 2019.7.15
*Emal: 2117610943@qq.com
*/
#pragma once
#include "cocos2d.h"
#include "Scenes/MainMenuScene/MainMenu.h"
#include "ui/CocosGUI.h"
#include "Based/GlobalVariable.h"
#include "network/CCDownloader.h"
#include "Based/UserData.h"
using namespace cocos2d;
using namespace cocos2d::ui;
using namespace cocos2d::experimental;
class LoadingScene :public Scene
{
public:
CREATE_FUNC(LoadingScene);
static Scene* createLaodingScene();
static void loadUserFileData();
static void caveUserFileData();
CC_CONSTRUCTOR_ACCESS:
LoadingScene();
~LoadingScene();
virtual bool init() override;
private:
void update(float Time) override; /* 定时器 */
void setSystem(); /* 设置系统参数 */
void calculateFileNumbers(); /* 计算文件总数 */
void setRunFirstTime(); /* 获取第一次运行时间 */
void loadUserData(); /* 加载用户数据 */
void loadingText(); /* 加载文字 */
void loadingImage(); /* 加载图片 */
void loadingMusic(); /* 加载音乐 */
void loadingAnimation(); /* 加载动画 */
void showLoadingBackGround(); /* 展示背景 */
void showTileAndLoadingBar(); /* 展示进度条 */
void showLoadingBarFlower(const int &ID); /* 展示进度条上的花 */
void beginLoadingImageAndMusic(); /* 开始加载图片与音乐 */
void runLoGoCallBack(Node* node,const int &ID); /* 展示logo回调 */
void loadingTextCallBack(); /* 加载文字回调 */
void loadingAnimationCallBack(); /* 加载动画回调 */
void beginGameCallBack(Ref* pSender); /* 开始游戏回调 */
int openResourcesPath(map<string, string>& Path, const std::string& xml, bool IsEncryption = false); /* 打开资源路径 */
void throwException();
void checkEdition();
void changeFiles();
private:
int _textNumbers; // 文本数
int _loadFileNumbers; // 文件加载数
int _allFileNumbers; // 文件总数(图片,音乐,动画,文本)
bool _flowerVisible[5] = { false }; // 加载花朵是否可见
float _loadingPrecent; // 加载进度
Sprite* _sprite[8]; // 精灵图片
MenuItemLabel* _label; // 文字标签
LoadingBar* _loadingBar; // 进度条
EventListenerTouchOneByOne* _listener; // 加载监听
Global* _global; // 全局变量单例
Director* _director; // 导演单例
FileUtils* _files; // 文件单例
UserData* _userData;
std::unique_ptr<network::Downloader> _downloader;
};
LoadingScene.cpp
由于cpp文件中的代码数量太多,所以就不全部放到这里了。
/**
*Copyright (c) 2019 LZ.All Right Reserved
*Author : LZ
*Date: 2019.7.15
*Emal: 2117610943@qq.com
*/
#include "LoadingScene.h"
#include "tinyxml2/tinyxml2.h"
#include "Based/LevelData.h"
#include "Based/UserInformation.h"
#include "Based/PlayMusic.h"
#include "AudioEngine.h"
#define MYDEBUG 1
LoadingScene::LoadingScene() :
_loadFileNumbers(0),
_textNumbers(0),
_allFileNumbers(0),
_loadingPrecent(0),
_label(nullptr),
_loadingBar(nullptr),
_listener(nullptr),
_global(Global::getInstance()),
_director(Director::getInstance()),
_files(FileUtils::getInstance()),
_userData(UserData::getInstance())
{
_downloader.reset(new network::Downloader());
}
LoadingScene::~LoadingScene()
{
SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("resources/images/LoadingScene/LoadingScene.plist");
}
Scene* LoadingScene::createLaodingScene()
{
return LoadingScene::create();
}
bool LoadingScene::init()
{
if (!Scene::init())return false;
#if MYRELEASE
# if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
checkEdition(); /* 检查版本 */
# endif
#endif
setRunFirstTime(); /* 获取第一次运行时间 */
calculateFileNumbers(); /* 计算文件总数 */
setSystem(); /* 设置系统参数 */
loadUserData(); /* 加载用户信息 */
showLoadingBackGround(); /* 展示加载界面 */
return true;
}
//... 省略,代码数量太多,请到github/gitee仓库中查看
二、函数讲解
init()函数
bool LoadingScene::init()
{
if (!Scene::init())return false;
#if MYRELEASE
# if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
checkEdition(); /* 检查版本 */
# endif
#endif
setRunFirstTime(); /* 获取第一次运行时间 */
calculateFileNumbers(); /* 计算文件总数 */
setSystem(); /* 设置系统参数 */
loadUserData(); /* 加载用户信息 */
showLoadingBackGround(); /* 展示加载界面 */
return true;
}
LoadingScene中的init函数重写父类Scene中的init函数。在该函数中首先调用父类init函数,如果父类init函数初始化失败,表示场景创建失败,所以直接返回false,退出场景。否则继续向下执行。
#if MYRELEASE
# if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
checkEdition(); /* 检查版本 */
# endif
#endif
上述代码使用了条件编译,如果是release发布的版本,并且平台是windows,则会执行版本更新检查函数。否则不会编译这段代码。在debug版本中不会进行版本更新检查。
之后init函数会继续往下执行以下函数。如果全部执行成功,最后返回true,表示场景创成功。
setRunFirstTime(); /* 获取第一次运行时间 */
calculateFileNumbers(); /* 计算文件总数 */
setSystem(); /* 设置系统参数 */
loadUserData(); /* 加载用户信息 */
showLoadingBackGround(); /* 展示加载界面 */
setRunFirstTime()函数
void LoadingScene::setRunFirstTime()
{
time_t tt;
struct tm* nowtime;
time(&tt);
nowtime = localtime(&tt);
if (UserDefault::getInstance()->getStringForKey("FIRSTRUNTIME").size() == 0)
{
UserDefault::getInstance()->setStringForKey("FIRSTRUNTIME", to_string(nowtime->tm_year + 1900) + "年 " +
to_string(nowtime->tm_mon) + "月 " + to_string(nowtime->tm_mday) + "日 星期" + to_string(nowtime->tm_wday) + " " +
to_string(nowtime->tm_hour) + "时 " + to_string(nowtime->tm_min) + "分 " + to_string(nowtime->tm_sec) + "秒");
}
UserDefault::getInstance()->setIntegerForKey("BEGINDAY", nowtime->tm_mday);
UserDefault::getInstance()->setIntegerForKey("BEGINHOUR", nowtime->tm_hour);
UserDefault::getInstance()->setIntegerForKey("BEGINMIN", nowtime->tm_min);
UserDefault::getInstance()->setIntegerForKey("BEGINSEC", nowtime->tm_sec);
}
在该函数中首先获取系统时间,然后判断是否是第一次执行,如果是的话就会记录当前执行的时间。
在Cocos2d-x中使用UserDefault类来存储游戏信息,它存储的格式是html格式,使用键值对的方式存储信息。其中它的key值是唯一的,value值可以是字符串,整数,浮点数等类型。下面这行代码如果返回为空则表示第一次运行,然后更新该key(FIRSTRUNTIME),后续再次运行返回值就不为空,表示不是第一次运行。
UserDefault::getInstance()->getStringForKey("FIRSTRUNTIME")
calculateFileNumbers()函数
由于在加载文件的过程中需要计算加载进度,所有首先要统计需要加载文件的总数。才能根据此时加载文件数量和文件总数来计算加载的进度。
void LoadingScene::calculateFileNumbers()
{
#if MYDEBUG
/* 文件总数 = 文本数 + 图片数 + 音乐数 + 动画数 */
_allFileNumbers =
openResourcesPath(_global->userInformation->getTextPath(), "resources/Text/TextPath.reanim.compiled", true) +
openResourcesPath(_global->userInformation->getImagePath(), "resources/Text/ImagePath.reanim.compiled", true) +
openResourcesPath(_global->userInformation->getMusicPath(), "resources/Text/MusicPath.reanim.compiled", true) +
openResourcesPath(_global->userInformation->getAnimationPath(), "resources/Text/AnimationPath.reanim.compiled", true);
#else
_allFileNumbers =
openResourcesPath(_global->userInformation->getTextPath(), "resources/Text/TextPath.xml", false) +
openResourcesPath(_global->userInformation->getImagePath(), "resources/Text/ImagePath.xml", false) +
openResourcesPath(_global->userInformation->getMusicPath(), "resources/Text/MusicPath.xml", false) +
openResourcesPath(_global->userInformation->getAnimationPath(), "resources/Text/AnimationPath.xml", false);
#endif
}
openResourcesPath函数用来统计文件数量,资源文件总共被分为四种,分别是文本、图片、音乐以及动画。
openResourcesPath()函数
该函数用于统计文件数量,函数有三个参数,第一个参数是用于保存文件信息,使用C++中的map存储,第二个参数表示存储资源文件目录的文件的地址,该文件存储了游戏中所用到的某一类资源文件的目录。第三个参数是表示打开参数二中的文件是否需要解密,如果参数二中的文件是加密的,则需要将该参数设置为true。
int LoadingScene::openResourcesPath(map<string, string>& Path, const std::string& xml, bool IsEncryption)
{
tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument();
if (IsEncryption)
{
std::string textpath = _files->getStringFromFile(xml);
char* passWords = (char*)malloc(sizeof(char) * textpath.size());
OpenLevelData::getInstance()->decrypt(textpath, passWords);
/* 打开资源路径 */
doc->Parse(passWords);
}
else
{
/* 打开资源路径 */
doc->Parse(_files->getStringFromFile(xml).c_str());
}
auto root = doc->RootElement();
for (auto e = root->FirstChildElement(); e; e = e->NextSiblingElement())
{
for (auto at = e->FirstAttribute(); at; at = at->Next())
{
Path.insert(pair<string, string>(at->Name(), at->Value()));
}
}
delete doc;
return Path.size();
}
该函数首先打开参数二中的文件,如果加密则先解密,然后读取遍历读取该文件,并将信息保存到map中,然后返回文件数量。
setSystem()函数
该函数用于设置鼠标样式以及图标样式。
void LoadingScene::setSystem()
{
/* 设置光标 */
_director->getOpenGLView()->setCursor("resources/images/System/cursor.png", Point::ANCHOR_TOP_LEFT);
/* 设置图标 */
_director->getOpenGLView()->setIcon("resources/images/System/PlantsVsZombies.png");
}
loadUserData()函数
该函数用于加载用户存档信息,并根据信息初始化游戏。
游戏最多支持8个用户存档,遍历读取存档。然后初始化游戏设置,比如游戏帧率,是否拉升显示、是否垂直同步、是否隐藏鼠标等设置。这些设置都之前用户的个性化设置信息,加载游戏时自动读取恢复设置,无需用户再次手动设置。
void LoadingScene::loadUserData()
{
auto userdefault = UserDefault::getInstance();
/* 读取用户存档名称 */
for (int i = 0; i < 8; i++)
{
if (!userdefault->getStringForKey(_global->userInformation->getUserCaveFileNameKey(i).c_str()).size())
{
_global->userInformation->setUserCaveFileName(i, "未命名存档");
}
else
{
_global->userInformation->setUserCaveFileName(i, userdefault->getStringForKey(_global->userInformation->getUserCaveFileNameKey(i).c_str()));
}
}
_userData->createNewUserDataDocument();
loadUserFileData();
/* 用户名称 */
_global->userInformation->setUserName(_global->userInformation->getUserCaveFileName(_global->userInformation->getUserCaveFileNumber()));
_global->userInformation->setIsUpdate(true);
/* 显示信息 */
switch (userdefault->getBoolForKey("SHOWINFORMATION"))
{
case true:
_director->setDisplayStats(true);
_global->userInformation->setIsShowInformation(cocos2d::ui::CheckBox::EventType::SELECTED);
break;
case false:
_director->setDisplayStats(false);
_global->userInformation->setIsShowInformation(cocos2d::ui::CheckBox::EventType::UNSELECTED);
break;
}
/* 是否高帧率 */
switch (userdefault->getBoolForKey("SHOWHIGHFPS"))
{
case true:
_director->setAnimationInterval(1.0f / UserInformation::getScreenDisplayFrequency());
_global->userInformation->setIsSelectHighFPS(cocos2d::ui::CheckBox::EventType::SELECTED);
break;
case false:
_director->setAnimationInterval(1.0f / 30);
_global->userInformation->setIsSelectHighFPS(cocos2d::ui::CheckBox::EventType::UNSELECTED);
break;
}
/* 是否全屏 */
switch (userdefault->getBoolForKey("SHOWFULLSCREEN"))
{
case true:
((GLViewImpl*)_director->getOpenGLView())->setFullscreen();
_global->userInformation->setIsSelectFullScreen(cocos2d::ui::CheckBox::EventType::SELECTED);
break;
case false:
((GLViewImpl*)_director->getOpenGLView())->setWindowed(1280, 720);
_global->userInformation->setIsSelectFullScreen(cocos2d::ui::CheckBox::EventType::UNSELECTED);
break;
}
/* 是否拉伸显示 */
switch (userdefault->getBoolForKey("STRETCHINGSHOW"))
{
case true:
_global->userInformation->setIsSelectStretchingShow(cocos2d::ui::CheckBox::EventType::SELECTED);
_director->getOpenGLView()->setDesignResolutionSize(_director->getWinSize().width, _director->getWinSize().height, ResolutionPolicy::EXACT_FIT);
break;
case false:
_global->userInformation->setIsSelectStretchingShow(cocos2d::ui::CheckBox::EventType::UNSELECTED);
_director->getOpenGLView()->setDesignResolutionSize(_director->getWinSize().width, _director->getWinSize().height, ResolutionPolicy::SHOW_ALL);
break;
}
/* 是否垂直同步 */
switch (userdefault->getBoolForKey("VERTICALSYNCHRONIZATION"))
{
case true:
wglSwapIntervalEXT(1);
_global->userInformation->setIsVerticalSynchronization(CheckBox::EventType::SELECTED);
break;
case false:
_global->userInformation->setIsVerticalSynchronization(CheckBox::EventType::UNSELECTED);
wglSwapIntervalEXT(0);
break;
}
/* 是否隐藏鼠标 */
_global->userInformation->setIsSelectCursorNotHide(_userData->openBoolUserData("CURSORHIDE") ?
cocos2d::ui::CheckBox::EventType::SELECTED : cocos2d::ui::CheckBox::EventType::UNSELECTED);
/* 是否显示缓入动画 */
_global->userInformation->setIsEaseAnimation(_userData->openBoolUserData("EASEANIMATION") ?
cocos2d::ui::CheckBox::EventType::SELECTED : cocos2d::ui::CheckBox::EventType::UNSELECTED);
if (!userdefault->getBoolForKey("UPDATE0619"))
{
changeFiles();
userdefault->setBoolForKey("UPDATE0619", true);
}
}
update()函数
设置定时器函数scheduleUpdate后,每一帧都会调用update函数。根据不同的加载进度来播放不同的加载动画。
void LoadingScene::update(float Time)
{
if (_loadingPrecent <= 100)
{
_loadingBar->setPercent(_loadingPrecent); /* 设置加载进度 */
_sprite[5]->setScale(1 - _loadingPrecent / 170); /* 设置精灵大小 */
_sprite[5]->setRotation(9 * _loadingPrecent); /* 设置精旋转度数 */
_sprite[5]->setPosition(Vec2(10 + 290 / 100.0 * _loadingPrecent, 100 - _sprite[5]->getContentSize().height / 400 * _loadingPrecent));
if (_loadingPrecent >= 20) showLoadingBarFlower(0);
if (_loadingPrecent >= 40) showLoadingBarFlower(1);
if (_loadingPrecent >= 60) showLoadingBarFlower(2);
if (_loadingPrecent >= 80) showLoadingBarFlower(3);
if (_loadingPrecent >= 100) showLoadingBarFlower(4);
}
if (_loadingPrecent >= 100) /* 如果加载完成 */
{
_label->setString(_global->userInformation->getGameText().find("点击开始")->second); /* 重新设置标签文字内容 */
auto action = TintBy::create(0.5f, 0, 255, 255);
_label->runAction(RepeatForever::create(Sequence::create(action, action->reverse(), nullptr)));
_sprite[5]->setVisible(false); /* 设置精灵5为不可见 */
_label->setEnabled(true); /* 设置触摸可行 */
}
}
loadingText()/loadingMusic()/loadingImage()/loadingAnimation()
循环遍历文件,逐个进行加载。Cocos2d-x中文件加载是异步加载方式,会创建新的线程进行文件加载,与渲染线程是同时进行的。如果不使用异步加载方式,在文件加载的过程中会导致游戏卡顿。
void LoadingScene::loadingText()
{
/* 循环加载文本 */
for (auto& i : _global->userInformation->getTextPath())
{
ValueVector Text = _files->getValueVectorFromFile(i.second);
for (auto& file : Text)
{
auto file_map = file.asValueMap();
_global->userInformation->getGameText().insert(pair<string, string>(file_map.at("id").asString(), file_map.at("info").asString()));
}
/* 加载回调 */
this->loadingTextCallBack();
}
}
void LoadingScene::loadingImage()
{
/* 循环加载图片 */
for (auto& i : _global->userInformation->getImagePath())
{
_director->getTextureCache()->addImageAsync(i.second + "pvr.ccz", [=](Texture2D* texture)
{
SpriteFrameCache::getInstance()->addSpriteFramesWithFile(i.second + "plist", texture);
_loadFileNumbers++; /* 文件数加一 */
_loadingPrecent = ((_loadFileNumbers * 1.0f) / _allFileNumbers) * 100; /* 计算加载的百分比 */
});
}
}
void LoadingScene::loadingMusic()
{
/* 循环加载音乐 */
for (auto& i : _global->userInformation->getMusicPath())
{
AudioEngine::preload(i.second, [=](bool isSucceed)
{
if (isSucceed)/* 如果加载成功 */
{
_loadFileNumbers++; /* 文件数加一 */
_loadingPrecent = ((_loadFileNumbers * 1.0f) / _allFileNumbers) * 100; /* 计算加载的百分比 */
}
});
}
}
void LoadingScene::loadingAnimation()
{
/* 循环加载动画 */
for (auto& i : _global->userInformation->getAnimationPath())
{
/* 临时存储文件名字 */
char JsonName[128], AtlasName[128];
/* 转换 */
snprintf(JsonName, 128, "resources/Animations/compiled/%s.compiled", (i.second).c_str());
snprintf(AtlasName, 128, "resources/Animations/reanim/%s.reanim", (i.second).c_str());
/* 加载 */
spSkeletonJson* json = spSkeletonJson_createWithLoader((spAttachmentLoader*)Cocos2dAttachmentLoader_create(spAtlas_createFromFile(AtlasName, nullptr)));
auto skeletonData = spSkeletonJson_readSkeletonDataFile(json, JsonName);
spSkeletonJson_dispose(json);
/* 把加载到的动画放入map中*/
_global->userInformation->getAnimationData().insert(pair<string, spSkeletonData*>(i.second, skeletonData));
/* 进行回调 */
this->loadingAnimationCallBack();
}
}
void LoadingScene::loadingTextCallBack()
{
_loadFileNumbers++; /* 文件数加一 */
_loadingPrecent = ((_loadFileNumbers * 1.0f) / _allFileNumbers) * 100; /* 计算加载的百分比 */
}
void LoadingScene::loadingAnimationCallBack()
{
_loadFileNumbers++; /* 文件数加一 */
_loadingPrecent = ((_loadFileNumbers * 1.0f) / _allFileNumbers) * 100; /* 计算加载的百分比 */
}
其他函数
还有很多函数这里就不一一列举了,代码比较简单,可以自行调试理解。
三、后续
下一篇会详细讲解游戏关卡文件的结构组成以及游戏读取关卡信息的代码细节。