C ++ゲームの保存ファイルを作成するにはどうすればよいですか?


33

ビデオゲームプログラミングコースの最終版をコーディングしています。ゲームの保存ファイルを作成して、ユーザーがプレイして後で戻ってくる方法を知りたいです。これがどのように行われるかについての考えは、私が以前に行ったことはすべて、単一実行プログラムです。



2
SQLite
Nick Shvelidze

1
@Shveloそれはできますが、必ずしも必要ではない多くの複雑さを追加するようです。
ネイト

回答:


38

メモリ内の変数をハードドライブに保存するには、シリアル化を使用する必要があります。シリアル化には多くの種類があります。.NETXMLでは一般的な形式ですが、バイナリおよびJSONシリアライザーを使用できます。私はあまりC ++プログラマーではありませんが、クイック検索でC ++のシリアル化の例を見つけました。

シリアル化機能を提供するライブラリがあります。いくつかは他の回答で言及されています。

興味のある変数は、おそらくゲームの状態に関連するでしょう。たとえば、おそらくこのタイプの情報を知りたいと思うでしょう

  1. プレイヤーはレベル3でプレイしていました
  2. プレイヤーはX、Yワールド座標にいました
  3. プレーヤーのバックパックには3つのアイテムがあります
    1. 武器
    2. フード

使用されているテクスチャは気にしません(プレーヤーが外観を変更できない限り、それは特別な場合です)。これらは通常同じであるためです。重要なゲーム状態データの保存に集中する必要があります。

ゲームを開始すると、「新しい」ゲームの通常どおりに起動します(これにより、テクスチャ、モデルなどが読み込まれます)が、適切なタイミングで、保存ファイルからゲーム状態オブジェクトに値を読み込み、「デフォルト」の新しいゲームの状態。次に、プレーヤーが再生を再開できるようにします。

ここでは大幅に簡略化しましたが、一般的な考え方を理解しておく必要があります。より具体的な質問がある場合は、ここで新しい質問をしてください。私たちはそれを手助けすることができます。


私は私が救うために必要なものを理解し、しかし、私は正確な方法が何であるかを知っていただきたいと思います、あなたは、プロジェクト内の.txtファイル、それらの修正変数、またはいくつかの他の方法にそれを保存しない
タッカーモルガン

はい、ゲームがシンプルな場合、テキストファイルで十分です。あなたは...誰もがテキストファイルを編集し、したがって、ゲームのセーブ自分自身を作ることができることを心に留めておく必要がある
ネイト・

テキストファイルの保存は、単純なゲームのためだけではありません。Paradoxは、旗艦のEuropa Universalisエンジンと同じエンジンを使用して作成したすべてのゲームのファイルを保存するために、構造化テキスト形式を使用しました。特に後半のゲームでは、これらのファイルは膨大になる可能性があります。
ダン・ニーリー

1
@DanNeely公正な点、多くの複雑なデータを保存するためにテキスト形式を使用できない理由はありませんが、一般的に言えば、データがそれほど複雑な場合、別の形式(バイナリ、xmlなど)の利点がより顕著になります。
ネイト

1
@NateBross同意しました。Paradoxゲームは非常にmodフレンドリーで、シナリオデータに同様の(同一の?)形式を使用しました。データの大部分をテキストとして保存することで、一般ユーザーが使用するシナリオエディターツールに投資する必要がなくなりました。
ダン・ニーリー

19

通常、これはゲームに固有です。これまでに、クラス内のファイルの書き込みと読み取りについて学習したことは間違いありません。基本的な考え方は次のとおりです。

  1. ゲームを終了するときに、保存する値をファイルに書き込みます。
  2. ゲームをロードするときに、保存ファイルが存在するかどうかを確認します。存在する場合は、読み取り値をプログラムにロードします。ファイルが存在しない場合は、今と同じように続行し、値を開始/デフォルト値に設定します。

あなたが書くことはあなた次第です、それはあなたのゲームに依存します。書き方の1つは、必要な変数を特定の順序でバイトストリームとして書き出すことです。次に、ロードするときに、同じ順序でプログラムに読み込みます。

例(クイック疑似コード):

SaveGame(FileInput file) {
    file.writeInt(playerLevel);
    file.writeInt(playerHealth);
    file.writeInt(gameProgress);
}

LoadGame(FileInput file) {
    if(file.exists()) {
        playerLevel= file.readInt();
        playerHealth = file.readInt();
        gameProgress = file.readInt();
    } else {
        playerLevel = 1;
        playerHealth = 100;
        gameProgress = 0;
    }
}

1
この方法は素晴らしく、小さいですが、データの塊にいくつかの簡単なタグを入れることをお勧めします。そうすることで、後でファイルの途中にある何かを変更する必要がある場合、それを行うことができます。あなたがしなければならない唯一の「古いからの変換」は、その1ブロック内です。1回限りの割り当てではそれほど重要ではありませんが、ユーザーがファイルの保存を開始した後に作業を続けると、位置が唯一の識別子であるストレートバイトを使用するだけで少し悪夢になります。
ルニン

1
うん、これは将来の保存ファイルを生成しません。また、文字列のような可変バイトサイズのデータ​​に対しても機能しません。後者は、書き込もうとしているデータのサイズを最初に書き込み、それを使用して正しいバイト数を読み込むために読み込むことで、簡単に修正できます。
マイケルハウス

6

これを行う方法はおそらく多数ありますが、私が常に見つけて個人的にも専門的にも使用している最も簡単な方法は、保存したいすべての値を含む構造を作成することです。

struct SaveGameData
{
    int              characterLevel; // Any straight up values from the player
    int              inventoryCount; // Number of items the player has on them or stored or what not
    int[STAT_COUNT]  statistics;     // This is usually a constant size (I am tracking X number of stats)
    // etc
}

struct Item
{
    int itemTypeId;
    int Durability; // also used as a 'uses' count for potions and the like
    int strength;   // damage of a weapon, protection of armor, effectiveness of a potion
    // etc
}

次に、基本的なファイルIO値を使用して、ファイルとの間でデータを読み書きします。inventoryCountは、ファイル内のメインのSaveGameData構造体の後に保存されるItem構造体の数です。そのため、そのデータを取得した後に読み取る構造体の数がわかります。ここで重要なのは、アイテムのリストなどを除き、何か新しいものを保存したい場合、どこかに構造体に値を追加するだけです。アイテムのリストの場合は、アイテムオブジェクト、メインヘッダーのカウンター、およびエントリに対して既に暗示されているように、読み取りパスを追加する必要があります。

これには、保存ファイルのさまざまなバージョンが特別な処理なしで互いに互換性がないという欠点があります(メイン構造の各エントリの単なるデフォルト値であっても)。ただし、これにより、新しいデータ値を追加し、必要なときに値を入力するだけで、システムを簡単に拡張できます。

繰り返しになりますが、これを行うにはかなり多くの方法があり、これはC ++よりもCの方につながる可能性がありますが、仕事は完了しました!


1
また、これはプラットフォームに依存しないため、C ++文字列、参照またはポインターを介して参照されるオブジェクト、または上記のいずれかを含むオブジェクトでは機能しないことに注意してください!
キロタン

このプラットフォームが独立していないのはなぜですか?PC、PS *システム、360で正常に機能しました。fwrite(pToDataBuffer、sizeof(datatype)、countOfElements、pToFile); これらのオブジェクトのすべてのあなたはそれらのデータへのポインタ、およびオブジェクトのサイズと、あなたが書きたいそれらの数を取得。..して読むことができると仮定すると一致...そのための作品
ジェームズ

これは、ある一つのプラットフォーム上で保存されたファイルは、別の1にロードすることができることを単に保証はありません、プラットフォームに依存しません。これは、ゲームデータの保存などにはあまり関係ありません。data-and-size-memcpyへのポインタは明らかに少し厄介な場合がありますが、機能します。
leftaroundabout

3
実際、永久に動作し続けるという保証はありません。新しいコンパイラで構築された新しいバージョンをリリースしたり、構造体のパディングを変更する新しいコンパイルオプションをリリースするとどうなりますか?この理由だけでraw-struct fwrite()の使用を強くお勧めします(ちなみに、これに関する経験から話しています)。
ふわふわ

1
「32ビットのデータ」ではありません。元のポスターは、単に「変数を保存する方法」を尋ねています。変数を直接書き込むと、プラットフォーム間で情報が失われます。fwriteの前に前処理が必要な場合は、答えの最も重要な部分を省きました。データを正しく保存し、些細なビットのみを含めるようにデータを処理する方法。fwriteを呼び出してディスクに何かを書き込みます。
キロタン

3

最初に、保存する必要があるデータを決定する必要があります。たとえば、これはキャラクターの位置、彼のスコア、コインの数である可能性があります。もちろん、ゲームははるかに複雑になる可能性が高いため、レベル番号や敵リストなどの追加データを保存する必要があります。

次に、これをファイルに保存するコードを記述します(ofstreamを使用)。使用できる比較的単純な形式は次のとおりです。

x y score coins

そのため、ファイルは次のようになります。

14 96 4200 100

つまり、彼は4200と100コインのスコアでポジション(14、96)にいたということです。

また、このファイルをロードするコードを記述する必要があります(ifstreamを使用)。


敵の保存は、ファイルに自分の位置を含めることで実行できます。次の形式を使用できます。

number_of_enemies x1 y1 x2 y2 ...

最初にnumber_of_enemiesが読み取られ、次に単純なループで各位置が読み取られます。


1

ユーザーが値を「9999999999999999999」にテキスト編集できないように、1つの追加/提案では、シリアル化に暗号化レベルを追加します。これを行う正当な理由の1つは、整数のオーバーフローを防ぐことです(たとえば)。



0

完全を期すために、私は個人的に使用し、まだ言及されていなかったc ++シリアル化ライブラリーcerealについて言及したいと思います。
使いやすく、シリアライズのためのすっきりしたきれいな構文があります。また、保存できる複数の種類の形式(XML、Json、Binary(エンディアネスを考慮したポータブル版を含む))も提供します。継承をサポートし、ヘッダーのみです。


0

ゲームは、データ構造を危険にさらし(うまくいけば)、バイトに変換(シリアル化)してデータを保存する必要があります。将来、それらのバイトをロードし直して、元の構造に戻すことができます(逆シリアル化)。C ++では、反射が非常に制限されているため、それほど複雑ではありません。それでも、いくつかのライブラリはここであなたを助けることができます。私はそれについて記事を書きました:https : //rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ 基本的に、私はあなたが見てみることをお勧めしますC ++ 11コンパイラを対象とする場合は、シリアルライブラリ。protobufのように中間ファイルを作成する必要がないため、迅速な結果が必要な場合はそこに時間を節約できます。バイナリとJSONを選択することもできるため、ここでのデバッグに非常に役立ちます。

それに加えて、セキュリティが懸念される場合、特にJSONのような人間が読める形式を使用している場合は、保存しているデータを暗号化/復号化することができます。ここでは、AESのようなアルゴリズムが役立ちます。


-5

fstream入力/出力ファイルに使用する必要があります。構文は簡単なEXです。

#include <fstream>
// ...
std::ofstream flux ; // to open a file in ouput mode
flux.open("myfile.whatever") ; 

または

#include <fstream>
// ...
std::ifstream flux ; // open a file in input mode
flux.open("myfile.whatever") ;

ファイルでは、appendbinarytruncなど、他のアクションも可能です。代わりに、std :: ios::( flags)を置く代わりに上記と同じ構文を使用します。

  • ios::out 出力操作用
  • ios::in 入力操作用
  • ios::binary 文字ベースではなく、バイナリ(生バイト)IO操作用
  • ios::app ファイルの終わりから書き込みを開始するため
  • ios::trunc ファイルが既に存在する場合は、古いコンテンツを削除し、新しいものに置き換えます
  • ios::ate -入出力用にファイルポインタを「最後に」配置する

例:

#include <fstream>
// ...
std::ifstream flux ;
flux.open("myfile.whatever" , ios::binary) ;

以下は、より完全ですがシンプルな例です。

#include <iostream>
#include <fstream>

using namespace std ;

int input ;
int New_Apple ;
int Apple_Instock ;
int Eat_Apple ;
int Apple ;

int  main()
{
  bool shouldQuit = false;
  New_Apple = 0 ;
  Apple_Instock = 0 ;
  Eat_Apple = 0 ;

  while( !shouldQuit )
  {
    cout << "------------------------------------- /n";
    cout << "1) add some apple " << endl ;
    cout << "2) check apple in stock " << endl ;
    cout << "3) eat some apple " << endl ;
    cout << "4) quit " << endl ;
    cout << "------------------------------------- /n";
    cin >> input ;

    switch (input)
    {
      case 1 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " how much apple do you want to add /n";
        cout << "------------------------------------ /n";      
        cin >> New_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ; 

        Apple = New_Apple + Apple_Instock ;

        ofstream apple_adder ;
        apple_adder.open("apple.apl") ;
        apple_adder << Apple ;
        apple_adder.close() ;

        cout << "------------------------------------ /n";
        cout << New_Apple << " Apple has been added ! /n";
        cout << "------------------------------------ /n";
        break;
      }

      case 2 :  
      {
        system("cls") ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        cout << "------------------------------------ /n";
        cout << " there is " << Apple_Instock ;
        cout << "apple in stock /n" ;
        cout << "------------------------------------ /n";
        break;
      }

      case 3 :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << "How many apple do you want to eat /n" ;
        cout << "------------------------------------ /n";
        cin >> Eat_Apple ;

        ifstream apple_checker ;
        apple_checker.open("apple.apl") ;
        apple_checker >> Apple_Instock ;
        apple_checker.close() ;

        Apple = Apple_Instock - Eat_Apple ; 

        ofstream apple_eater ;
        apple_eater.open("apple.apl") ;
        apple_eater << Apple ;
        apple_eater.close() ;

        cout << "----------------------------------- /n";
        cout << Eat_Apple ;
        cout << " Apple has been eated! /n";
        cout << "----------------------------------- /n";
        cout << Apple << " Apple left in stock /n";
        cout << "----------------------------------- /n";
        break;
      }

      case 4 :
      {
        shouldQuit = true;
        break;
      }

      default :
      {
        system("cls") ;

        cout << "------------------------------------ /n";
        cout << " invalide choice ! /n";
        cout << "------------------------------------ /n"; 
        break;
      }
    }
  }
  return 0;
}

4
-1これは非常に悪い答えです。コードを正しくフォーマットして表示し、何をしているのかを説明する必要があります。誰もコードの塊を解読したくありません。
ベイランクール

あなたが正しいコメントをありがとう、katu私は私のコードをもっとよく説明する必要がありますウェブサイトから私のソースをどのようにフォーマットするか教えてもらえますか?私はこの種のものに新しいです
フランシスコフォーシエ

このサイトから、またはこのサイトのために?投稿の書式設定のヘルプについては、書式設定のヘルプページをご覧ください。投稿に使用するテキスト領域のヘッダーの横にも感嘆符があります。
ベイランクール

求められていることを文書化してください。すべてをコメントする必要はありません。そして、私のコメントで、あなたが何をしていたかを説明しないことで、一般的に、少なくとも短い段落で提案する戦略を紹介することを意味しました。(たとえば、「テクニックの1つは、ストリーム演算子でバイナリファイル形式を使用することです。同じ順序で読み取りと書き込みを行う必要があります。blabla lba」)。
ベイランクール

2
gotos を使用すると、公共の場所でリンクされます。gotosを使用しないでください。
ベイランクール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.