1

私は、このようなトピックに関する情報を探すのに数え切れないほどの時間を費やしてきました。C++ で SDL を使用して、楽しみのために独自のカスタム ゲーム エンジンを作成しています。ゲーム内リソースを管理するカスタム バイナリ ファイルを作成しようとしています。これまでのところ、ファイルに配置するオブジェクトの各「タイプ」を保存する際に、ベクトルを適切に再生することができませんでした。そこで、ベクトルを使用するという考えを捨てて、配列に行きました。ベクトルまたは配列の両方を使用する例を以下に示します。そのため、最初にファイルのヘッダーを作成します。構造体は次のとおりです。

    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[]         

};

この構造体の主な問題は、ベクターがうまく動作しないこと、unsigned char ポインターが問題を引き起こし続けること、コンパイラーが変数配列を好まないために char データの配列 (16 進数ストレージ用) が機能しないことです。

最後の構造体は、私のリソース ファイル構造です。

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);

上記で気づいたように、私は2つの異なる方法を試しました. 私が最初に試した方法は、古き良き fwrite を使用することでした。2 番目の方法は、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);

まだ葉巻はありません。ポインター (しゃれは意図されていません) またはリソースマネージャーの適切な例を持っている人はいますか?

4

1 に答える 1

1

これは私が専門とすることの 1 つです。これにはプログラミングの学校全体がありますが、私が従う基本的なルールは次のとおりです。

1)「一定の」レイアウトを持つものには FIXED-LENGTH 構造を使用します。
これらは、ファイルのフラグビット、サブレコードの数を示すバイトなどです。ファイルの内容をできる限りこれらの構造に入れます。特に優れた I/O システムと組み合わせると非常に効率的です。 .

これを行うには、プリプロセッサ マクロ「#pragma pack(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) 「Serializable」のような名前の基本クラスの純粋なインターフェースを作成します。これは、ファイル オブジェクト全体を raw メモリに出し入れするための高レベル 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;
};

注: 静的な "Load" をサポートするには、すべての "Serializable" に静的関数を追加する必要があります。それをサポートするにはいくつかの (非常に異なる) 方法がありますが、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 つのオブジェクトにローカライズされます。

于 2013-07-30T16:11:26.803 に答える