Gestion de la Persistance des Données XML dans Unity

Introduction à la Persistance XML

Le langage XML (Extensible Markup Language) est largement utilisé dans le développement Unity pour structurer et stocker des données de manière lisible. Contrairement aux formats binaires, XML permet une inspection facile des sauvegardes. L'objectif principal est de transférer l'état mémoire d'une application vers un stockage permanent (disque dur ou serveur) afin de récupérer les données lors d'une session ultérieure.

Manipulation du DOM avec XmlDocument

L'approche native via System.Xml offre un contrôle granulaire sur la structure du fichier. Voici les opérations fondamentales :

  • Chargement du document en mémoire.
  • Navigation dans l'arborescence des nœuds.
  • Extraction des valeurs (via InnerText pour le contenu ou Attributes pour les métadonnées).

Le script suivant illustre la lecture, l'écriture et la modification dynamique d'un fichier de configuration.

using System.IO;
using System.Xml;
using UnityEngine;

namespace Data.Management
{
    public class XmlDataController : MonoBehaviour
    {
        private void Start()
        {
            // Initialisation du document
            XmlDocument doc = new XmlDocument();
            
            // Chargement depuis le dossier StreamingAssets (lecture seule souvent)
            // Pour la production, privilégiez PersistentDataPath
            string loadPath = Application.streamingAssetsPath + "/Config.xml";
            
            // Gestion des plateformes (Android nécessite une coroutine pour StreamingAssets)
            #if !UNITY_ANDROID
            doc.Load(loadPath);
            #endif

            // Navigation vers la racine
            XmlNode rootNode = doc.SelectSingleNode("GameSave");
            if (rootNode != null)
            {
                // Accès aux attributs
                XmlNode profileNode = rootNode.SelectSingleNode("Profile");
                string userId = profileNode.Attributes["uid"].Value;
                Debug.Log($"Utilisateur : {userId}");

                // Accès aux nœuds enfants (InnerText)
                XmlNode settingsNode = rootNode.SelectSingleNode("Settings");
                string volume = settingsNode.SelectSingleNode("AudioLevel").InnerText;
                Debug.Log($"Volume : {volume}");
            }

            // Création d'un nouveau fichier de sauvegarde
            CreateSaveFile();
        }

        private void CreateSaveFile()
        {
            XmlDocument newDoc = new XmlDocument();
            
            // Déclaration XML standard
            XmlDeclaration decl = newDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
            newDoc.AppendChild(decl);

            // Racine
            XmlElement root = newDoc.CreateElement("GameSave");
            newDoc.AppendChild(root);

            // Nœud Profil avec attributs
            XmlElement profile = newDoc.CreateElement("Profile");
            profile.SetAttribute("uid", "USER_99");
            profile.SetAttribute("level", "5");
            root.AppendChild(profile);

            // Nœud Inventaire
            XmlElement inventory = newDoc.CreateElement("Inventory");
            for (int i = 0; i < 5; i++)
            {
                XmlElement item = newDoc.CreateElement("Item");
                item.SetAttribute("itemId", (100 + i).ToString());
                item.InnerText = $"Objet_{i}";
                inventory.AppendChild(item);
            }
            root.AppendChild(inventory);

            // Sauvegarde dans un dossier accessible en écriture
            string savePath = Path.Combine(Application.persistentDataPath, "SaveGame.xml");
            newDoc.Save(savePath);
            
            ModifyExistingData(savePath);
        }

        private void ModifyExistingData(string path)
        {
            if (!File.Exists(path)) return;

            XmlDocument modDoc = new XmlDocument();
            modDoc.Load(path);

            // Modification d'une valeur
            XmlNode target = modDoc.SelectSingleNode("GameSave/Profile");
            if (target != null)
            {
                target.Attributes["level"].Value = "10";
            }

            // Suppression et ajout
            XmlNode invNode = modDoc.SelectSingleNode("GameSave/Inventory");
            if (invNode.FirstChild != null)
            {
                invNode.RemoveChild(invNode.FirstChild);
            }

            modDoc.Save(path);
        }
    }
}

Gestion des Chemins d'Accès dans Unity

Le choix du répertoire est crucial pour la persistance :

  • Resources : Lecture seule, inclus dans le build.
  • StreamingAssets : Lecture seule sur mobile, écriture possible sur PC.
  • DataPath : Variable selon la plateforme, souvent inaccessible en écriture après build.
  • PersistentDataPath : Recommandé. Lecture/écriture garantie sur toutes les plateformes.

Sérialisation Automatique avec XmlSerializer

Pour éviter la manipulation manuelle des nœuds, XmlSerializer permet de convertir directement des objets C# en XML. Cette méthode est plus propre pour les structures de données complexes.

Considérons les classes de données suivantes :

using System.Collections.Generic;
using System.Xml.Serialization;

[XmlRoot("PlayerProfile")]
public class PlayerData
{
    [XmlElement("UserName")]
    public string username;

    [XmlElement("Score")]
    public int highScore;

    // Personnalisation du nom de la liste dans le XML
    [XmlArray("EquipmentList")]
    [XmlArrayItem("Gear")]
    public List<string> equipment;

    // Les champs privés ou propriétés sans setter public ne sont pas sérialisés
    [XmlIgnore]
    public int temporaryCache;
}

public class GameStats
{
    [XmlAttribute("Version")]
    public string ver = "1.0";
    
    public float playTime;
}</string>

Voici comment effectuer la sérialisation (écriture) et la désérialisation (lecture) :

using System.IO;
using System.Xml.Serialization;
using UnityEngine;

public class SerializationHandler : MonoBehaviour
{
    void Start()
    {
        string path = Path.Combine(Application.persistentDataPath, "Profile.xml");
        
        // Sérialisation
        PlayerData data = new PlayerData
        {
            username = "HeroOne",
            highScore = 5000,
            equipment = new List<string> { "Sword", "Shield" }
        };

        using (StreamWriter writer = new StreamWriter(path))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
            serializer.Serialize(writer, data);
        }

        // Désérialisation
        if (File.Exists(path))
        {
            using (StreamReader reader = new StreamReader(path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(PlayerData));
                PlayerData loadedData = serializer.Deserialize(reader) as PlayerData;
                Debug.Log($"Joueur chargé : {loadedData.username}");
            }
        }
    }
}</string>

Limitations et Bonnes Pratiques

L'utilisation de XmlSerializer impose cetraines contraintes techniques :

  • Seuls les champs publics (public) sont sauvegardés par défaut.
  • Les dictionnaires (Dictionary<K,V>) ne sont pas supportés nativement par le sérialiseur XML standard.
  • Attention aux initialisations : si une liste est initialisée avec des valeurs par défaut dans la déclaration de la classe, la désérialisation peut dupliquer ces valeurs si le fichier XML contient également des éléments. Il est préférable d'initialiser les collections dans le constructeur ou via des méthodes dédiées.

Étiquettes: Unity C# XML sérialisation PersistanceDeDonnées

Publié le 3 août à 11h34