1. 程式人生 > >cocos2d-x3.2原始碼分析之 ---- 類FileUtils實現把資源放在Resources檔案目錄下達到多平臺的引用

cocos2d-x3.2原始碼分析之 ---- 類FileUtils實現把資源放在Resources檔案目錄下達到多平臺的引用

  我以TMXTiledMap::Create函式為講解物件。

  首先轉到TMXTiledMap::Create的定義中,其定義是在CCFastTMXTiledMap.cpp檔案中,程式碼1如下。其目錄是E:\mycoscos2d\test2\cocos2d\cocos\2d中,這就說明這是與具體平臺無關的,後面我們會看到與具體平臺相關的程式碼。
程式碼1:  複製程式碼
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; }
複製程式碼

  在程式碼1中,我們可以看到先建立一個TMXTileMap物件,然後初始化,最後加入自動釋放池。在這裡我們也完全沒有看到關於路徑相關的字串。其中讓人覺得,路徑設定有可能在TMXTiledMap()::initWithTMXFile()中,於是我們繼續轉到TMXTiledMap()::initWithTMXFile()定義中。程式碼2如下。

程式碼2: 複製程式碼
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; }
複製程式碼

  在程式碼2中,我們也沒有發現關於路徑字串的資訊。再看看程式碼1中只調用了此函式,我們由此推斷路徑字串設定在此函式或此函式的呼叫中的概率非常大。在程式碼2中,我們可以看到兩個函式的呼叫,TMXMapInfo::create()和buildWithMapInfo(),顯然,TMXMapInfo::create的函式名讓我們覺得路徑字串的設定在其中概率更大,因此我們轉到TMXMapInfo::create程式碼定義中,其程式碼在CCTMXXMLParser.cpp檔案中,如程式碼3。其目錄是E:\mycoscos2d\test2\cocos2d\cocos\2d,這就說明與平臺無關。

程式碼3: 複製程式碼
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;  
}
複製程式碼

  在程式碼3中,如同程式碼1的分析,我們要轉到TMXMapInfo::initWithTMXFile()的定義中,如程式碼4。

程式碼4:
bool TMXMapInfo::initWithTMXFile(const std::string& tmxFile)  
{  
    internalInit(tmxFile, "");  
    return parseXMLFile(_TMXFileName.c_str());  
}
複製程式碼
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;  
    }  
    ...  
}
複製程式碼

  在程式碼5中,我們終於看到fullpath的字樣了,這就說明路徑字串的設定就在眼前了。於是我們就要轉到FileUtils::getInstance()->fullPathForFilename()函式定義中,其類定義在CCFileUtils.cpp中,如程式碼6,由於程式碼有點長,只貼出關鍵部分。此時我們看看CCFileUtils.cpp的路徑,我們會發現路徑是E:\mycoscos2d\test2\cocos2d\cocos\platform,到這裡我們終於看到platform這個關鍵字,這就說明已經到了與平臺相關的程式碼中。

程式碼6: 複製程式碼
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;  
            }  
        }  
    }  
   ...  
}
複製程式碼

  在程式碼6中,我們看到了this->getPathForFilename(),你會不會覺得奇怪,其他函式的呼叫都沒有加this,就它加了this,具體原因在後面講解。在這裡,顯然我們對this->getPathForFilename()的興趣最大,於是我們就轉到其定義中如程式碼7,其實從後面講解中,可以看到程式碼是先轉到與平臺一致的FileUtilsxxx::getPathForFilename()中,然後再由平臺FileUtilsxxx::getPathForFileName 呼叫FileUtils::getPathForFilename()。平臺的getPathForFillname作用是把路徑格式轉化為符合平臺路徑的格式

程式碼7: 複製程式碼
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 + resolutionDirectory  
    std::string path = searchPath;  
    path += file_path;  
    path += resolutionDirectory;  
    path = getFullPathForDirectoryAndFilename(path, file);  
    //CCLOG("getPathForFilename, fullPath = %s", path.c_str());  
    return path;  
}
複製程式碼

  在程式碼 7中,我們看到這個函式作用是把資源路徑和一開始create的檔名相連線。我們轉到getFullPathForDirectoryAndFilename()函式定義中,如程式碼8。

程式碼8: 複製程式碼
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;  
}
複製程式碼

  在程式碼8中,我們看到是字串的連線,根本沒有看到資源路徑的獲取。於是我們就回到程式碼7中。

  在程式碼7中,我們看到searchPath變數,從程式碼註釋中可以看到//searchPath + file_path + resolutionDirectory,就可以發現searchPath就是我們路徑的名稱。

  在程式碼7中我們看到也只是字串的連線,而且searchPath是作為引數傳入的。於是我們就回到程式碼6中。

  在程式碼6中,程式碼7中的searchPath只是_searchPathArray中的一個迭代器。好,這就說明路徑就藏_searchPathArray中,最後我們在CCFileUtils.cpp檔案中找到了FileUtils::init(),如程式碼9。

程式碼9:
bool FileUtils::init()  
{  
    _searchPathArray.push_back(_defaultResRootPath);  
    _searchResolutionsOrderArray.push_back("");  
    return true;  
}

  在程式碼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:
#include "base/CCPlatformConfig.h"  
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32  
#include "CCStdC.h"  
#include "platform/CCCommon.h"  
#include "platform/CCApplicationProtocol.h"  
#include <string>

  我們來看看this->getInstance()的程式碼,如程式碼11。此時的FileUtils* FileUtils::getInstance()是在CCFileUtils-Win32.cpp中的而不是在CCFileUtils.cpp中。這是為了跨平臺,s_sharedFileUtils是在FileUtils中定義的。FileUtils-Win32是繼承FileUtils的。這是很巧妙的技巧!

程式碼11: 複製程式碼
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;  
}
複製程式碼

  在程式碼11中,我們看到s_sharedFileUtils->init(),於是轉到定義處,由於此時s_sharedFileUtils是從FileUtilsWin32轉換而來的,而且在FileUtils中init()為虛擬函式,所以init()會轉到FileUtilsWin32::init(),而不是FileUtils->init(),這是c++的多型。FileUtilsWin32::init()如程式碼12。

程式碼12:
bool FileUtilsWin32::init()  
{  
    _checkPath();  
    _defaultResRootPath = s_resourcePath;  
    return FileUtils::init();  
}

  在程式碼12中,我們看到_checkPath()函式,那就轉到它的定義看看,如程式碼13。

程式碼13: 複製程式碼
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("/");  
    }  
}
複製程式碼

  好吧,在這裡我們終於看到win32平臺獲得路徑的函式GetCurrentDirectoryW(sizeof(utf16Path)-1, utf16Path),這個函式就是獲得資源路徑的,例如路徑E:\mycoscos2d\test2\Resources。到此為止,我們終於找到這個設定資源路徑的函數了。在Android平臺的程式碼如程式碼14,每次在用Eclipse匯入專案,會先把Resource的資源複製到E:\mycoscos-2d\test2\proj.android\assets這個路徑中,以保持同步

程式碼14:
bool FileUtilsAndroid::init()  
{  
    _defaultResRootPath = "assets/";  
    return FileUtils::init();  
}

  回到程式碼12中,FileUtilsWin32::init()最後還是呼叫了FileUtils::init(),那我們來看看FileUtils::init()的定義,如程式碼15。這是很巧妙的機巧!

程式碼15:
bool FileUtils::init()  
{  
    _searchPathArray.push_back(_defaultResRootPath);  
    _searchResolutionsOrderArray.push_back("");  
    return true;  
}  

  在程式碼15中,路徑字串加入了_searchPathArray容器中!

  我們現在回到程式碼6中,this->getPathForFilename(newFilename, *resolutionIt, *searchIt),為什麼加入this?那就要看看程式碼6的函式呼叫者程式碼5,在程式碼5中有FileUtils::getInstance(),它返回的是由FileUtilsWin32轉換而來的,而FileUtils中getPathForFilename為虛擬函式,根據C++多型,所以會呼叫FileUtilsWin32::getPathForFilename()。如程式碼16。
程式碼16: 複製程式碼
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);  
}
複製程式碼

  在程式碼16中,我們看到FileUtilsWin32::getPathForFilename()作用是把路徑轉換為符合平臺的路徑格式。

  到此為止,我們詳細講解了cocos2d-x3.2如何通過FileUtils類來實現把資源放在Resources檔案目錄下達到多平臺的引用。