仿《雷霆战机》飞行射击手游开发--资源预加载
2018-06-18 00:04:52来源:未知 阅读 ()
转载请注明:http://www.cnblogs.com/thorqq/p/5639022.html
项目首页:https://www.oschina.net/p/raiden
绝大多数游戏在启动后首先出现的是一个“载入中”的场景,此场景的用处是将游戏所需的图片、音乐、数据等资源从存储卡(或磁盘、闪存)读入内存,这样,后面需要用到这些资源时,可以直接从内存读取,以加快游戏的运行,提高流畅性。下面,就对资源的预加载机制做一个介绍。
资源的类型
预加载的目的是为了后续读取的快捷,所以,一般会预加载那些较大较复杂的文件,例如以下这些:
- 单张大图:背景大图
- 合成图:可多幅图片合成的大图,这里我们使用TexturePacker合成plist+png文件
- 骨骼动画:使用Cocos Skeletal Animation Editor创建的骨骼动画文件,ExportJson+plist+png文件
- 场景:使用Cocos Studio创建的csd文件
- 声音:ogg音乐文件
- 本地数据:游戏存档数据(格式为json文件)、游戏配置数据(例如关卡、飞机属性、子弹属性等固定的数据,格式为sqlite数据库文件)
- 远程数据:由于本游戏是弱联网游戏,所以保存在服务器上的数据不多。这里仅仅实现了用户登录、获取时间的功能
下面,我们将逐一介绍不同资源载入的方法。
加载方法
单张大图
- 定义std::vector<std::string> m_imageArray,将需要加载的图片路径放到容器中
- 对每个图片逐个调用Director::getInstance()->getTextureCache()->addImageAsync()函数进行加载,注意他的第二个参数CC_CALLBACK_1(Preload::asynLoadingImageDone, this, m_imageArray[i]),当一张图片加载结束后,系统就会调用Preload::asynLoadingImageDone函数,同时传入图片的路径作为输入参数。
- 在回调函数asynLoadingImageDone中,首先要通知界面加载进度,然后根据图片的总数和待加载数判断是否已经全部记载完成,若全部加载成功,则通知loadingDone(PreloadType::Image)
详细的代码如下所示:
1 //1、需要加载的png或jpg 2 m_imageArray.push_back("BigImg/Bag_Bg.png"); 3 m_imageArray.push_back("BigImg/BigScreen_Bg.png"); 4 m_imageArray.push_back("BigImg/Daily_Bg.png"); 5 m_imageArray.push_back("BigImg/MainUI_Bg.jpg"); 6 7 8 void Preload::asynLoadingImage() 9 { 10 //2、将图片加入全局cache中 11 m_iImageCnt = m_imageArray.size(); 12 for (unsigned i = 0; i < m_imageArray.size(); i++) 13 { 14 Director::getInstance()->getTextureCache()->addImageAsync( 15 m_imageArray[i], 16 CC_CALLBACK_1(Preload::asynLoadingImageDone, this, m_imageArray[i])); 17 } 18 } 19 20 //3、单张图片加载成功后的回调函数 21 void Preload::asynLoadingImageDone(Texture2D* texture, const std::string& filename) 22 { 23 //通知观察者加载进度 24 this->notifyProgress(++m_iTmpProgress); 25 m_iImageCnt--; 26 //全部加载完成 27 if (0 == m_iImageCnt) 28 { 29 m_bImageLoaded = true; 30 this->loadingDone(PreloadType::Image); 31 } 32 }
合成图
合成图的加载与单张图片的加载类似,不同之处在于在回调函数中多了一步加载plist文件:
SpriteFrameCache::getInstance()->addSpriteFramesWithFile(file.append(".plist"), texture);
1 //plist图片 2 std::vector<std::string> m_plistArray; 3 4 //1、需要加载的图片,不包含后缀名 5 m_plistArray.push_back("Bag"); 6 m_plistArray.push_back("Common"); 7 m_plistArray.push_back("Daily"); 8 9 void Preload::asynLoadingPlist() 10 { 11 //2、加载图片文件 12 m_iImagePlistCnt = m_plistArray.size(); 13 for (unsigned i = 0; i < m_plistArray.size(); i++) 14 { 15 Director::getInstance()->getTextureCache()->addImageAsync( 16 std::string(m_plistArray[i]).append(".png"), 17 CC_CALLBACK_1(Preload::asynLoadingPlistDone, this, m_plistArray[i])); 18 } 19 } 20 21 void Preload::asynLoadingPlistDone(Texture2D* texture, const std::string& filename) 22 { 23 this->notifyProgress(++m_iTmpProgress); 24 25 //3、加载plist文件 26 std::string file = filename; 27 SpriteFrameCache::getInstance()->addSpriteFramesWithFile(file.append(".plist"), texture); 28 m_iImagePlistCnt--; 29 30 if (0 == m_iImagePlistCnt) 31 { 32 //全部加载完成 33 m_bImagePlistLoaded = true; 34 this->loadingDone(PreloadType::Plist); 35 } 36 }
骨骼动画
骨骼动画也是类似的加载方法,先使用addArmatureFileInfoAsync()函数加载骨骼动画的图片、合图信息(plist文件)、动画信息(ExportJson文件),然后回调函数asynLoadingArmatureDone()。
1 std::vector<std::string> m_armatureArray; 2 m_armatureArray.push_back("Anim/Anim_Plane_01"); 3 m_armatureArray.push_back("Anim/Anim_Plane_02"); 4 m_armatureArray.push_back("Anim/Anim_Plane_03"); 5 6 void Preload::asynLoadingArmature() 7 { 8 auto p = m_armatureArray[m_iArmatureCnt]; 9 DEBUG_LOG("Preload::asynLoadingArmature: %s", p.c_str()); 10 ArmatureDataManager::getInstance()->addArmatureFileInfoAsync( 11 std::string(p).append("0.png"), 12 std::string(p).append("0.plist"), 13 std::string(p).append(".ExportJson"), 14 this, 15 CC_SCHEDULE_SELECTOR(Preload::asynLoadingArmatureDone)); 16 } 17 18 void Preload::asynLoadingArmatureDone(float dt) 19 { 20 this->notifyProgress(++m_iTmpProgress); 21 22 m_iArmatureCnt++; 23 if (m_armatureArray.size() == m_iArmatureCnt) 24 { 25 m_bArmatureLoaded = true; 26 this->loadingDone(PreloadType::Armature); 27 } 28 else 29 { 30 asynLoadingArmature(); 31 } 32 }
场景
场景并没有特殊的异步加载函数,只能通过CSLoader::createNode()和CSLoader::createTimeline()根据csd文件生成node,然后保存到自定义的map中,以后要使用场景数据时,从map中获取。
注意,此加载方法在cocos2dx-3.4中可以正常运行,在3.8中会出现错误,原因未知。不过加载单个场景文件的时间很短,一般并不会影响游戏的体验,所以本游戏的最新版本中并没有预加载场景文件。
1 std::vector<std::string> m_uiArray; 2 std::map<std::string, Node*> m_uiMap; 3 4 //菜单 5 m_uiArray.push_back("Bag.csb"); 6 m_uiArray.push_back("Daily.csb"); 7 m_uiArray.push_back("Instruction.csb"); 8 9 void Preload::syncLoadingUI() 10 { 11 //不能在非主线程中调用CSLoader::createNode,否则会导致OpenGL异常 12 for (auto file : m_uiArray) 13 { 14 auto node = Preload::getUI(file); 15 node->retain(); 16 m_uiMap.insert(std::map<std::string, Node*>::value_type(file, node)); 17 18 auto timeLine = CSLoader::createTimeline(file); 19 timeLine->retain(); 20 m_actionMap.insert(std::map<std::string, cocostudio::timeline::ActionTimeline*>::value_type(file, timeLine)); 21 22 DEBUG_LOG("Preload::syncLoadingUI: %s", file.c_str()); 23 this->notifyProgress(++m_iTmpProgress); 24 } 25 26 m_bUILoaded = true; 27 this->loadingDone(PreloadType::Ui); 28 } 29 30 Node* Preload::getUI(const std::string& filename) 31 { 32 DEBUG_LOG("Preload::getUI: %s", filename.c_str()); 33 return CSLoader::createNode(filename);; 34 35 //cocos2dx-3.8 不支持以下操作。3.4支持 36 //auto ui = m_uiMap.find(filename); 37 //if (ui != m_uiMap.end()) 38 //{ 39 // return ui->second; 40 //} 41 //else 42 //{ 43 // auto csb = CSLoader::createNode(filename); 44 // csb->retain(); 45 // m_uiMap.insert(std::map<std::string, Node*>::value_type(filename, csb)); 46 47 // return csb; 48 //} 49 }
声音
由于cocos提供了新老两种音频接口,所以声音文件的预加载也分成两种。
对于老的接口,需区分音乐和音效文件,并且函数没有返回值;
对于新的接口,不区分音乐和音效文件,通过回调来判断加载的结果。
//老的音频接口 CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic(filename); CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect(filename); //新的音频接口 AudioEngine::preload(filename, [filename](bool isSuccess){ if (!isSuccess) { DEBUG_LOG("Load fail: %s", path.c_str()); } });
本地数据
本地数据包括了:存档数据、游戏配置数据,及其他一些定制化的数据。这里我们可以使用cocos提供的异步任务接口+回调加载结果来进行预加载。
1 void Preload::asynLoadingDatabase() 2 { 3 auto loadEnd = [this](void*) 4 { 5 DEBUG_LOG("asynLoadingDatabase OK"); 6 7 m_bOtherLoaded = true; 8 this->loadingDone(PreloadType::Other); 9 }; 10 11 AsyncTaskPool::getInstance()->enqueue(AsyncTaskPool::TaskType::TASK_IO, loadEnd, (void*)NULL, [this]() 12 { 13 if (!GlobalData::getInstance()->initialize(this)) 14 { 15 CCLOG("Initialize globla data failed"); 16 this->notifyError("Initialize globla data failed"); 17 return; 18 } 19 20 m_iTmpProgress += PreloadProgress::GlobalData; 21 this->notifyProgress(m_iTmpProgress); 22 23 if (!GameData::getInstance()->loadData()) 24 { 25 CCLOG("Initialize game data failed"); 26 this->notifyError("Initialize game data failed"); 27 return; 28 } 29 30 m_iTmpProgress += PreloadProgress::GameData; 31 this->notifyProgress(m_iTmpProgress); 32 33 if (!AchievementMgr::getInstance()->init()) 34 { 35 CCLOG("Initialize achievement data failed"); 36 this->notifyError("Initialize achievement data failed"); 37 return; 38 } 39 40 m_iTmpProgress += PreloadProgress::AchievementMgr; 41 this->notifyProgress(m_iTmpProgress); 42 43 Sound::preload(this); 44 45 m_iTmpProgress += PreloadProgress::Sound; 46 this->notifyProgress(m_iTmpProgress); 47 }); 48 }
远程数据
远程数据一般是通过发送异步http或者其他tcp请求来实现数据的加载,根据网络协议的不同,相关的接口也各不相同,这里不再详述。
加载界面
在此加载界面中,我们使用一个仪表盘和转动的指针来告诉用户当前的加载进度。那么,后台加载任务与前台的指针转动是如何关联起来的呢?我们使用了观察者模式。下面上一张百度找出的观察者模式的图:
Observer模式的角色:
Subject(被观察者)
被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。
ConcreteSubject
被观察者的具体实现。包含一些基本的属性状态及其他操作。
Observer(观察者)
接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。
ConcreteObserver
观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。
在本游戏中实现了一个简化版的观察者模式:
1、首先,我们定义一个被观察者抽象类。其中定义了开始、进度、错误、警告、结束等接口。
1 class PreloadListener 2 { 3 public: 4 virtual void onStart() = 0; 5 virtual void onProgress(int percent) = 0; 6 virtual void onError(const char* info) = 0; 7 virtual void onWarning(const char* info) = 0; 8 virtual void onEnd(PreloadError errorCode) = 0; 9 };
2、定义载入界面场景,继承自PreloadListener,并实现onXXX接口。
1 class LoadingLayer : 2 public Layer, public PreloadListener 3 { 4 public: 5 static Scene* scene(); 6 7 LoadingLayer(); 8 virtual ~LoadingLayer(); 9 10 virtual bool init(); 11 virtual void update(float dt) override; 12 13 CREATE_FUNC(LoadingLayer); 14 15 void initUI(); 16 void ToMainMenu(); 17 18 virtual void onStart() override; 19 virtual void onProgress(int percent) override; 20 virtual void onError(const char* info) override; 21 virtual void onWarning(const char* info) override; 22 virtual void onEnd(PreloadError errorCode) override; 23 24 private: 25 Node* m_pRootNode; 26 27 Sprite* m_pNeedle; 28 ui::LoadingBar* m_pLoadingBar; 29 ui::Text* m_pTxtErrorInfo; 30 31 long m_iBeginTime; 32 long m_iEndTime; 33 34 int m_iStart; 35 };
特别注意一下onProgress接口,这里需要实现指针转动的逻辑:
1 void LoadingLayer::onProgress(int percent) 2 { 3 float degree = LoadingLayerConstant::NeedleMinDegree + 4 (LoadingLayerConstant::NeedleMaxDegree - LoadingLayerConstant::NeedleMinDegree) * percent / 100; 5 m_pNeedle->setRotation(degree); 6 }
3、在加载任务中添加上报载入进度的函数。这样,每当载入一张图片或者任意一个资源文件的时候,就可以调用notifyProgress函数以使得界面上的指针转动了。
1 void Preload::notifyProgress(int progress) 2 { 3 //这里的m_pListener其实就是LoadingLayer的实例 4 if (m_pListener) 5 { 6 m_pListener->onProgress((int)(progress * 100.f / m_iAllProgress)); 7 } 8 }
下载源代码
转载请注明:http://www.cnblogs.com/thorqq/p/5639022.html
下一篇,我们将分析游戏的核心:“飞机”
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:vs2008+qt进行开发
- 洛谷 P2756 飞行员配对方案问题 2018-09-01
- 洛谷P2762 太空飞行计划问题(最大权闭合图) 2018-07-25
- 仿《雷霆战机》飞行射击手游开发--项目总览 2018-06-27
- 骑士飞行棋 C#代码详解 2018-06-18
- 仿《雷霆战机》飞行射击手游开发--项目总览 2018-06-18
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash