[轉載]cocos2d-x3.2源碼分析(一)類FileUtils--實現把資源放在Resources文件目錄下達到多平臺的引用

當你創建TMXTiledMap* tilemap=TMXTiledMap::create("test1.tmx")或Sprite *sprite=Sprite("HelloWorld.p-ng"),有沒有產生這樣的疑問--爲什麼把資源test1.tmx和HelloWorld.png放在項目目錄下的Resources文件中即可直接引用而不用標明具體路徑,並且可以在多個平臺下引用?或許很多人就會這樣說:“別人告訴我放在這個文件夾中就可以了,我自己使用確實可行,也沒有出錯,我就沒有多去探究了”。如果你想知道這具體原因,就要閱讀下面的分析了。如果你並不關心其原因,你可以關閉這個網頁了。 
     我以TMXTiledMap::Create函數爲講解對象。
    首先轉到TMXTiledMap::Create的定義中,其定義是在CCFastTMXTiledMap.cpp文件中,代碼1如下。其目錄是E:\mycoscos2d\test2\cocos2d\cocos\2d中,這就說明這是與具體平臺無關的,後面我們會看到已具體平臺相關的代碼。
   代碼1: 
[cpp] view plaincopy
<span style="font-size:18px;">TMXTiledMap * TMXTiledMap::create(const std::string& tmxFile)  
{  
    TMXTiledMap *ret = new TMXTiledMap();  
    if (ret->initWithTMXFile(tmxFile))  
    {  
        ret->autorelease();  
        return ret;  
    }  
    CC_SAFE_DELETE(ret);  
    return nullptr;  
}</span>  
   在代碼1中,我們可以看到先創建一個TMXTileMap對象,然後初始化,最後加入自動釋放池。如果想了解cosco2d-x3.2內存的管理,請繼續關注我的博客。在這裏我們也完全沒有看到關於路徑相關的字符串。其中讓人覺得,路徑設置有可能在TMXTiledMap()::initWithTMXFile()中,於是我們繼續轉到TMXTiledMap()::initWithTMXFile()定義中。代碼2如下。
   代碼2:
[cpp] view plaincopy
<span style="font-size:18px;">bool TMXTiledMap::initWithTMXFile(const std::string& tmxFile)  
{  
    CCASSERT(tmxFile.size()>0, "FastTMXTiledMap: tmx file should not be empty");   
    setContentSize(Size::ZERO);  
    TMXMapInfo *mapInfo = TMXMapInfo::create(tmxFile);  
    if (! mapInfo)  
    {  
        return false;  
    }  
    CCASSERT( !mapInfo->getTilesets().empty(), "FastTMXTiledMap: Map not found. Please check the filename.");  
    buildWithMapInfo(mapInfo);  
    return true;  
}</span>  
    在代碼2中,我們也沒有發現關於路徑字符串的信息。再看看代碼1中只調用了此函數,我們由此推斷路徑字符串設定在此函數或此函數的調用中的概率非常大。在代碼2中,我們可以看到兩個函數的調用,TMXMapInfo::create()和buildWithMapInfo(),顯然,TMXMapInfo::create的函數名讓我們覺得路徑字符串的設置在其中概率更大,因此我們轉到TMXMapInfo::create代碼定義中,其代碼在CCTMXXMLParser.cpp文件中,如代碼3。其目錄是E:\mycoscos2d\test2\cocos2d\cocos\2d,這就說明以平臺無關。
  代碼3:
[cpp] view plaincopy
<span style="font-size:18px;">TMXMapInfo * TMXMapInfo::create(const std::string& tmxFile)  
{  
    TMXMapInfo *ret = new TMXMapInfo();  
    if(ret->initWithTMXFile(tmxFile))  
    {  
        ret->autorelease();  
        return ret;  
    }  
    CC_SAFE_DELETE(ret);  
    return nullptr;  
}</span>  
  在代碼3中,如同代碼1的分析,我們要轉到TMXMapInfo::initWithTMXFile()的定義中,如代碼4。
  代碼4:
[cpp] view plaincopy
<span style="font-size:18px;">bool TMXMapInfo::initWithTMXFile(const std::string& tmxFile)  
{  
    internalInit(tmxFile, "");  
    return parseXMLFile(_TMXFileName.c_str());  
}  
</span>  
  在代碼4中,我們還是沒有看到路徑字符串的設定,如同代碼2的分析,我們要轉到同一個類中的internalInit()函數中,如代碼5。
   代碼5:
[cpp] view plaincopy
<span style="font-size:18px;">void TMXMapInfo::internalInit(const std::string& tmxFileName, const std::string& resourcePath)  
{  
    if (tmxFileName.size() > 0)  
    {  
        _TMXFileName = FileUtils::getInstance()->fullPathForFilename(tmxFileName);  
    }  
    if (resourcePath.size() > 0)  
    {  
        _resources = resourcePath;  
    }  
    ...  
}</span>  
   在代碼5中,我們終於看到fullpath的字樣了,這就說明路徑字符串的設定就在眼前了。於是我們就要轉到FileUtils::getInstance()->fullPathForFilename()函數定義中,其類定義在CCFileUtils.cpp中,如代碼6,由於代碼有點長,只貼出關鍵部分。此時我們看看CCFileUtils.cpp的路徑,我們會發現路徑是E:\mycoscos2d\test2\cocos2d\cocos\platform,到這裏我們終於看到platform這個關鍵字,這就說明已經到了與平臺相關的代碼中。
   代碼6:
[cpp] view plaincopy
<span style="font-size:18px;">std::string FileUtils::fullPathForFilename(const std::string &filename)  
{  
    ...  
    std::string fullpath;   
    for (auto searchIt = _searchPathArray.cbegin(); searchIt != _searchPathArray.cend(); ++searchIt)  
    {  
        for (auto resolutionIt = _searchResolutionsOrderArray.cbegin(); resolutionIt != _searchResolutionsOrderArray.cend(); ++resolutionIt)  
        {  
            fullpath = this->getPathForFilename(newFilename, *resolutionIt, *searchIt);  
              
            if (fullpath.length() > 0)  
            {  
                // Using the filename passed in as key.  
                _fullPathCache.insert(std::make_pair(filename, fullpath));  
                return fullpath;  
            }  
        }  
    }  
   ...  
}</span>  
    在代碼6中,我們看到了this->getPathForFilename(),你會不會覺得奇怪,其他函數的調用都沒有加this,就它加了this,具體原因在後面講解。在這裏,顯然我們對this->getPathForFilename()的興趣最大,於是我們就轉到其定義中如代碼7,其實從後面講解中,可以看到代碼是先轉到與平臺一致的FileUtilsxxx::getPathForFilename()中,然後再由平臺FileUtilsxxx::getPathForFileName 調用FileUtils::getPathForFilename()。平臺的getPathForFillname作用是把路徑格式轉化爲符合平臺路徑的格式。
    代碼7:
[cpp] view plaincopy
<span style="font-size:18px;">std::string FileUtils::getPathForFilename(const std::string& filename, const std::string& resolutionDirectory, const std::string& searchPath)  
{  
    std::string file = filename;  
    std::string file_path = "";  
    size_t pos = filename.find_last_of("/");  
    if (pos != std::string::npos)  
    {  
        file_path = filename.substr(0, pos+1);  
        file = filename.substr(pos+1);  
    }  
    // searchPath + file_path + resourceDirectory  
    std::string path = searchPath;  
    path += file_path;  
    path += resolutionDirectory;  
    path = getFullPathForDirectoryAndFilename(path, file);  
    //CCLOG("getPathForFilename, fullPath = %s", path.c_str());  
    return path;  
}</span>  
  在代碼 7中,我們看到這個函數作用是把資源路徑和一開始create的文件名相連接。我們轉到getFullPathFor-
DirectoryAndFilename()函數定義中,如代碼8。
  代碼8:
[cpp] view plaincopy
<span style="font-size:18px;">std::string FileUtils::getFullPathForDirectoryAndFilename(const std::string& directory, const std::string& filename)  
{  
    // get directory+filename, safely adding '/' as necessary   
    std::string ret = directory;  
    if (directory.size() && directory[directory.size()-1] != '/'){  
        ret += '/';  
    }  
    ret += filename;  
      
    // if the file doesn't exist, return an empty string  
    if (!isFileExistInternal(ret)) {  
        ret = "";  
    }  
    return ret;  
}</span>  
   在代碼8中,我們看到是字符串的連接,根本沒有看到資源路徑的獲取。於是我們就回到代碼7中。
   在代碼7中,我們看到searchPath變量,從代碼註釋中可以看到//searchPath + file_path + resourceDirectory,就可以發現searchPath就是我們路徑的名稱。
   在代碼7中我們看到也只是字符串的連接,而且searchPath是作爲參數傳入的。於是我們就回到代碼6中。
   在代碼6中,代碼7中的searchPath只是_searchPathArray中的一個迭代器。好,這就說明路徑就藏_searchPathArray中,最後我們在CCFileUtils.cpp文件中找到了FileUtils::init(),如代碼9。
    代碼9:
[cpp] view plaincopy
<span style="font-size:18px;">bool FileUtils::init()  
{  
    _searchPathArray.push_back(_defaultResRootPath);  
    _searchResolutionsOrderArray.push_back("");  
    return true;  
}</span>  
   在代碼9中,我們看到了_searchPathArray.push_back(_defaultResRootPath),好的,這就是把路徑放進容器中。而又是什麼函數調用init()函數?當然是調用代碼6中的函數的變量,也我們就回到代碼5中this->getInstance()返回的變量。
   於是我們就轉到this->getInstance代碼中,此時的目錄是E:\mycoscos2d\test2\cocos2d\cocos\platform\win32\CCFileUtilsWin32.cpp,好的,終於轉到與平臺相關的目錄中了。注意我用的VS2012來開發,所以才轉到win32這個目錄中,如果你是Eclipse來開發,你就轉到E:\mycoscos2d\test2\cocos2d\cocos\platform\android\CCFileUtils-Android.cpp這個目錄中。如果是IOS,你就轉到E:\mycoscos2d\test2\cocos2d\cocos\platform\apple\CCFileUtils-Apple.mm。這是爲什麼轉到相關的平臺的CCFileUtilsxxx.cpp中呢,這是因爲在每個與平臺相關的頭文件中有#if CC_TARGET_PLATFORM == CC_PLATFORM_XXX的條件預處理,也這樣說在那個平臺就包含那個頭文件。例如:在CCFileUtilsWin32.h文件中有代碼10。這是很巧妙的技巧!
   代碼10:
[cpp] view plaincopy
<span style="font-size:18px;">#include "base/CCPlatformConfig.h"  
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32  
#include "CCStdC.h"  
#include "platform/CCCommon.h"  
#include "platform/CCApplicationProtocol.h"  
#include <string></span>  
    我們來看看this->getInstance()的代碼,如代碼11。此時的FileUtils* FileUtils::getInstance()是在CCFileUtils-Win32.cpp中的而不是在CCFileUtils.cpp中。這是爲了誇平臺,s_sharedFileUtils是在FileUtils中定義的。FileUtils-Win32是繼承FileUtils的。這是很巧妙的技巧!
  代碼11:
[cpp] view plaincopy
<span style="font-size:18px;">FileUtils* FileUtils::getInstance()  
{  
    if (s_sharedFileUtils == nullptr)  
    {  
        s_sharedFileUtils = new FileUtilsWin32();  
        if(!s_sharedFileUtils->init())  
        {  
          delete s_sharedFileUtils;  
          s_sharedFileUtils = nullptr;  
          CCLOG("ERROR: Could not init CCFileUtilsWin32");  
        }  
    }  
    return s_sharedFileUtils;  
}</span>  
    在代碼11中,我們看到s_sharedFileUtils->init(),於是轉到定義處,由於此時s_sharedFileUtils是從FileUtilsWin32轉換而來的,而且在FileUtils中init()爲虛函數,所以init()會轉到FileUtilsWin32::init(),而不是FileUtils->init(),這是c++的多態。FileUtilsWin32::init()如代碼12。
   代碼12:
[cpp] view plaincopy
<span style="font-size:18px;">bool FileUtilsWin32::init()  
{  
    _checkPath();  
    _defaultResRootPath = s_resourcePath;  
    return FileUtils::init();  
}</span>  
   在代碼12中,我們看到_checkPath()函數,那就轉到它的定義看看,如代碼13。
   代碼13:
[cpp] view plaincopy
<span style="font-size:18px;">static void _checkPath()  
{  
    if (0 == s_resourcePath.length())  
    {  
        WCHAR utf16Path[CC_MAX_PATH] = {0};  
        GetCurrentDirectoryW(sizeof(utf16Path)-1, utf16Path);  
          
        char utf8Path[CC_MAX_PATH] = {0};  
        int nNum = WideCharToMultiByte(CP_UTF8, 0, utf16Path, -1, utf8Path, sizeof(utf8Path), nullptr, nullptr);  
  
        s_resourcePath = convertPathFormatToUnixStyle(utf8Path);  
        s_resourcePath.append("/");  
    }  
}</span>  
    好吧,在這裏我們終於看到win32平臺獲得路徑的函數GetCurrentDirectoryW(sizeof(utf16Path)-1, utf16Path),這個函數就是獲得資源路徑的,例如路徑E:\mycoscos2d\test2\Resources。到此爲止,我們終於找到這個設置資源路徑的函數了。在Android平臺的代碼如代碼14,每次在用Eclipse導入項目,會先把Resource的資源複製到E:\mycoscos-2d\test2\proj.android\assets這個路徑中,以保持同步。
   代碼14:
[cpp] view plaincopy
<span style="font-size:18px;">bool FileUtilsAndroid::init()  
{  
    _defaultResRootPath = "assets/";  
    return FileUtils::init();  
}</span>  
   回到代碼12中,FileUtilsWin32::init()最後還是調用了FileUtils::init(),那我們來看看FileUtils::init()的定義,如代碼15。這是很巧妙的機巧!
   代碼15:
[cpp] view plaincopy
<span style="font-size:18px;">bool FileUtils::init()  
{  
    _searchPathArray.push_back(_defaultResRootPath);  
    _searchResolutionsOrderArray.push_back("");  
    return true;  
}  
</span>  
   在代碼15中,路徑字符串加入了_searchPathArray容器中!
   我們現在回到代碼6中,this->getPathForFilename(newFilename, *resolutionIt, *searchIt),爲什麼加入this?那就要看看代碼6的函數調用者代碼5,在代碼5中有FileUtils::getInstance(),它返回的是由FileUtilsWin32轉換而來的,而FileUtils中getPathForFilename爲虛函數,根據C++多態,所以會調用FileUtilsWin32::getPathForFilename()。如代碼16。
   代碼16:
[cpp] view plaincopy
<span style="font-size:18px;">std::string FileUtilsWin32::getPathForFilename(const std::string& filename, const std::string& resolutionDirectory, const std::string& searchPath)  
{  
    std::string unixFileName = convertPathFormatToUnixStyle(filename);  
    std::string unixResolutionDirectory = convertPathFormatToUnixStyle(resolutionDirectory);  
    std::string unixSearchPath = convertPathFormatToUnixStyle(searchPath);  
    return FileUtils::getPathForFilename(unixFileName, unixResolutionDirectory, unixSearchPath);  
}</span>  
  在代碼16中,我們看到FileUtilsWin32::getPathForFilename()作用是把路徑轉換爲符合平臺的路徑格式。
  到此爲止,我們詳細講解了cocos2d-x3.2如何通過FileUtils類來實現把資源放在Resources文件目錄下達到多平臺的引用。
  最後,我們最後用一張圖片作爲總結。
  


    如需轉載,請標明出處,http://blog.csdn.net/cbbbc/article/details/38178753

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章