Come faccio a creare un file di salvataggio per un gioco C ++?


33

Sto programmando la mia finale per un corso di programmazione di videogiochi e voglio sapere come creare un file di salvataggio per il mio gioco, in modo che un utente possa giocare e poi tornare più tardi. Qualunque idea di come sia fatto, ogni cosa che ho fatto in precedenza è stata programmi a esecuzione singola.



2
Puoi anche usare SQLite
Nick Shvelidze il

1
@Shvelo Anche se potresti farlo, sembra che aggiungerebbe molta complessità che non è necessariamente necessaria.
Nate,

Risposte:


38

È necessario utilizzare la serializzazione per salvare le variabili in memoria sul disco rigido. Esistono molti tipi di serializzazione, in .NET XML è un formato comune, sebbene siano disponibili serializzatori binari e JSON. Non sono un programmatore C ++, ma una rapida ricerca ha mostrato un esempio di serializzazione in C ++:

Esistono librerie che offrono funzionalità di serializzazione. Alcuni sono menzionati in altre risposte.

Le variabili che ti interesseranno probabilmente saranno correlate allo stato del gioco. Ad esempio, probabilmente vorrai conoscere questo tipo di informazioni

  1. Il giocatore stava giocando al livello 3
  2. Il giocatore era alle coordinate X, Y del mondo
  3. Il giocatore ha tre oggetti nel suo zaino
    1. Arma
    2. Armatura
    3. Cibo

Non ti interessa davvero quali texture vengono utilizzate (a meno che il tuo giocatore non possa cambiare il suo aspetto, questo è un caso speciale), perché di solito sono le stesse. Devi concentrarti sul salvataggio di importanti dati di gamestate.

Quando si avvia il gioco, si avvia normalmente per un "nuovo" gioco (questo carica trame, modelli, ecc.) Ma al momento opportuno si caricano nuovamente i valori dal file di salvataggio nell'oggetto stato del gioco sostituendo il "predefinito" nuovo stato del gioco. Quindi permetti al giocatore di riprendere a giocare.

L'ho semplificato molto qui, ma dovresti avere l'idea generale. Se hai una domanda più specifica, fai una nuova domanda qui e possiamo provare ad aiutarti.


Capisco cosa devo salvare, ma cosa mi piacerebbe sapere qual è il modo esatto, lo salvi in ​​un file .txt nel progetto, quelle variabili modificate o in qualche altro modo
Tucker Morgan

Sì, se il tuo gioco è semplice, un file di testo potrebbe essere sufficiente; devi tenere a mente che chiunque può modificare un file di testo e quindi salvare i propri giochi ...
Nate

I salvataggi di file di testo non sono solo per giochi semplici. Paradox ha utilizzato un formato di testo strutturato per salvare i file per tutti i giochi creati utilizzando lo stesso motore del motore di punta Europa Universalis. Soprattutto in ritardo di gioco, questi file potrebbero essere enormi.
Dan Neely,

1
@DanNeely Un punto giusto, nessuna ragione per cui non è possibile utilizzare un formato di testo per archiviare molti dati complicati, ma in generale, quando i dati sono così complicati, i vantaggi di un altro formato (binario, xml, ecc.) Diventano più pronunciati.
Nate

1
@NateBross Concordato. I giochi di Paradox erano molto adatti alle mod e utilizzavano un formato simile (identico?) Per i dati degli scenari. Memorizzare la maggior parte dei loro dati come testo significava che non avevano bisogno di investire in strumenti di editor di scenari per uso pubblico.
Dan Neely,

19

In genere questo è specifico per il tuo gioco. Sono sicuro che finora hai imparato a scrivere e leggere dai file delle tue lezioni. L'idea di base è:

  1. All'uscita dal gioco, scrivi i valori che vuoi salvare in un file.
  2. Quando si carica il gioco, verificare se esiste un file di salvataggio, in caso affermativo, caricare i valori letti nel programma. Se il file non esiste, continua come fai ora e imposta i valori sui valori iniziali / predefiniti.

What you write is up to you, it depends on your game. One way of writing is to write out the variables you want in a specific order as a byte stream. Then when loading, read them in to your program in the same order.

For example (in quick pseudo code):

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
This method is nice and small, though I'd recommend putting in some simple tags for chunks of data. That way if later on you need to change something that's normally in the middle of the file, you can do so and the only "conversion from old" you have to do is within that one block. It's not as important for a one off assignment, but if you continue work after people start getting save files it's a bit of a nightmare just using straight bytes with position being the only identifier.
Lunin

1
Yep, this does not generate future-proof save files. It also doesn't work for data that has variable byte sizes like strings. The latter is easy to fix by first writing the size of the data that's about to be written, then using that when loading to read the correct number of bytes.
MichaelHouse

6

There are probably a large number of ways to do this, but the simplest that I always found and have used both personally and professionally is to make a structure that contains all of the values I want saved.

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
}

I then just fwrite/fread the data to and from a file using the basic File IO values. The inventoryCount is the number of Item structures that are saved after the main SaveGameData structure in the file so I know how many of those to read after fetching that data. The key here is that when I want to save something new, unless its a list of items or the like, all I have ever have to do is add a value to the structure some where. If its a list of items then I will have to add a read pass like I have already implied for the Item objects, a counter in the main header and then the entries.

This does have the downside of making different versions of a save file incompatible with each other with out special handling (even if it is just default values for each entry in the main structure). But overall this makes the system easy to extend just by adding in a new data value and putting a value into it when needed.

Again, quite a few ways to do this and this might lead more towards C than C++, but it has gotten the job done!


1
It's also worth noting that this is not platform-independent, won't work for C++ strings, or for objects referred to via references or pointers, or any objects containing any of the above!
Kylotan

Why isnt this platform independent? It worked fine on the PC, PS* systems and the 360.. fwrite(pToDataBuffer, sizeof(datatype), countOfElements, pToFile); works for all of those objects assuming you can get a pointer to their data, and the size of the object and then the number of them you want to write.. and read matches that..
James

It is platform-independent, there's just no guarantee that files saved on one platform can be loaded on another one. Which is rather irrelevant for e.g. game data saving. The pointer-to-data-and-size-memcpy stuff can obviously be a bit awkward, but it works.
leftaroundabout

3
Actually there's no guarantee that it'll keep working for you forever - what happens if you release a new version that's built with a new compiler or even new compilation options that changes the struct padding? I would strongly, strongly discommend the use of raw-struct fwrite() for this reason alone (I am speaking from experience on this one, incidentally).
fluffy

1
It's not about '32 bits of data'. The original poster is simply asking "how do I save my variables". If you fwrite the variable directly, then you lose information across platforms. If you have to preprocess before the fwrite, then you've left out the most important part of the answer, ie. how to process the data so that it's saved correctly, and only included the trivial bit, ie. calling fwrite to put something on a disk.
Kylotan

3

First you need to decide what data needs to be saved. For instance, this could be the location of the character, his score, and the number of coins. Of course, your game will likely be much more complex, and so you will need to save additional data such as the level number and enemy list.

Next, write code to save this to a file (use ofstream). A relatively simple format you can use is as follows:

x y score coins

And so the file would look like:

14 96 4200 100

Which would mean he was at position (14, 96) with a score of 4200 and 100 coins.

You also need to write code to load this file (use ifstream).


Saving enemies can be done by including their position in the file. We can use this format:

number_of_enemies x1 y1 x2 y2 ...

First the number_of_enemies is read and then each position is read with a simple loop.


1

One addition/suggestion would to add a level of encryption to your serialization so users cannot text edit their values to "9999999999999999999". One good reason to do this would be to prevent integer overflows (for example).



0

For completeness sake I want to mention a c++ serialization library, that I personally use and was not mentioned yet: cereal.
It's easy to use and has a nice, clean syntax for serializing. It also offers multiple types of formats you can save to (XML, Json, Binary (including a portable version with respects endianess)). It supports inheritance and is header-only,


0

Your game will compromise data structures (hopefully?) that you need to transform into bytes (serialize) so you can store them. In a future, you may load those bytes back and transform them back into your original structure (deserialization). In C++ it is not so tricky since reflection is very limited. But still, some libraries can help you here. I wrote an article about it: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ Basically, I would suggest you to have a look at cereal library if you may target C++ 11 compilers. No need to create intermediate files like with protobuf, so you will save some time there if you want quick results. You may also choose between binary and JSON, so it can quite help debugging here.

On top of that, if security is a concern, you may want to encrypt/decrypt the data you are storing, especially if you are using human-readable formats like JSON. Algorithms like AES are helpful here.


-5

You need to use fstream for input / output files. The syntax is simple EX:

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

Or

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

Other actions are possible on your file: append, binary, trunc, etc. You would use the same syntax as above instead we put std::ios::(flags), for instance:

  • ios::out for output operation
  • ios::in for input operation
  • ios::binary for binary (raw byte) IO operation, instead of character-based
  • ios::app for to start to write at the end of the file
  • ios::trunc for if the file already exist replace delete the old content and replace by new
  • ios::ate - position the file pointer "at the end" for input/output

Ex:

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

Here is a more complete but simple example.

#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 This is a very bad answer. You should format and display correctly the code and explain what you're doing, no one wants to decipher a chunk of code.
Vaillancourt

Thank you katu for the comment you are right i should explain my code more well can you tell me how i format my source from the website cause i am new to this kind of thing
Francisco Forcier

From this site, or for this site? For help in formatting the posts, you can visit the formatting help page. There is an exclamation mark beside the header of the text area you use to post to help you as well.
Vaillancourt

Try to document what's asked; you don't need to comment everything. And by not explaining what you were doing, in my comment, I meant that generally you introduce the strategy that you suggest with at least a short paragraph. (e.g. "One of the technique is to use a binary file format with a stream operator. You have to be careful to read and write in the same order, bla bla lba").
Vaillancourt

2
And by using gotos, you'll get lynched on the public place. Don't use gotos.
Vaillancourt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.