Pages: [1]   Bas de page
Imprimer
Auteur Fil de discussion: [xna]Gestion des sauvegardes  (Lu 844 fois)
0 Membres et 1 Invité sur ce fil de discussion.
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« 16 Février 2012, 07:45:53 »

Bonjour/Bonsoir!

J'ai un petit projet depuis la fin de l'année dernière sur lequel je rravaille très irrégulièrement,  mais malgré tout, j'en suis maintenant à me préoccuper de la gestion des sauvegardes.

Est-ce que je dois tenir compte des profils? Comment faire pour détecter le profil principal, ou forcer une connexion de profil? Où est-ce que j'écris les fichiers?  Comment lire ces fichiers par la suite?

Et sur Windows, où est ce que j'écris les fichiers ?

Autant de questions que je me pose, et pour lesquels je fais appel à vous Wink. Merci de votre aide!
Journalisée
Valryon Hors ligne
Sr. Member
****
Messages: 331


Voir le profil WWW
The World ends with you !

« Réponse #1 : 16 Février 2012, 10:25:12 »

Bonjour/Bonsoir!

J'ai un petit projet depuis la fin de l'année dernière sur lequel je rravaille très irrégulièrement,  mais malgré tout, j'en suis maintenant à me préoccuper de la gestion des sauvegardes.

Est-ce que je dois tenir compte des profils? Comment faire pour détecter le profil principal, ou forcer une connexion de profil? Où est-ce que j'écris les fichiers?  Comment lire ces fichiers par la suite?

Et sur Windows, où est ce que j'écris les fichiers ?

Autant de questions que je me pose, et pour lesquels je fais appel à vous Wink. Merci de votre aide!

Salut !

Alors pour mes jeux en XNA j'utilise EasyStorage : http://easystorage.codeplex.com/, ça permet d'abstraire la gestion de sauvegardes quelque soit le périphérique.

Mais pour PC ce n'est pas forcément optimal, puisque ça stocke les fichiers (XML dans mon cas) dans le dossier "Parties Enregistrées" sur Windows. C'est possible je pense d'éviter ça en choisissant plutôt le dossier AppData ou Program Data (attention, Program Files est verrouillé sur Windows 7 donc pas de sauvegardes à côté de l'exe !), mais je n'ai jamais essayé.

Pour illustrer, voici un bout de code utilisé dans mon jeu "Rabbit Apocalypse" (code source complet disponible sur mon blog http://www.valryon.fr).

---
Classe gérant les sauvegardes génériques avec EasyStorage (non testée sur WP7 et Xbox 360 mais ça doit marcher a peu de chose prêt) :

Code:
using System;
using EasyStorage;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif
using System.Xml;
using System.Xml.Serialization;
using System.Threading;
using Lapins.Engine.Core;

namespace Lapins.Engine.Storage
{
    /// <summary>
    /// Multi-platform savegame handler (with EasyStorage Lib)
    /// </summary>
    public class Saver<TSave>
    {
        /// <summary>
        /// Save file name
        /// </summary>
        private string _filename;

        /// <summary>
        /// Directory name
        /// </summary>
        private string _containerName;

        /// <summary>
        /// EasyStorage manager
        /// </summary>
        private IAsyncSaveDevice _saveDevice;

        /// <summary>
        /// Serializer
        /// </summary>
        private XmlSerializer _serializer;

        /// <summary>
        /// Current savegame
        /// </summary>
        private TSave _data;

        /// <summary>
        /// Raised when the save is completed
        /// </summary>
        public event Action<TSave> SaveCompleted;

        /// <summary>
        /// Raised when the load is completed
        /// </summary>
        public event Action<TSave> LoadCompleted;

        public Saver(Game instance, string filename, string contrainerName)
        {
            _filename = filename;
            _containerName = contrainerName;
            _serializer = new XmlSerializer(typeof(TSave));

            #region EasyStorage Init

            EasyStorageSettings.SetSupportedLanguages(Language.French, Language.Spanish, Language.English, Language.German, Language.Japanese, Language.Italian);

            //Windows Phone7: IsolatedStorage
#if WINDOWS_PHONE
            saveDevice = new IsolatedStorageSaveDevice();
#else
            //Xbox & PC: SharedDevice
            //-- Create
            SharedSaveDevice sharedSaveDevice = new SharedSaveDevice();
            instance.Components.Add(sharedSaveDevice);

            _saveDevice = sharedSaveDevice;

            //-- Events on Xbox
            // Force a player to choose a storage location on current storage disconnection
            sharedSaveDevice.DeviceSelectorCanceled += (s, e) => e.Response = SaveDeviceEventResponse.Force;
            sharedSaveDevice.DeviceDisconnected += (s, e) => e.Response = SaveDeviceEventResponse.Force;

            // Ask the player to choose
            sharedSaveDevice.PromptForDevice();
#endif

            // End of savegame events
            _saveDevice.SaveCompleted += new SaveCompletedEventHandler(saveDevice_SaveCompleted);
            _saveDevice.LoadCompleted += new LoadCompletedEventHandler(saveDevice_LoadCompleted);

            #endregion
        }

        /// <summary>
        /// Register an empty save data (or reset the current one)
        /// </summary>
        /// <param name="emptySave"></param>
        public void Initialize(TSave emptySave)
        {
            _data = emptySave;
            ForceSaveAsync();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void saveDevice_SaveCompleted(object sender, FileActionCompletedEventArgs args)
        {
            if (SaveCompleted != null) SaveCompleted(_data);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void saveDevice_LoadCompleted(object sender, FileActionCompletedEventArgs args)
        {
            if (LoadCompleted != null) LoadCompleted(_data);
        }

        /// <summary>
        /// Save asynchronously the data
        /// </summary>
        /// <returns></returns>
        public bool SaveAsync()
        {
            if (_saveDevice.IsReady)
            {
                _saveDevice.SaveAsync(
                     _containerName,
                     _filename,
                    stream =>
                    {
                        XmlWriterSettings settings = new XmlWriterSettings()
                        {
                            Indent = true,
                        };

                        using (XmlWriter writer = XmlWriter.Create(stream, settings))
                        {
                            SerializeSave(writer);
                        }

                    });

                return true;
            }

            return false;
        }

        /// <summary>
        /// Load asynchronously the data
        /// </summary>
        /// <returns></returns>
        public bool LoadAsync()
        {
            if (_saveDevice.IsReady)
            {
                _saveDevice.LoadAsync(
                    _containerName,
                    _filename,
                   stream =>
                   {
                       using (XmlReader reader = XmlReader.Create(stream))
                       {
                           DeserializeSave(reader);
                       }
                   });

                return true;
            }

            return false;
        }

        /// <summary>
        /// Try to save until it works
        /// </summary>
        /// <returns></returns>
        public void ForceSaveAsync()
        {
            new Thread(new ThreadStart(
              delegate()
              {
                  while (this.SaveAsync() == false) ;
              }
                )
             ).Start();
        }

        /// <summary>
        /// Try to load until it works
        /// </summary>
        /// <returns></returns>
        public void ForceLoad()
        {
            while (this.LoadAsync() == false) ;
        }

        /// <summary>
        /// Try to load until it works
        /// </summary>
        /// <returns></returns>
        public void ForceLoadAsync()
        {
            new Thread(new ThreadStart(
              delegate()
              {
                  while (this.LoadAsync() == false) ;
              }
                )
             ).Start();
        }

        /// <summary>
        /// Savegame serialization
        /// </summary>
        /// <param name="writer"></param>
        private void SerializeSave(XmlWriter writer)
        {
            _serializer.Serialize(writer, _data);
        }

        /// <summary>
        /// Savegame deserialization
        /// </summary>
        /// <param name="reader"></param>
        private void DeserializeSave(XmlReader reader)
        {
            var data = _serializer.Deserialize(reader);
            if (data != null)
            {
                _data = (TSave)data;
            }
        }

        /// <summary>
        /// Current Savegame
        /// </summary>
        public TSave Data
        {
            get { return _data; }
        }
    }

----
La classe sérializable (XML) contenant les données à sauver :

Code:
using System;
using Lapins.Engine.Core;
using Lapins.Engine.Score;
using Microsoft.Xna.Framework;

namespace Lapins.Save
{
    [Serializable]
    public class LapinsSaveData
    {
        public const int HighscoresLinesCount = 10;

        // Serializable fields
        // -- Savegame attributes
        public string Version;
        public string ApplicationName;
        public DateTime Date;

        // -- Parameters
        public int ResolutionWidth;
        public int ResolutionHeight;
        public bool IsFullscreen;

        // -- Score
        public Highscores Highscores;

        public LapinsSaveData()
        {
            Version = Application.Version;
            ApplicationName = Application.Name;
            Date = DateTime.Now;
            Highscores = new Highscores(HighscoresLinesCount);

            IsFullscreen = false;
            ResolutionWidth = 1280;
            ResolutionHeight = 768;
        }
    }
}

---
Utilisation.

Création :
Code:
Saver = new Saver<LapinsSaveData>(this, "lapins.lps", "Lapins");

Chargement (je n'utilise pas le côté asynchrone car c'est déjà dans mon code de chargement, Intiailize de Game) :
Code:

...
          // Let's load the savegame and wait until it's ready
            Saver.ForceLoadAsync();
            Saver.LoadCompleted += Saver_LoadCompleted;
...

       void Saver_LoadCompleted(LapinsSaveData data)
        {
            Saver.LoadCompleted -= Saver_LoadCompleted;

            if (data == null)
            {
                Saver.Initialize(new LapinsSaveData());
            }

        }

---
Sauvegarde d'un score dans un objet Highscore prévu pour :
 
Code:
           
// Save score
Game.Saver.Data.Highscores.AddScore(new ScoreLine("playername", _score));
Game.Saver.SaveAsync();


Journalisée

Développeur professionnel et passionné de jeu vidéo, auteur de The Great Paper Adventure (PC/X360) :
-> http://www.thegreatpaperadventure.com
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« Réponse #2 : 17 Février 2012, 07:48:07 »

Merci pour ta réponse ! Je pense me tourner vers EasyStorage vu qu'il a l'air parfait!
C'est la première fois que je vais utiliser la serialisation Azn  ton code me sera précieux je pense!
Journalisée
Valryon Hors ligne
Sr. Member
****
Messages: 331


Voir le profil WWW
The World ends with you !

« Réponse #3 : 17 Février 2012, 14:46:04 »

Merci pour ta réponse ! Je pense me tourner vers EasyStorage vu qu'il a l'air parfait!
C'est la première fois que je vais utiliser la serialisation Azn  ton code me sera précieux je pense!

J'ai reçu un mail à ce sujet d'un dév venant d'ici, je me permets de reposter un exemple "écrit de tête" plus simple que celui de Rabbit Apocalypse!

Les "..." sont à remplacer par le code habituel d'XNA. L'idée est de stocker un entier, d'incrémenter cet entier à chaque appui d'une touche du clavier et de retrouver la valeur à chaque réouverture du jeu.

Code:
...
public class MyData
{
public int Number {get;set;}
   
public MyData() {
// Valeurs par défauts
Number = 0;
}
}

Code:
...
public class Game1 : Game
{
/// <summary>
/// Gestionnaire de sauvegarde EasyStorage
/// </summary>
private IAsyncSaveDevice _saveDevice;

/// <summary>
/// Serializeur XML
/// </summary>
private XmlSerializer _serializer;

/// <summary>
/// Sauvegarde
/// </summary>
private MyData _data;

...

public override Initialize() {
...

#region EasyStorage Initialisation du gestionnaire de sauvegarde

// Langues autorisées, EasyStorage gère des pop-ups pour le choix du périph de stockage sur console
EasyStorageSettings.SetSupportedLanguages(Language.French, Language.Spanish, Language.English, Language.German, Language.Japanese, Language.Italian);

_serializer = new XmlSerializer(typeof(MyData));

// La création d'un périphértique de stockage

#if WINDOWS_PHONE
// -- Windows Phone7: IsolatedStorage
saveDevice = new IsolatedStorageSaveDevice();
#else
// -- Xbox & PC: SharedDevice
SharedSaveDevice sharedSaveDevice = new SharedSaveDevice();
this.Components.Add(sharedSaveDevice);

_saveDevice = sharedSaveDevice;

//-- Events Xbox lancés en cas de déconnexion du périph, genre arrachage de carte mémoire
// Ici on force le joueur a choisir un nouveau périphérique de stockage dans le cas où il a été enlevé
sharedSaveDevice.DeviceSelectorCanceled += (s, e) => e.Response = SaveDeviceEventResponse.Force;
sharedSaveDevice.DeviceDisconnected += (s, e) => e.Response = SaveDeviceEventResponse.Force;

// Affichage de la pop-up pour choisir un emplacement de sauvegarde. S'il n'y en a qu'un, il n'y a pas de pop-up le choix est automatique
sharedSaveDevice.PromptForDevice();
#endif

// On charge les données sauvées
// Normalement c'est asynchrone, mais ici on attend que le chargement soit réussi
while (this.LoadAsync() == false) ;

// Si les données sont vides, il faut lse créer
if (_data == null)
{
_data = new MyData();
SaveAsync();
}

...
}

public override Update(GameTime gameTime)
{
...

// Incrémentation du nombre et sauvegarde
//-- Appui sur une touche
if(Keyboard.GetState().GetPressedKeys().Count > 0) {
_data.Number++;
SaveAsync();
}

...
}

public override Draw(GameTime gameTime)
{
...
// TODO Affichage du nombre
...
}

/// <summary>
/// Load asynchronously the data
/// </summary>
/// <returns></returns>
private bool LoadAsync()
{
if (_saveDevice.IsReady)
{
_saveDevice.LoadAsync(
"MonJeu",
"fichierSauve.xml",
   stream =>
   {
// A remplacer par ce que l'on veut au lieu du XML puisque l'on dispose d'un flux
   using (XmlReader reader = XmlReader.Create(stream))
   {
var data = _serializer.Deserialize(reader);
if (data != null)
{
_data = (MyData)data;
}
   }
   });

return true;
}

return false;
}

/// <summary>
/// Save asynchronously the data
/// </summary>
/// <returns></returns>
private bool SaveAsync()
{
if (_saveDevice.IsReady)
{
_saveDevice.SaveAsync(
"MonJeu",
"fichierSauve.xml",
stream =>
{
// A remplacer par ce que l'on veut au lieu du XML puisque l'on dispose d'un flux
XmlWriterSettings settings = new XmlWriterSettings()
{
Indent = true,
};

using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
_serializer.Serialize(writer, _data);
}

});

return true;
}

return false;
}
}
Journalisée

Développeur professionnel et passionné de jeu vidéo, auteur de The Great Paper Adventure (PC/X360) :
-> http://www.thegreatpaperadventure.com
Valryon Hors ligne
Sr. Member
****
Messages: 331


Voir le profil WWW
The World ends with you !

« Réponse #4 : 19 Février 2012, 21:30:52 »

Le code au dessus possède un défaut majeur : on ne peut pas utiliser la sauvegarde dans le Initialize. Il doit y avoir des opérations effectuées après qui permette d'accéder aux disques durs et autres, donc les Load et Save sont à faire au plus tôt dans le LoadContent.

Je partage ici une source complète d'un dev qui m'a envoyé un mail après être passé ici :
http://blendman.free.fr/dev/xna/sauvegarder.zip
Journalisée

Développeur professionnel et passionné de jeu vidéo, auteur de The Great Paper Adventure (PC/X360) :
-> http://www.thegreatpaperadventure.com
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« Réponse #5 : 20 Février 2012, 09:26:33 »

Ok, merci pour l'info Smiley.
Donc si par exemple je veux connaître les paramètres graphiques sauvegardés, je suis obligés de faire une première GameApplication, puis de lancer l'autre avec les paramètres chargés.
C'est peut-être pas plus mal côté design de faire un "bootstrapper".

ps: oh toi qui rode sur ce forum, inscris-toi et poste (si tu veux bien) Smiley
Journalisée
Valryon Hors ligne
Sr. Member
****
Messages: 331


Voir le profil WWW
The World ends with you !

« Réponse #6 : 20 Février 2012, 09:53:04 »

Ok, merci pour l'info Smiley.
Donc si par exemple je veux connaître les paramètres graphiques sauvegardés, je suis obligés de faire une première GameApplication, puis de lancer l'autre avec les paramètres chargés.
C'est peut-être pas plus mal côté design de faire un "bootstrapper".

ps: oh toi qui rode sur ce forum, inscris-toi et poste (si tu veux bien) Smiley

Hmm tu ne pourras pas faire deux applications je pense, mais le problème que tu entrevois avec les paramètres graphiques est tout à fait pertinent.

Disons que dans le constructeur et la méthode Initiailize de ta classe Game, tu ne dois pas avoir besoin des paramètres.

C'est une fois dans le LoadContent, ou plus tard (premier Update par exemple) que tu pourra appeler la sauvegarde et récupérer la résolution, les préférences au clavier, etc... il faut prévoir de quoi modifier tout ça "à la volée".

Par exemple pour les paramètres graphiques :
Code:
     
   graphics.PreferredBackBufferWidth = 800;
   graphics.PreferredBackBufferHeight = 600;
   graphics.IsFullScreen = false;
   graphics.ApplyChanges();
Journalisée

Développeur professionnel et passionné de jeu vidéo, auteur de The Great Paper Adventure (PC/X360) :
-> http://www.thegreatpaperadventure.com
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« Réponse #7 : 20 Février 2012, 10:04:19 »

Je pensais faire un premier truc qui me charge la sauvegarde, puis fermer ce "Game", puis en lancer un autre, installer le Save Device dans ses composants et hop... Je vais essayer quand même Azn.

ça me paraîtrait bizarre de ne pas pouvoir créer deux "Game" car c'est ce que je fais notamment, lorsque mon game principal plante, j'en crée un autre pour afficher la stacktrace.
Journalisée
Valryon Hors ligne
Sr. Member
****
Messages: 331


Voir le profil WWW
The World ends with you !

« Réponse #8 : 20 Février 2012, 10:08:26 »

Je pensais faire un premier truc qui me charge la sauvegarde, puis fermer ce "Game", puis en lancer un autre, installer le Save Device dans ses composants et hop... Je vais essayer quand même Azn.

ça me paraîtrait bizarre de ne pas pouvoir créer deux "Game" car c'est ce que je fais notamment, lorsque mon game principal plante, j'en crée un autre pour afficher la stacktrace.

Moi ça me paraît bizarre d'utiliser plusieurs Game Cheesy
Je ne sais pas si c'est possible sur Xbox / WP7, ça serait intéressant de tester. Mais je ne juge pas, je suis juste surpris Smiley
Journalisée

Développeur professionnel et passionné de jeu vidéo, auteur de The Great Paper Adventure (PC/X360) :
-> http://www.thegreatpaperadventure.com
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« Réponse #9 : 20 Février 2012, 10:57:56 »

J'ai remarqué que le LoadContent() est appelé par l'Initialize() de la classe Game que lorsque il y a un GraphicsManager. Donc c'est possible de charger dans son propre Initialize() que après l'appel de base.Initialize() en fait.
Journalisée
Zipler Hors ligne
Full Member
***
Messages: 106


Voir le profil
« Réponse #10 : 20 Février 2012, 11:34:55 »

Evidemment, ce que je voulais faire ne marche pas pour une raison inexplicable.

Finalement, je charge la sauvegarde, puis modifie la taille de la fenêtre et du back buffer dans le "Game" principal. Et crotte.
« Dernière édition: 20 Février 2012, 12:15:28 par Zipler » Journalisée
Pages: [1]   Haut de page
Imprimer

Aller à: