Comment créer un fichier de sauvegarde pour un jeu C ++?


33

Je suis en train de coder ma finale pour un cours de programmation de jeux vidéo et je veux savoir comment créer un fichier de sauvegarde pour mon jeu afin qu'un utilisateur puisse y jouer, puis y revenir plus tard. Toute idée de la façon dont cela est fait, tout ce que j'ai fait auparavant a été des programmes à exécution unique.



2
Vous pouvez également utiliser SQLite
Nick Shvelidze

1
@ Shhvelo Bien que vous puissiez faire cela, il semble que cela ajouterait beaucoup de complexité qui n'est pas nécessairement nécessaire.
Nate

Réponses:


38

Vous devez utiliser la sérialisation pour enregistrer vos variables en mémoire sur votre disque dur. Il existe de nombreux types de sérialisation. Le format .NET XML est un format courant, bien que des sérialiseurs binaires et JSON soient disponibles. Je ne suis pas un grand programmeur C ++, mais une recherche rapide a donné un exemple de sérialisation en C ++:

Il existe des bibliothèques offrant des fonctionnalités de sérialisation. Certains sont mentionnés dans d'autres réponses.

Les variables qui vous intéresseront vont probablement être liées à l’état du jeu. Par exemple, vous voudrez probablement connaître ce type d’information

  1. Le joueur jouait au niveau 3
  2. Le joueur était aux coordonnées mondiales X, Y
  3. Le joueur a trois objets dans son sac à dos
    1. Arme
    2. Armure
    3. Nourriture

Vous ne vous soucierez pas vraiment des textures utilisées (sauf si votre joueur peut changer leur apparence, c'est un cas particulier), car elles sont généralement les mêmes. Vous devez vous concentrer sur la sauvegarde des données de jeu importantes.

Lorsque vous démarrez votre jeu, vous commencez normalement pour un "nouveau" jeu (cela charge vos textures, modèles, etc.), mais au moment opportun, vous chargez les valeurs de votre fichier de sauvegarde dans l'objet d'état du jeu en remplaçant le "par défaut" nouveau état du jeu. Ensuite, vous autorisez le joueur à reprendre la lecture.

Je l'ai grandement simplifié ici, mais vous devriez avoir une idée générale. Si vous avez une question plus spécifique, posez-en une nouvelle ici et nous pouvons essayer de vous aider.


Je comprends ce que j'ai besoin de sauvegarder, mais j'aimerais savoir quelle est la méthode exacte. Enregistrez-vous dans un fichier .txt du projet, ces variables modifiées ou un autre moyen
Tucker Morgan

Oui, si votre jeu est simple, un fichier texte peut suffire; vous devez garder à l'esprit que tout le monde peut éditer un fichier texte et créer ainsi ses propres sauvegardes ...
Nate

Les sauvegardes de fichiers texte ne concernent pas que des jeux simples. Paradox a utilisé un format de texte structuré pour enregistrer les fichiers de tous les jeux créés avec le même moteur que le moteur phare de Europa Universalis. Surtout en fin de partie, ces fichiers pourraient être énormes.
Dan Neely

1
@DanNeely Un point juste, aucune raison pour laquelle vous ne pouvez pas utiliser un format texte pour stocker beaucoup de données compliquées, mais en général, lorsque vos données sont aussi compliquées, les avantages d'un autre format (binaire, xml, etc.) deviennent plus prononcés.
Nate

1
@NateBross D'accord. Les jeux Paradox étaient très conviviaux pour les mods et utilisaient un format similaire (identique?) Pour les données de scénario. En stockant la plupart de leurs données sous forme de texte, ils n'ont pas besoin d'investir dans des outils d'édition de scénarios destinés au public.
Dan Neely

19

En règle générale, cela est spécifique à votre jeu. Je suis sûr que vous avez appris à écrire et à lire des fichiers dans vos classes jusqu'à présent. L'idée de base est:

  1. Lorsque vous quittez le jeu, écrivez les valeurs que vous souhaitez enregistrer dans un fichier.
  2. Lors du chargement du jeu, vérifiez s'il existe un fichier de sauvegarde. Si c'est le cas, chargez les valeurs lues dans votre programme. Si le fichier n'existe pas, continuez comme vous le faites maintenant et définissez les valeurs sur leurs valeurs de départ / par défaut.

Ce que vous écrivez dépend de vous, cela dépend de votre jeu. Une façon d'écrire consiste à écrire les variables souhaitées dans un ordre spécifique sous forme de flux d'octets. Puis, lors du chargement, lisez-les dans votre programme dans le même ordre.

Par exemple (en pseudo code rapide):

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
Cette méthode est jolie et petite, bien que je vous recommande de mettre en place des balises simples pour les morceaux de données. Ainsi, si vous devez ultérieurement modifier quelque chose qui se trouve normalement au milieu du fichier, vous pouvez le faire et la seule "conversion d'ancien" que vous devez effectuer se situe dans ce bloc. Ce n'est pas aussi important pour une affectation ponctuelle, mais si vous continuez à travailler après que les gens commencent à obtenir des fichiers de sauvegarde, c'est un peu cauchemardesque d'utiliser simplement des octets droits avec la position comme unique identifiant.
Lunin

1
Oui, cela ne génère pas de fichiers de sauvegarde à l'épreuve du temps. Cela ne fonctionne pas non plus pour les données dont la taille en octets est variable, comme les chaînes. Ce dernier est facile à corriger en écrivant d’abord la taille des données sur le point d’être écrites, puis en l’utilisant lors du chargement pour lire le nombre correct d’octets.
MichaelHouse

6

Il existe probablement de nombreuses façons de le faire, mais le plus simple que j'ai toujours trouvé et utilisé personnellement et professionnellement consiste à créer une structure contenant toutes les valeurs que je souhaite sauvegarder.

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
}

Je viens alors de fwrite / fread les données vers et depuis un fichier en utilisant les valeurs de base File IO. InventoryCount est le nombre de structures Item qui sont enregistrées après la structure SaveGameData principale dans le fichier. Par conséquent, je sais combien de celles-ci doivent être lues après l'extraction de ces données. La clé ici est que lorsque je veux enregistrer quelque chose de nouveau, à moins que ce ne soit une liste d’articles ou tout autre objet similaire, tout ce que j’ai à faire est d’ajouter une valeur à la structure quelque part. Si c'est une liste d'éléments, je devrai ajouter une lecture, comme je l'ai déjà indiqué pour les objets Article, un compteur dans l'en-tête principal, puis les entrées.

Cela présente l'inconvénient de rendre différentes versions d'un fichier de sauvegarde incompatibles les unes des autres sans traitement spécial (même s'il ne s'agit que de valeurs par défaut pour chaque entrée de la structure principale). Mais dans l’ensemble, cela rend le système facile à étendre en ajoutant simplement une nouvelle valeur de données et en y ajoutant une valeur en cas de besoin.

Encore une fois, de nombreuses façons de faire ceci et cela pourraient mener davantage au C que au C ++, mais le travail est fait!


1
Il convient également de noter que ceci n'est pas indépendant de la plate-forme, ne fonctionnera pas pour les chaînes C ++, ni pour les objets référencés via des références ou des pointeurs, ou pour tout objet contenant l'un de ces éléments!
Kylotan

Pourquoi cette plateforme n'est-elle pas indépendante? Cela a bien fonctionné sur le PC, les systèmes PS * et le système 360 ​​.. fwrite (pToDataBuffer, sizeof (type de données), countOfElements, pToFile); fonctionne pour tous ces objets en supposant que vous puissiez obtenir un pointeur sur leurs données, la taille de l'objet puis leur nombre .. et lire les correspondances qui ..
James

Il est indépendant de la plate-forme, rien ne garantit que les fichiers enregistrés sur une plate-forme puissent être chargés sur une autre. Ce qui n’est pas pertinent pour, par exemple, la sauvegarde de données de jeu. Le pointeur sur les données et la taille de la mémoire peut évidemment être un peu gênant, mais cela fonctionne.
gauche autour du

3
En fait, rien ne garantit que cela continuera à fonctionner pour vous pour toujours - que se passera-t-il si vous publiez une nouvelle version construite avec un nouveau compilateur ou même de nouvelles options de compilation modifiant le remplissage de la structure? Je déconseillerais fortement, fortement l'utilisation de raw-struct fwrite () pour cette seule raison (je parle par expérience de celle-ci, d'ailleurs).
moelleux

1
Il ne s'agit pas de «32 bits de données». L’affiche originale demande simplement "comment enregistrer mes variables". Si vous écrivez directement la variable, vous perdez des informations sur toutes les plates-formes. Si vous devez prétraiter avant l'écriture, vous avez omis la partie la plus importante de la réponse, à savoir. comment traiter les données de manière à ce qu'elles soient correctement enregistrées et n'incluent que le bit trivial, c'est-à-dire. appelant fwrite pour mettre quelque chose sur un disque.
Kylotan

3

Vous devez d’abord décider quelles données doivent être sauvegardées. Par exemple, cela pourrait être l'emplacement du personnage, son score et le nombre de pièces. Bien entendu, votre jeu sera probablement beaucoup plus complexe et vous devrez donc sauvegarder des données supplémentaires telles que le numéro de niveau et la liste des ennemis.

Ensuite, écrivez le code pour enregistrer cela dans un fichier (utilisation destream). Un format relativement simple que vous pouvez utiliser est le suivant:

x y score coins

Et le fichier ressemblerait à ceci:

14 96 4200 100

Ce qui voudrait dire qu'il était à la position (14, 96) avec un score de 4200 et 100 pièces.

Vous devez également écrire du code pour charger ce fichier (utilisez ifstream).


La sauvegarde des ennemis peut se faire en incluant leur position dans le fichier. Nous pouvons utiliser ce format:

number_of_enemies x1 y1 x2 y2 ...

Tout d'abord, number_of_enemieson lit et ensuite chaque position est lue avec une simple boucle.


1

Une addition / suggestion serait d’ajouter un niveau de cryptage à votre sérialisation afin que les utilisateurs ne puissent pas éditer leur valeur au format texte en "9999999999999999999". Une bonne raison de le faire serait d'éviter les débordements d'entiers (par exemple).



0

Par souci d'exhaustivité, je veux mentionner une bibliothèque de sérialisation c ++, que j'utilise personnellement et qui n'a pas encore été mentionnée: céréales .
Il est facile à utiliser et possède une syntaxe propre et élégante pour la sérialisation. Il propose également plusieurs types de formats dans lesquels vous pouvez enregistrer (XML, Json, Binary (y compris une version portable respectueuse de l'environnement)). Il supporte l'héritage et est seulement en-tête,


0

Votre jeu va compromettre les structures de données (espérons-le?) Que vous devez transformer en octets (sérialiser) afin que vous puissiez les stocker. Dans le futur, vous pourrez ré-charger ces octets et les reconvertir dans votre structure d'origine (désérialisation). En C ++, ce n’est pas si compliqué, car la réflexion est très limitée. Mais encore, certaines bibliothèques peuvent vous aider ici. J'ai écrit un article à ce sujet: https://rubentorresbonet.wordpress.com/2014/08/25/an-overview-of-data-serialization-techniques-in-c/ En gros, je vous suggère de jeter un coup d'œil à bibliothèque de céréales si vous pouvez cibler les compilateurs C ++ 11. Pas besoin de créer des fichiers intermédiaires comme avec protobuf, vous gagnerez du temps si vous voulez des résultats rapides. Vous pouvez également choisir entre binaire et JSON, cela peut donc aider au débogage ici.

De plus, si la sécurité vous préoccupe, vous souhaiterez peut-être chiffrer / déchiffrer les données que vous stockez, en particulier si vous utilisez des formats lisibles par l'homme tels que JSON. Des algorithmes comme AES sont utiles ici.


-5

Vous devez utiliser fstreampour les fichiers d'entrée / sortie. La syntaxe est simple EX:

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

Ou

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

D'autres actions sont possibles sur votre fichier: append , binary , trunc , etc. Vous utiliseriez la même syntaxe que celle décrite ci-dessus à la place de std :: ios: :( flags), par exemple:

  • ios::out pour l'opération de sortie
  • ios::in pour l'opération d'entrée
  • ios::binary pour opération IO binaire (octet brut), au lieu de caractères
  • ios::app pour commencer à écrire à la fin du fichier
  • ios::trunc car si le fichier existe déjà, remplacez l'ancien contenu et remplacez par nouveau
  • ios::ate - positionne le pointeur de fichier "à la fin" pour les entrées / sorties

Ex:

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

Voici un exemple plus complet mais simple.

#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 C'est une très mauvaise réponse. Vous devez formater et afficher correctement le code et expliquer ce que vous faites. Personne ne veut déchiffrer un bloc de code.
Vaillancourt

Merci katu pour le commentaire que tu as raison, je devrais expliquer mon code plus bien peux-tu me dire comment je formate ma source depuis le site web car je suis nouveau dans ce genre de chose
Francisco Forcier

Depuis ce site ou pour ce site? Pour obtenir de l'aide sur le formatage des publications, vous pouvez consulter la page d'aide sur le formatage . Il y a un point d'exclamation à côté de l'en-tête de la zone de texte que vous utilisez pour poster pour vous aider également.
Vaillancourt

Essayez de documenter ce qui est demandé. vous n'avez pas besoin de tout commenter. Et en n'expliquant pas ce que vous faisiez, dans mon commentaire, je voulais dire que vous présentez généralement la stratégie que vous suggérez avec au moins un court paragraphe. (par exemple, "une des techniques consiste à utiliser un format de fichier binaire avec un opérateur de flux. Vous devez faire attention à lire et à écrire dans le même ordre, bla bla lba").
Vaillancourt

2
Et en utilisant gotos, vous serez lynché sur la place publique. Ne pas utiliser gotos.
Vaillancourt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.