Serialización de datos en .NET: como exportar e importar datos de nuestro juego en archivos fácilmente

No hace mucho un compañero de los foros de Stratos-AD venia preguntando como podría implementar un sistema para guardar partidas de su juego en XNA y después volver a cargarlas, no en si el como el organizar los datos e información del juego y como gestionarlo en un archivo si no en si como podría exportar e importar fácilmente información a un archivo en disco. Esto me recordó que yo en su día, cuando empece a programar en .NET, me encontré con el mismo problema y que esto es una duda que a muchos nos surge.

En Visual Basic 6.0, donde estuve muchos años programando, esto era relativamente fácil ya que mediante las funciones integradas para gestionar la escritura y lectura de archivos en formato binario era tan fácil como pasar la propia variable de estructura de datos como parámetro a escribir o leer, Visual Basic 6.0 se encargaba de volcar en formato binario el contenido integro de la variable y despues permitía volver a crear la variable con el mismo contenido. Supongo que en C/C++ sera igual de directo, sin embargo en .NET ya no era tan fácil, no al menos de la misma forma. Aquí no podías llamar a las funciones de System.IO y decirle que te volcara la variable directamente en un archivo. Sin embargo si que existe una manera sencilla de hacer esto, mediante la serialización a XML.

Resumiendo mucho, la serialización a XML es un mecanismo que permite convertir una estructura de datos en una estructura de texto en formato XML. Este mecanismo esta presente en varias tecnologías de programación como .NET o Java. Yo voy a explicar mi ejemplo en .NET mediante C#.

Ejemplo practico

El siguiente código muestra una función para serializar y otra para deserializar contenido de un archivo XML:

XMLSerializer.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace EjemploSerializacion
{
    /// <summary>
    /// Funciones de serialización XML.
    /// </summary>
    public static class XMLSerializer
    {
        /// <summary>
        /// Serializa un objeto a un archivo XML.
        /// </summary>
        /// <param name="data">Objeto a serializar a XML.</param>
        /// <param name="filename">Archivo XML a generar.</param>
        public static void Serialize(object data, string filename)
        {
            XmlSerializer mySerializer = new XmlSerializer(data.GetType());
            StreamWriter myWriter = new StreamWriter(filename);
            mySerializer.Serialize(myWriter, data);
            myWriter.Close();
        }

        /// <summary>
        /// Deserializa un archivo XML a objeto.
        /// </summary>
        /// <typeparam name="T">Tipo del objeto a deserializar.</typeparam>
        /// <param name="filename">Archivo XML a deserializar.</param>
        /// <returns>Devuelve una instancia del objeto serializado en el archivo XML.</returns>
        public static T Deserialize<T>(string filename)
        {
            XmlSerializer mySerializer = new XmlSerializer(typeof(T));
            FileStream myFileStream = new FileStream(filename, FileMode.Open);
            T ret = (T)mySerializer.Deserialize(myFileStream);
            myFileStream.Close();
            return ret;
        }
    }
}

Estas dos funciones permiten exportar como importar la estructura de campos públicos de cualquier tipo de clase en formato XML de forma sencilla y rápida. Veamos un ejemplo. Imaginemos que tenemos una estructura que define estados de nuestro jugador, una estructura tal que a si:

Player.cs
using System;
using System.Collections.Generic;

namespace EjemploSerializacion
{
    public class Player
    {
        public string Name;             // Nombre del jugador.
        public int Life;                // Puntos de vida.
        public int Shield;              // Puntos de escudo.
        public int Xp;                  // Puntos de experiencia.
        public List<Weapon> Weapons;    // Lista de armas y su estado.
    }
}
Weapon.cs
using System;

namespace EjemploSerializacion
{
    public class Weapon
    {
        public string Name;     // Nombre del arma.
        public int Ammo;        // Municion.
        public int Level;       // Nivel del arma.
    }
}

Rellenamos de datos la estructura:

Player player = new Player();
player.Name = "John Deckard";
player.Life = 100;
player.Shield = 25;
player.Xp = 50;
player.Weapons = new List<Weapon>();

Weapon mp5 = new Weapon();
mp5.Name = "H&K MP5";
mp5.Ammo = 32;
mp5.Level = 25;
player.Weapons.Add(mp5);

Weapon desertEagle = new Weapon();
desertEagle.Name = "IMI Desert Eagle";
desertEagle.Ammo = 17;
desertEagle.Level = 13;
player.Weapons.Add(desertEagle);

Weapon knife = new Weapon();
knife.Name = "Knife";
knife.Ammo = 0;
knife.Level = 30;
player.Weapons.Add(knife);

Ahora para exportar esta estructura en un paso a un archivo XML tan solo tendríamos que hacer la siguiente llamada a nuestra clase XMLSerializer:

XMLSerializers.Serialize(player, "playerState.xml");

El resultado del archivo playerState.xml seria el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<Player xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>John Deckard</Name>
  <Life>100</Life>
  <Shield>25</Shield>
  <Xp>50</Xp>
  <Weapons>
    <Weapon>
      <Name>H&amp;K MP5</Name>
      <Ammo>32</Ammo>
      <Level>25</Level>
    </Weapon>
    <Weapon>
      <Name>IMI Desert Eagle</Name>
      <Ammo>17</Ammo>
      <Level>13</Level>
    </Weapon>
    <Weapon>
      <Name>Knife</Name>
      <Ammo>0</Ammo>
      <Level>30</Level>
    </Weapon>
  </Weapons>
</Player>

Como podéis ver el resultado es un formato muy legible y fácil de editar a mano si fuese necesario. Ahora veamos el proceso a la inversa, crear una variable de la clase Player e inicializarla con el contenido del archivo XML que hemos generado:

Player player = XMLSerializers.Deserialize<Player>("playerState.xml");

La variable player se ha inicializado con todo el contenido original del archivo XML.

Como veis, el sistema es muy sencillo de usar e implementar. Es una manera rápida de poder almacenar información útil en archivo y volver a cargarla después para trabajar con ella, puede ser útil por ejemplo a la hora de desarrollar herramientas para edición de niveles, para guardar la configuración del juego, o como en el caso del compañero de Stratos-AD, para crear el mecanismo de salvar y cargar partidas.

Detalles a tener en cuenta de los serializadores

En principio este sistema permite exportar cualquier tipo de dato a XML, pero en verdad no es así. Habra tipos de datos que por unos motivos u otros no se pueda serializar. Sin ir muy lejos, el objeto Dictionary<> no es serializable. Eso plantea un problema si en vez de serializar un array o una lista queremos serializar un diccionario. Pero para todo hay solución. El siguiente código, basado en el original de John Saunders, define una clase que emula un diccionario de .NET pero de forma que sea serializable:

SerializableDictionary.cs
/// Codigo basado en el original de John Saunders de su articulo "One Way to Serialize Dictionaries"
/// http://johnwsaundersiii.spaces.live.com/blog/cns!600A2BE4A82EA0A6!699.entry

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

namespace EjemploSerializacion
{
    /// <summary>
    /// Clase Diccionario que soporta serialización a XML.
    /// </summary>
    /// <typeparam name="K">Tipo de dato de la clave.</typeparam>
    /// <typeparam name="V">Tipo de dato del valor.</typeparam>
    /// <remarks>Los serializadores XML de .NET (el InmediateXMLSerializer de XNA no se ve afectado) no puede serializar clases que implementen la interfaz IDictionary. Esta clase implementa las funcionalidades de un diccionario (IDictionary) pero con capacidad para ser serializado a XML.</remarks>
    public class SerializableDictionary<K, V>
    {
        #region Construction and Initialization
        /// <summary>
        /// Constructor de la clase.
        /// </summary>
        /// <param name="original">Diccionario (IDictionary) que se usara para inicializar la clase.</param>
        public SerializableDictionary(IDictionary<K, V> original)
        {
            this.Dictionary = original;
        }

        /// <summary>
        /// Constructor de la clase.
        /// </summary>
        public SerializableDictionary()
        {
            this.Dictionary = new Dictionary<K,V>();
        }
        #endregion

        #region Interfaz similar a IDictionary
        /// <summary>
        /// Añade un elemento al diccionario.
        /// </summary>
        /// <param name="Key">Clave del elemento.</param>
        /// <param name="Value">Valor del elemento.</param>
        public void Add(K Key, V Value)
        {
            this.Dictionary.Add(Key, Value);
        }

        /// <summary>
        /// Añade un elemento al diccionario.
        /// </summary>
        /// <param name="item">Elemento a añadir.</param>
        public void Add(SerializableKeyValuePair item)
        {
            this.Dictionary.Add(item.ToKeyValuePair());
        }

        /// <summary>
        /// Elimina un elemento del diccionario.
        /// </summary>
        /// <param name="Key">Clave del elemento.</param>
        public void Remove(K Key)
        {
            this.Dictionary.Remove(Key);
        }

        /// <summary>
        /// Elimina un elemento del diccionario.
        /// </summary>
        /// <param name="item">Elemento a eliminar.</param>
        public void Remove(SerializableKeyValuePair item)
        {
            this.Dictionary.Remove(item.ToKeyValuePair());
        }

        /// <summary>
        /// Devuelve la lista de valores del diccionario.
        /// </summary>
        [XmlIgnore]
        public ReadOnlyCollection<V> Values { get { RebuildInternalDictionary(); return this.Dictionary.Values.ToList<V>().AsReadOnly(); } }

        /// <summary>
        /// Devuelve la lista de claves del diccionario.
        /// </summary>
        [XmlIgnore]
        public ReadOnlyCollection<K> Keys { get { RebuildInternalDictionary(); return this.Dictionary.Keys.ToList<K>().AsReadOnly(); } }

        /// <summary>
        /// Propiedad por defecto del diccionario.
        /// </summary>
        /// <param name="key">Clave del elemento.</param>
        /// <returns>Devuelve el elemento del diccionario con la clave indicada.</returns>
        /// <remarks>Esta propiedad permite acceder directamente a la lista de elementos pasando como parametro la clave, de igual forma a como se haria con un Dictionary(IDictionary).</remarks>
        [XmlIgnore]
        public V this[K key] { get { RebuildInternalDictionary(); return this.Dictionary[key]; } }

        /// <summary>
        /// Numero de elementos del diccionario.
        /// </summary>
        [XmlIgnore]
        public int Count { get { RebuildInternalDictionary(); return this.Dictionary.Count; } }
        #endregion

        /// <summary>
        /// Reconstruye el diccionario interno si la lista tiene datos.
        /// </summary>
        /// <remarks>Este metodo se implementa para los casos en que la instancia de la clase ha sido generada a 
        /// partir de deseriaizacion ya que el diccionario interno no se reconstruye automaticamente.</remarks>
        private void RebuildInternalDictionary()
        {
            if (this.Dictionary.Count == 0 && this._list != null && this._list.Count > 0)
            {
                foreach (SerializableKeyValuePair kvp in this._list)
                    this.Dictionary.Add(kvp.ToKeyValuePair());
            }
        }

        #region The Proxy List
    /// <summary>
    /// Acceso al diccionario (IDictionary) interno.
    /// </summary>
    [XmlIgnore]
    public IDictionary<K, V> Dictionary { get; internal set; }

    /// <summary>
    /// Holds the keys and values
    /// </summary>
    public class SerializableKeyValuePair
    {
        public K Key { get; set; }
        public V Value { get; set; }
        /// <summary>
        /// Convertir clase a KeyValuePair.
        /// </summary>
        /// <returns>Devuelve una instancia de KeyValuePair para ser usada con el diccionario (IDictionary) interno de la clase.</returns>
        internal KeyValuePair<K, V> ToKeyValuePair()
        {
            return new KeyValuePair<K, V>(this.Key, this.Value);
        }
    }

    // This field will store the deserialized list
    private Collection<SerializableKeyValuePair> _list; 

    /// <remarks>
    /// XmlElementAttribute is used to prevent extra nesting level. It's
    /// not necessary.
    /// </remarks>
    [XmlElement]
    public Collection<SerializableKeyValuePair> KeysAndValues
    {
        get
        {
            if (_list == null)
            {
                _list = new Collection<SerializableKeyValuePair>();
            }

            // On deserialization, Original will be null, just return what we have
            if (Dictionary == null)
            {
                return _list;
            }

            // If Original was present, add each of its elements to the list
            if (this.Dictionary.Count > 0) _list.Clear();
            foreach (var pair in Dictionary)
            {
                _list.Add(new SerializableKeyValuePair { Key = pair.Key, Value = pair.Value });
            }

            return _list;
        }
    }
    #endregion

        /// <summary>
        /// Convenience method to return a dictionary from this proxy instance
        /// </summary>
        /// <returns></returns>
        public Dictionary<K, V> ToDictionary()
        {
            return KeysAndValues.ToDictionary(key => key.Key, value => value.Value);
        }
    }
}

Veamos un ejemplo de como quedaría serializada esta clase. Vamos cambiar en la clase Player el tipo de dato List al miembro Weapons por SerializableDictionary:

public SerializableDictionary<string, Weapon> Weapons;

El codigo de inicializacion del objeto Player quedaria asi:

Player player = new Player();
player.Name = "John Deckard";
player.Life = 100;
player.Shield = 25;
player.Xp = 50;
player.Weapons = new SerializableDictionary<string, Weapon>();

Weapon mp5 = new Weapon();
mp5.Name = "H&K MP5";
mp5.Ammo = 32;
mp5.Level = 25;
player.Weapons.Add("MP5", mp5);

Weapon desertEagle = new Weapon();
desertEagle.Name = "IMI Desert Eagle";
desertEagle.Ammo = 17;
desertEagle.Level = 13;
player.Weapons.Add("Desert Eagle", desertEagle);

Weapon knife = new Weapon();
knife.Name = "Knife";
knife.Ammo = 0;
knife.Level = 30;
player.Weapons.Add("Knife", knife);

Al exportarlo a XML quedaría así el resultado:

<?xml version="1.0" encoding="utf-8"?>
<Player xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>John Deckard</Name>
  <Life>100</Life>
  <Shield>25</Shield>
  <Xp>50</Xp>
  <Weapons>
    <KeysAndValues>
      <Key>MP5</Key>
      <Value>
        <Name>H&amp;K MP5</Name>
        <Ammo>32</Ammo>
        <Level>25</Level>
      </Value>
    </KeysAndValues>
    <KeysAndValues>
      <Key>Desert Eagle</Key>
      <Value>
        <Name>IMI Desert Eagle</Name>
        <Ammo>17</Ammo>
        <Level>13</Level>
      </Value>
    </KeysAndValues>
    <KeysAndValues>
      <Key>Knife</Key>
      <Value>
        <Name>Knife</Name>
        <Ammo>0</Ammo>
        <Level>30</Level>
      </Value>
    </KeysAndValues>
  </Weapons>
</Player>

¿Se puede usar la serialización XML en XNA y en XBox360?

Desde luego. GreyInfection utiliza el sistema de serialización para la definición de los niveles, el formato de animación de los sprites y tiles, para guardar la configuración del juego y para guardar el progreso del mismo. Lo único que la forma de acceder directamente al archivo no es tan directa como en Windows. En un proyecto de XBox360, obviando el Content Manager, puedes meter recursos en directorio local del juego, aunque no podrás escribir en esta ubicación. En GreyInfection los niveles y esquemas de animación están en una carpeta local. Para leer desde la carpeta local habría usar esta variante de la función de deserialización:

// Añadimos esta linea a la cabecera de la clase:
using Microsoft.Xna.Framework;

/// <summary>
/// Deserializa un archivo XML a objeto.
/// </summary>
/// <typeparam name="T">Tipo del objeto a deserializar.</typeparam>
/// <param name="filename">Archivo XML a deserializar.</param>
/// <returns>Devuelve el contenido del XML en el formato indicado.</returns>
/// <remarks>Esta funcion permite deserializar archivos XML desde el Title Storage.</remarks>
public static T DeserializeFromTitleStorage<T>(string filename)
{
    XmlSerializer mySerializer = new XmlSerializer(typeof(T));
    Stream stream = TitleContainer.OpenStream(filename);
    T ret = (T)mySerializer.Deserialize(stream);
    stream.Close();
    return ret;
}

Supongamos que tenemos una carpeta del proyecto XNA que se llama Levels donde tenemos definidos los niveles de nuestro juego, la forma de cargarlo seria de esta manera:

MiClase level = XMLSerializer.DeserializeFromTitleStorage<MiClase>("Levels/Level0.xml");

Dos cosas a tener en cuenta si usáis este método en vuestros proyectos de XNA:

  • Para que el directorio que añadís y sus archivos se compilen con el proyecto debéis indicar en la propiedad Copiar en el directorio de resultados de cada archivo (se puede hacer en bloque esta operación) el valor Copiar si es posterior ya que de lo contrario al ejecutar el proyecto los archivos no estarían en su ubicación.

  • El formato de las rutas de acceso a los archivos no trabaja con la barra de separación de directorios \ si no con la /.

Para trabajar archivos en modo lectura y escritura, para guardar y poder recuperar datos, debemos trabajar con los StorageDevice de XNA que es el mecanismo que ofrece para trabajar en XBox360 con archivos. Desde los StorageDevices podemos usar las funciones Serialize y Deserialize que usamos en Windows.

Un detalle importante. Desde XNA 4.0 se soportan archivos XML en el Content Manager del proyecto. El XML que generamos con los serializadores no es valido para el Content Manager. XNA usa un mecanismo propio para generar y leer estructuras XML, como el que podéis ver por ejemplo en los archivos de fuentes de texto, SpriteFonts, que agregamos a los proyectos. XNA ofrece una forma muy fácil de cargar XML desde su Content Manager, tan sencilla como cargar cualquier otro tipo de asset desde allí, pero no ofrece mecanismo similar para generar dichos XML. Pues bien, existe una forma de hacerlo mediante XNA pero tiene el inconveniente de que usa una versión del Framework de .NET que no es la que utiliza a la hora de compilar los proyectos y que solo esta disponible para Windows, el IntermediateSerializer.

Este sistema puede llegar a ser muy útil ya que nos permitirá generar archivos XML y cargarlos desde el Content Manager de forma limpia, ademas de que tiene la peculiaridad de que si permite serializar clases como Dictionary<> que los serializadores normales no pueden. La idea de usar este sistema seria para herramientas como editores de contenido del juego, un editor de niveles por ejemplo.

Para poder usar el serializador de XNA crear un proyecto nuevo y desde la pestaña de propiedades cambiar el Framework de .NET de .NET Framework 4 Client Profile a .NET Framework 4. Con esto ya tenemos acceso al siguiente espacio de nombres:

Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

Con esto ya podréis usar el siguiente código para generar archivos XML legibles desde el Content Manager de XNA:

using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

/// <summary>
/// Serializa un objeto a XML de forma que se pueda importar desde el Content Manager de XNA.
/// </summary>
/// <param name="data">Referencia al objeto a serializar.</param>
/// <param name="filename">Nombre del archivo XML que contendra la informacion del objeto serializado.</param>
/// <remarks>Este codigo solo funciona en Windows y con el Target Framework ".NET Framework 4" para permitir el espacio de nombres 
/// Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate (referencia: Microsoft.Xna.Framework.Content.Pipeline).
/// Los objetos a deserializar deben estar declarados en una libreria externa al proyecto del juego y debe ser agregada como 
/// referencia al proyecto de Content.</remarks>
public static void SerializeToXMLContentFormat(object data, string filename)
{
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    using (XmlWriter writer = XmlWriter.Create(filename, settings))
    {
        IntermediateSerializer.Serialize(writer, data, null);
    }
}

Luego la carga del XML desde XNA seria así de sencilla:

Miclase variable = Content.Load<MiClase>("nombre del asset");

Hay que tener en cuenta que para que funcione correctamente los XML que generamos con este método la clase que serializamos ha de estar visible tanto por el proyecto del juego como por el proyecto del Content Manager, eso se soluciona creando un proyecto de biblioteca (una DLL) que añadiremos como referencia a ambos proyectos.

¿Windows Phone permite tambien trabajar con serialización de archivos?

En Windows Phone también podríamos usar la serialización a XML solo que adaptando el código a como trabaja Windows Phone los accesos a archivos, que se realizan mediante los IsolatedStorages de System.IO. El código de las funciones Serialize y Deserialize para Windows Phone quedarían así:

/// <summary>
/// Serializa un objeto a un archivo XML.
/// </summary>
/// <param name="data">Objeto a serializar a XML.</param>
/// <param name="filename">Archivo XML a generar.</param>
public static void Serialize(object data, string filename)
{
    XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
    xmlWriterSettings.Indent = true;

    using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (IsolatedStorageFileStream stream = myIsolatedStorage.OpenFile(filename, FileMode.Create))
        {
            XmlSerializer serializer = new XmlSerializer(data.GetType());
            using (XmlWriter xmlWriter = XmlWriter.Create(stream, xmlWriterSettings))
            {
                serializer.Serialize(xmlWriter, data);
            }
        }
    }
}

/// <summary>
/// Deserializa un archivo XML a objeto.
/// </summary>
/// <typeparam name="T">Tipo del objeto a deserializar.</typeparam>
/// <param name="filename">Archivo XML a deserializar.</param>
/// <returns>Devuelve una instancia del objeto serializado en el archivo XML.</returns>
public static T Deserialize<T>(string filename)
{
    try
    {
        using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (IsolatedStorageFileStream stream = myIsolatedStorage.OpenFile(filename, FileMode.Open))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                return (T)serializer.Deserialize(stream);
            }
        }
    }
    catch
    {
        throw; // Se produjo un error.
    }
}

XML es legible y facil de editar, ¿pero y si necesito que el archivo no sea legible por cualquiera?

No es XML todo lo que se puede hacer con serializadores. También se puede serializar en formato binario mediante BinaryFormatter. A continuación el código de la clase con las funciones para serializar en binario:

BinarySerializer.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace EjemploSerializacion
{
    public static class BinarySerializer
    {
        /// <summary>
        /// Serializa un objeto a un archivo binario.
        /// </summary>
        /// <param name="data">Objeto a serializar.</param>
        /// <param name="filename">Archivo binario a generar.</param>
        public static void Serialize(object data, string filename)
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, data);
            stream.Close();
        }

        /// <summary>
        /// Deserializa un archivo binario a objeto.
        /// </summary>
        /// <typeparam name="T">Tipo del objeto a deserializar.</typeparam>
        /// <param name="filename">Archivo binario a deserializar.</param>
        /// <returns>Devuelve la instancia de la clase serializada.</returns>
        public static T Deserialize<T>(string filename)
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
            T ret = (T)formatter.Deserialize(stream);
            stream.Close();
            return ret;
        }
    }
}

Un detalle muy importante. Para que el serializador binario funcione correctamente, cada clase que queramos serializar habremos de añadirle la clausula [Serializable], de lo contrario el serializador nos devolverá una excepción por que la clase no esta indicada como que es serializable. Esto no sucede con los serializadores XML.

[Serializable]
public class MiClase
{

El serializador binario lo veo útil en Windows, donde cualquiera podría husmear en nuestros archivos que generamos, pero no lo usaría en XBox360 ni en Windows Phone, ya que allí no esta al alcance de cualquiera salvo de nuestra aplicación y con la serialización XML nos bastaría.
Edito: me aclara un compañero, Rodrigo Díaz, que los serializadores binarios no están disponibles en XBox360 ni Windows Phone.

Espero que el articulo haya sido de utilidad.

Salu2…

Enlaces de referencia:
MSDN: Fundamentos de la serialización de .NET Framework
MSDN: What Is Storage? (XNA)
Articulo de Shawn Hargreaves sobre el IntermediateSerializer de XNA
MSDN: Escritura de datos (Windows Phone)

Un pensamiento en “Serialización de datos en .NET: como exportar e importar datos de nuestro juego en archivos fácilmente”

Deja un comentario