Wie erstelle ich eine Sicherungsdatei für ein C ++ - Spiel?

33

Ich programmiere gerade mein Finale für einen Videospiel-Programmierkurs und möchte wissen, wie ich eine Speicherdatei für mein Spiel erstelle, damit ein Benutzer spielen und später wiederkommen kann. Jede Idee, wie das gemacht wird, alles, was ich vorher gemacht habe, waren Single-Run-Programme.

Tucker Morgan
quelle
mögliches Duplikat von Wie erstelle
ich
2
Sie können auch SQLite
Nick Shvelidze
1
@Shvelo Während Sie das tun könnten, scheint es, als würde es eine Menge Komplexität hinzufügen, die nicht unbedingt benötigt wird.
Nate

Antworten:

38

Sie müssen die Serialisierung verwenden , um Ihre Variablen im Speicher auf Ihrer Festplatte zu speichern. Es gibt viele Arten der Serialisierung. In .NET ist XML ein verbreitetes Format, obwohl Binär- und JSON-Serialisierer verfügbar sind. Ich bin kein großer C ++ - Programmierer, aber eine schnelle Suche ergab ein Beispiel für die Serialisierung in C ++:

Es gibt Bibliotheken, die Serialisierungsfunktionen anbieten. Einige werden in anderen Antworten erwähnt.

Die Variablen, die Sie interessieren werden, werden wahrscheinlich mit dem Spielstatus zusammenhängen. Beispielsweise möchten Sie wahrscheinlich diese Art von Informationen kennen

  1. Der Spieler hat Level 3 gespielt
  2. Der Spieler befand sich an den Koordinaten X, Y der Welt
  3. Der Spieler hat drei Gegenstände in seinem Rucksack
    1. Waffe
    2. Rüstung
    3. Essen

Es ist dir egal, welche Texturen verwendet werden (es sei denn, dein Player kann sein Aussehen ändern, das ist ein Sonderfall), da sie normalerweise gleich sind. Sie müssen sich darauf konzentrieren, wichtige Gamestate-Daten zu speichern.

Wenn Sie Ihr Spiel starten, starten Sie wie gewohnt für ein "neues" Spiel (dies lädt Ihre Texturen, Modelle usw.), aber zu gegebener Zeit laden Sie die Werte aus Ihrer Speicherdatei zurück in das Spielstatusobjekt und ersetzen das "Standard" -Neu Spielstatus. Dann erlauben Sie dem Spieler, das Spiel fortzusetzen.

Ich habe es hier stark vereinfacht, aber Sie sollten sich einen Überblick verschaffen. Wenn Sie eine spezifischere Frage haben, stellen Sie hier eine neue Frage, und wir können versuchen, Ihnen dabei zu helfen.

Nate
quelle
Ich verstehe, was ich speichern muss, aber was ich wissen möchte, wie es genau ist, speichern Sie es in eine TXT-Datei im Projekt, in diese geänderten Variablen oder auf eine andere Weise
Tucker Morgan
Ja, wenn Ihr Spiel einfach ist, ist möglicherweise eine Textdatei ausreichend. Sie müssen bedenken, dass jeder eine Textdatei bearbeiten und so seine eigenen Spiele speichern kann ...
Nate
Das Speichern von Textdateien ist nicht nur für einfache Spiele gedacht. Paradox verwendete ein strukturiertes Textformat zum Speichern von Dateien für alle Spiele, die mit derselben Engine wie das Flaggschiff Europa Universalis erstellt wurden. Besonders spätes Spiel, diese Dateien können enorm sein.
Dan Neely
1
@DanNeely Ein guter Punkt, kein Grund, warum Sie ein Textformat nicht zum Speichern vieler komplizierter Daten verwenden können. Wenn Ihre Daten jedoch so kompliziert sind, werden die Vorteile eines anderen Formats (binär, xml usw.) deutlicher.
Nate
1
@ NateBross Einverstanden. Die Paradox-Spiele waren sehr mod-freundlich und verwendeten ein ähnliches (identisches?) Format für Szenariodaten. Durch das Speichern der meisten Daten als Text mussten sie nicht in Szenario-Editor-Tools für die öffentliche Nutzung investieren.
Dan Neely
19

Normalerweise ist dies für Ihr Spiel spezifisch. Ich bin sicher, Sie haben bisher in Ihren Klassen gelernt, in Dateien zu schreiben und daraus zu lesen. Die Grundidee ist:

  1. Schreiben Sie beim Beenden des Spiels die Werte, die Sie speichern möchten, in eine Datei.
  2. Überprüfen Sie beim Laden des Spiels, ob eine gespeicherte Datei vorhanden ist. Wenn dies der Fall ist, laden Sie die gelesenen Werte in Ihr Programm. Wenn die Datei nicht vorhanden ist, fahren Sie wie jetzt fort und setzen Sie die Werte auf ihre Start- / Standardwerte.

Was du schreibst, liegt bei dir, es hängt von deinem Spiel ab. Eine Möglichkeit zum Schreiben besteht darin, die gewünschten Variablen in einer bestimmten Reihenfolge als Byte-Stream zu schreiben. Lesen Sie sie dann beim Laden in der gleichen Reihenfolge in Ihr Programm ein.

Zum Beispiel (in schnellem Pseudocode):

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;
    }
}
MichaelHouse
quelle
1
Diese Methode ist nett und klein, obwohl ich empfehlen würde, einige einfache Tags für Datenblöcke einzufügen. Wenn Sie später etwas ändern müssen, das normalerweise in der Mitte der Datei liegt, können Sie dies tun, und die einzige "Konvertierung von alt", die Sie vornehmen müssen, befindet sich in diesem einen Block. Es ist nicht so wichtig für eine einmalige Zuweisung, aber wenn Sie weiterarbeiten, nachdem die Leute angefangen haben, gespeicherte Dateien zu erhalten, ist es ein Alptraum, nur gerade Bytes zu verwenden, wobei Position die einzige Kennung ist.
Lunin
1
Ja, dies erzeugt keine zukunftssicheren Sicherungsdateien. Es funktioniert auch nicht für Daten mit variablen Bytegrößen wie Zeichenfolgen. Letzteres lässt sich leicht beheben, indem zuerst die Größe der zu schreibenden Daten geschrieben und dann beim Laden die richtige Anzahl von Bytes gelesen wird.
MichaelHouse
6

Es gibt wahrscheinlich eine Vielzahl von Möglichkeiten, dies zu tun, aber das Einfachste, was ich persönlich und beruflich immer gefunden und angewendet habe, ist, eine Struktur zu erstellen, die alle Werte enthält, die ich speichern möchte.

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
}

Ich schreibe / verteile dann einfach die Daten in und aus einer Datei unter Verwendung der grundlegenden Datei-E / A-Werte. Die Inventaranzahl ist die Anzahl der Artikelstrukturen, die nach der SaveGameData-Hauptstruktur in der Datei gespeichert werden, sodass ich weiß, wie viele davon nach dem Abrufen dieser Daten gelesen werden müssen. Der Schlüssel hier ist, dass wenn ich etwas Neues speichern möchte, es sei denn, es handelt sich um eine Liste von Elementen oder dergleichen, alles, was ich jemals tun musste, ist, der Struktur irgendwo einen Wert hinzuzufügen. Wenn es sich um eine Liste von Elementen handelt, muss ich einen Lesezugriff hinzufügen, wie ich es bereits für die Elementobjekte impliziert habe, einen Zähler im Hauptkopf und dann die Einträge.

Dies hat den Nachteil, dass verschiedene Versionen einer Sicherungsdatei ohne besondere Behandlung inkompatibel sind (auch wenn es sich nur um Standardwerte für jeden Eintrag in der Hauptstruktur handelt). Insgesamt lässt sich das System jedoch problemlos erweitern, indem lediglich ein neuer Datenwert hinzugefügt und bei Bedarf ein Wert hinzugefügt wird.

Auch hier gibt es einige Möglichkeiten, um dies zu tun, und dies könnte mehr zu C als zu C ++ führen, aber es hat die Arbeit erledigt!

James
quelle
1
Beachten Sie auch, dass dies nicht plattformunabhängig ist, nicht für C ++ - Zeichenfolgen oder für Objekte funktioniert, auf die über Verweise oder Zeiger verwiesen wird, oder für Objekte, die eines der oben genannten Elemente enthalten!
Kylotan,
Warum ist diese Plattform nicht unabhängig? Es funktionierte gut auf dem PC, PS * -Systemen und dem 360 .. fwrite (pToDataBuffer, sizeof (Datentyp), countOfElements, pToFile); funktioniert für alle diese Objekte, vorausgesetzt, Sie können einen Zeiger auf ihre Daten abrufen, und die Größe des Objekts und dann die Anzahl von ihnen, die Sie schreiben möchten .. und lesen passt dazu ..
James
Es ist plattformunabhängig und es gibt keine Garantie dafür, dass auf einer Plattform gespeicherte Dateien auf eine andere geladen werden können. Was für zB das Speichern von Spieldaten eher irrelevant ist. Das Zeiger-auf-Daten-und-Größe-memcpy-Zeug kann natürlich etwas umständlich sein, aber es funktioniert.
links ungefähr am
3
Eigentlich gibt es keine Garantie dafür, dass es für immer für Sie funktioniert - was passiert, wenn Sie eine neue Version veröffentlichen, die mit einem neuen Compiler erstellt wurde, oder sogar neue Kompilierungsoptionen, die das Struktur-Padding ändern? Ich würde die Verwendung von raw-struct fwrite () nur aus diesem Grund dringend empfehlen (ich spreche übrigens aus Erfahrung in diesem Fall).
Flauschige
1
Es geht nicht um '32 Datenbits '. Das Originalplakat fragt einfach "Wie speichere ich meine Variablen?". Wenn Sie die Variable direkt schreiben, gehen plattformübergreifend Informationen verloren. Wenn Sie vor dem Schreiben eine Vorverarbeitung durchführen müssen, haben Sie den wichtigsten Teil der Antwort ausgelassen, d. H. wie man die Daten so verarbeitet, dass sie korrekt gespeichert werden und nur das triviale Bit enthalten, d. h. Aufruf von fwrite, um etwas auf eine Festplatte zu schreiben.
Kylotan
3

Zuerst müssen Sie entscheiden, welche Daten gespeichert werden sollen. Dies kann zum Beispiel die Position des Charakters, seine Punktzahl und die Anzahl der Münzen sein. Natürlich wird Ihr Spiel wahrscheinlich viel komplexer und Sie müssen zusätzliche Daten wie die Levelnummer und die Feindliste speichern.

Als nächstes schreiben Sie Code, um dies in eine Datei zu speichern (Verwendung von Stream). Ein relativ einfaches Format, das Sie verwenden können, lautet wie folgt:

x y score coins

Und so würde die Datei aussehen:

14 96 4200 100

Das würde bedeuten, dass er mit einer Punktzahl von 4200 und 100 Münzen auf Position (14, 96) war.

Sie müssen auch Code schreiben, um diese Datei zu laden (verwenden Sie ifstream).


Das Speichern von Feinden kann durch Einfügen ihrer Position in die Datei erfolgen. Wir können dieses Format verwenden:

number_of_enemies x1 y1 x2 y2 ...

Zuerst number_of_enemieswird gelesen und dann wird jede Position mit einer einfachen Schleife gelesen.

Pubby
quelle
1

Ein Zusatz / Vorschlag wäre, Ihrer Serialisierung eine Verschlüsselungsstufe hinzuzufügen, damit Benutzer ihre Werte nicht in "99999999999999999" als Text bearbeiten können. Ein guter Grund, dies zu tun, wäre, beispielsweise Ganzzahlüberläufe zu verhindern.

Styler
quelle
0

Ich denke, Ihre beste Wahl ist boost :: serialization, weil es einfach ist, sich in Ihrer Hierarchie zu verbreiten. Sie müssen nur die Speicher- / Ladefunktion des obersten Objekts aufrufen, und alle Ihre Klassen können problemlos erstellt werden.

Siehe: http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html

Killrazor
quelle
0

Der Vollständigkeit halber möchte ich eine c ++ - Serialisierungsbibliothek erwähnen, die ich persönlich benutze und die noch nicht erwähnt wurde: Getreide .
Es ist einfach zu bedienen und hat eine schöne, saubere Syntax für die Serialisierung. Es bietet auch mehrere Arten von Formaten, die Sie speichern können (XML, Json, Binary (einschließlich einer portablen Version mit Respekt vor Endianess)). Es unterstützt die Vererbung und ist nur für den Header bestimmt.

LukeG
quelle
0

Ihr Spiel wird Datenstrukturen gefährden (hoffentlich?), Die Sie in Bytes umwandeln müssen (serialisieren), damit Sie sie speichern können. In Zukunft können Sie diese Bytes wieder laden und in Ihre ursprüngliche Struktur zurückverwandeln (Deserialisierung). In C ++ ist es nicht so schwierig, da die Reflektion sehr begrenzt ist. Dennoch können Ihnen einige Bibliotheken hier weiterhelfen. Ich habe einen Artikel darüber geschrieben: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ Grundsätzlich empfehle ich Ihnen einen Blick darauf zu werfen Getreidebibliothek, wenn Sie C ++ 11-Compiler als Ziel haben. Es müssen keine Zwischendateien wie mit protobuf erstellt werden, sodass Sie dort Zeit sparen, wenn Sie schnelle Ergebnisse erzielen möchten. Sie können auch zwischen Binär- und JSON-Dateien wählen, um hier das Debuggen zu vereinfachen.

Wenn die Sicherheit ein Problem darstellt, möchten Sie möglicherweise die gespeicherten Daten verschlüsseln / entschlüsseln, insbesondere wenn Sie für Menschen lesbare Formate wie JSON verwenden. Algorithmen wie AES sind hier hilfreich.

Rubén Torres Bonet
quelle
-5

Sie müssen fstreamfür Ein- / Ausgabedateien verwenden. Die Syntax ist einfach EX:

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

Oder

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

Andere Aktionen sind für Ihre Datei möglich: Anhängen , Binär , Abschneiden usw. Sie würden die gleiche Syntax wie oben verwenden, stattdessen setzen wir std :: ios: :( Flags), zum Beispiel:

  • ios::out für den Ausgabebetrieb
  • ios::in für den Eingabebetrieb
  • ios::binary für binären (Rohbyte-) E / A-Betrieb statt zeichenbasiert
  • ios::app um mit dem Schreiben am Ende der Datei zu beginnen
  • ios::trunc Wenn die Datei bereits vorhanden ist, ersetzen Sie den alten Inhalt und ersetzen Sie ihn durch neuen
  • ios::ate - Positionieren Sie den Dateizeiger "am Ende" für die Ein- / Ausgabe

Ex:

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

Hier ist ein vollständigeres, aber einfaches Beispiel.

#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;
}
Francisco Forcier
quelle
4
-1 Dies ist eine sehr schlechte Antwort. Sie sollten den Code korrekt formatieren und anzeigen und erklären, was Sie tun. Niemand möchte einen Teil des Codes entschlüsseln.
Vaillancourt
Vielen Dank an Katu für den Kommentar, dass Sie Recht haben. Ich sollte meinen Code besser erklären. Können Sie mir sagen, wie ich meine Quelle von der Website formatiere, weil ich neu in dieser Art von Dingen bin
Francisco Forcier
Von dieser Site oder für diese Site? Hilfe zum Formatieren der Beiträge finden Sie auf der Seite mit der Formatierungshilfe . Es gibt ein Ausrufezeichen neben der Überschrift des Textbereichs, den Sie zum Posten verwenden, um Ihnen ebenfalls zu helfen.
Vaillancourt
Versuchen Sie zu dokumentieren, was gefragt wird. Sie müssen nicht alles kommentieren. Und indem ich nicht erklärte, was Sie taten, meinte ich in meinem Kommentar, dass Sie im Allgemeinen die Strategie, die Sie vorschlagen, mit mindestens einem kurzen Absatz einführen. (zB "Eine der Techniken ist die Verwendung eines Binärdateiformats mit einem Stream-Operator. Sie müssen darauf achten, dass Sie in derselben Reihenfolge lesen und schreiben, bla bla lba").
Vaillancourt
2
Und wenn Sie gotos verwenden, werden Sie auf dem öffentlichen Platz gelyncht. Verwenden Sie nicht gotos.
Vaillancourt