2013-07-14 71 views
1

我花了無數個小時搜索有關這樣的話題的信息。我正在用C++編寫自己的自定義遊戲引擎,以便使用SDL。我正在嘗試創建一個自定義二進制文件來管理我的遊戲資源。到目前爲止,我還沒有能夠讓媒體在存儲我放置在文件中的每個「類型」對象時發揮出色。所以我放棄了使用矢量的想法並進入了數組。我在下面的例子中都使用了矢量或數組。所以,首先我爲這個文件創建一個頭文件。下面是結構:C++自定義二進制資源文件

struct Header 
{ 
    const char* name; // Name of Header file 
    float version;  // Resource version number 
    int numberOfObjects; 
    int headerSize;  // The size of the header 

}; 

然後創建所述報頭之後,我有另一結構,其限定一個目的是如何存儲在存儲器中。那就是:

struct ObjectData{ 

    int id; 
    int size; 
    const char* name; 
    // std::vector<char> data; // Does not work very well 
    // unsigned char* data; // Also did not 

    // Also does not work, because I do not know the size yet until I have the data. 
    // char data[]   

}; 

這種結構的主要問題是,載體不玩好,無符號的字符指針不停地給我的問題,和char數據的數組(十六進制存儲)是行不通的,因爲我的編譯器不喜歡變量數組。

最終的結構是我的資源文件結構。

struct ResourceFile 
{ 
    Header header; 

    int objectCount; 
    // Again, vectors giving me issues because of how they are constructed internally 
    // std::vector<ObjectData> objectList; 
    // Below does not work because, again, no variable data types; 
    // ObjectData objects[header.numberOfObjects] 


}; 

我的目標是能夠寫出一個單一的結構到二進制文件。像這樣:

Header header; 

    header.name = "Resources.bin"; 
    header.version = 1.0f; 
    header.headerSize = sizeof(header); 

    //vector<char> Object1 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj"); 
    //vector<char> Object2 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj"); 

    ObjectData cube; 
    cube.id = 0; 
    cube.name = "Evil Cubie"; 
    cube.data = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj"); 
    cube.size = sizeof(cube.id) + sizeof(cube.name) + cube.data.size(); 

    ofstream resourceFile("D:\\TEST_FOLDER\\Resources.bin", ios::out|ios::app|ios::binary); 

    resourceFile << header.name << header.version << header.headerSize;; 
    resourceFile << cube.id << cube.name << cube.size; 
    for each (char ch in cube.data) 
    { 
     resourceFile << ch; 
    } 


    resourceFile.close(); 

    /* 
    ObjectData cube2; 
    cube.id = 1; 
    cube.name = "Ugle Cubie"; 
    for each (char ch in Object1) 
    { 
     cube.object.push_back(ch); 
    } 
    */ 


    //resourceFile.data.push_back(cube); 
    //resourceFile.data.push_back(cube2); 

    //resourceFile.header.numberOfObjects = resourceFile.data.size(); 


    //FILE* dat = fopen(filename, "wb"); 
    //fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file 
    //fclose(dat); 

正如您在上面注意到的,我嘗試了兩種不同的方式。我嘗試的第一種方法是使用很好的舊fwrite。第二種方法甚至沒有用二進制編寫它,即使我通過由ofstream接受的標誌告訴計算機這樣做。

我的目標是讓代碼像這樣流利工作:

ResourceFile resourceFile; 

resourceFile.header.name = "Resources.bin"; 
resourceFile.header.version = 1; 
resrouceFile.header.numberOfObjects = 2; 
resourceFile.header.headerSize = sizeof(resourceFile.header); 

ObjectData cube; 
ObjectData cube2; 


resourceFile.data.push_back(cube); 
resourceFile.data.push_back(cube2); 

resourceFile.header.numberOfObjects = resourceFile.data.size(); 


FILE* dat = fopen(filename, "wb"); 
fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file 
fclose(dat); 

仍然沒有雪茄。任何人都有任何指針(沒有雙關語意思)或資源管理器的適當例子?

+0

這是一個適當的觀察我的朋友!我沒有收到任何錯誤。有什麼建議麼? – Ryan

回答

1

這是我擅長的事情之一,所以在這裏你去。這裏有一整套的編程,但我遵循的基本規則是:

1)對於具有「常量」佈局的東西使用FIXED-LENGTH結構。
這些是文件的標誌位,指示子記錄數的字節等等。儘可能多地將文件內容放入這些結構中 - 它們非常有效,特別是在與良好的I/O系統。

你此使用預處理器宏「的#pragma包(1)」以對準一個結構到字節邊界:

#ifdef WINDOWS 
#pragma pack(push) 
#endif 
#pragma pack(1) 

struct FixedSizeHeader { 
    uint32 FLAG_BYTES[1]; // All Members are pointers for a reason 
    char NAME[20]; 
}; 

#ifdef WINDOWS 
#pragma pack(pop) 
#endif 
#ifdef LINUX 
#pragma pack() 
#endif 

2)中包含「名稱創建一個基類,純接口序列化」。他是用於將整個文件對象存入原始存儲器和從存儲器轉出的高級API。

class Serializable { // Yes, the name comes from Java. The idea, however, predates it 
public: 
    // Choose your buffer type- char[], std::string, custom 
    virtual bool WriteToBinary(char* buffer) const = 0; 
}; 

注意:要支持靜態「加載」,您將需要所有的「可序列化」以具有額外的靜態功能。有幾種(非常不同的)方法可以支持這種方法,因爲C++沒有「虛擬靜態」,所以這些語言都不會執行。

3)創建用於管理每個文件類型的聚合類。它們應該與文件類型具有相同的名稱。根據文件結構的不同,每個可能包含更多的「聚合器」類,然後再回到固定結構。

下面是一個例子:

class GameResourceFile : public Serializable 
{ 
private: 
    // Operator= and the copy ctor should point to the same data for files, 
    // since that is what you get with FILE* 
protected: 
    // Actual member variables- allows specialized (derived) file types direct access 
    FixedSizeHeader* hdr;  // You don't have to use pointers here 
    ContentManager* innards; // Another aggregator- implements "Serializable" 

    GameResourceFile(FixedSizeHeader* hdr, ContentManager* innards) 
     : hdr(hdr), innards(innards) {} 
    virtual ~GameResourceFile() { delete hdr; delete innards; } 
public: 
    virtual bool WriteToBinary(char* outBuffer) const 
    { 
     // For fixed portions, use this 
     memcpy(outBuffer, hdr, sizeof(FixedSizeHeader)); // This is why we 'pack' 
     outBuffer += sizeof(FixedSizeHeader);   // Improve safety... 
     return innards->WriteToBinary(outBuffer); 
    } 

    // C++ doesn't enforce this, but you can via convention 
    static GameResourceFile* Load(const char* filename) 
    { 
     // Load file into a buffer- You'll want your own code here 
     // Now that's done, we have a buffer 
     char* srcContents; 
     FixedSizeHeader* hdr = new FixedSizeHeader(); 
     memcpy(hdr, srcContents, sizeof(FixedSizeHeader)); 
     srcContents += sizeof(FixedSizeHeader); 

     ContentManager* innards = ContentManager::Load(srcContents); // NOT the file 
     if(!innards) { 
      return 0; 
     } 
     return new GameResourceFile(hdr, innards); 
    } 
}; 

注意如何工程─每片負責序列化本身到緩衝區,直到我們得到了我們可以通過memcpy的增加(以「原始」結構)(你可以使所有組件的'可序列化'類)。如果有任何部分未能添加,則調用返回「false」,您可以中止。

我強烈建議使用像「引用對象」的模式來避免內存管理問題。但是,即使你沒有,你現在爲用戶提供一個不錯的,一站式購物方法從文件加載的數據對象:

GameResourceFile* resource = GameResourceFile::Load("myfile.game"); 
if(!resource) { // Houston, we have a problem 
    return -1; 
} 

的最好的事情又是增加所有低級別的操作和檢索的API那種數據給「GameResourceFile」。然後任何低級狀態機協調提交到磁盤&這樣的更改全部本地化爲1個對象。

+0

任何提示或鏈接在C做這個? – Dan