程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 仿《雷霆戰機》飛行射擊手游開發--資源預加載,《雷霆戰機》射擊手

仿《雷霆戰機》飛行射擊手游開發--資源預加載,《雷霆戰機》射擊手

編輯:C++入門知識

仿《雷霆戰機》飛行射擊手游開發--資源預加載,《雷霆戰機》射擊手


    絕大多數游戲在啟動後首先出現的是一個“載入中”的場景,此場景的用處是將游戲所需的圖片、音樂、數據等資源從存儲卡(或磁盤、閃存)讀入內存,這樣,後面需要用到這些資源時,可以直接從內存讀取,以加快游戲的運行,提高流暢性。下面,就對資源的預加載機制做一個介紹。

資源的類型

預加載的目的是為了後續讀取的快捷,所以,一般會預加載那些較大較復雜的文件,例如以下這些:

  • 單張大圖:背景大圖
  • 合成圖:可多幅圖片合成的大圖,這裡我們使用TexturePacker合成plist+png文件
  • 骨骼動畫:使用Cocos Skeletal Animation Editor創建的骨骼動畫文件,ExportJson+plist+png文件
  • 場景:使用Cocos Studio創建的csd文件
  • 聲音:ogg音樂文件
  • 本地數據:游戲存檔數據(格式為json文件)、游戲配置數據(例如關卡、飛機屬性、子彈屬性等固定的數據,格式為sqlite數據庫文件)
  • 遠程數據:由於本游戲是弱聯網游戲,所以保存在服務器上的數據不多。這裡僅僅實現了用戶登錄、獲取時間的功能

下面,我們將逐一介紹不同資源載入的方法。

加載方法

單張大圖

詳細的代碼如下所示:

 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請求來實現數據的加載,根據網絡協議的不同,相關的接口也各不相同,這裡不再詳述。

加載界面

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 }

 下載源代碼

 

下一篇,我們將分析游戲的核心:“飛機”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved