Paxnaman, versión final

Septiembre 8, 2009

Finalmente he terminado los exámenes y visto que el juego parece funcionar bien y no he encontrado ningún error llamativo, ni nadie de los que han jugado lo han encontrado, he decidido declarar la última versión como versión final del juego y de paso colgar también el código fuente. Podeis encontrar el ejecutable y el código en esta página.

En fin, terminado este proyecto creo que va siendo hora de retomar el tetris que dejé aparcado meses atrás. Claro que comparado con el pacman, me parece que no va ser demasiado impresionante, jej.


Pacman alfa 0.2

Agosto 28, 2009

Bueno, de momento la única persona que sé que ha probado el juego (gracias Fede, por tu tiempo y dedicación), no ha encontrado ningún fallo especialmente grave. Aun así he corregido un fallito que ocurre cuando te come algún fantasma estando el resto en modo vulnerable, que sale la puntuación como si hubieras comido un fantasma y queda feo algo así. La última versión está colgada aquí. Cualquier problema o pregunta podeis dejar un comentario o aprovechar que he abierto un tema en el foro de stratos para la ocasión. Un saludo a todos.


Pacman en fase alfa

Agosto 27, 2009

Por fin hoy he llegado a cerrar la primera fase del desarrollo del pacman que estoy haciendo en xna y ahora está en fase alfa, tendré que probarlo para ver que fallos me podría dar y tratar de corregirlo. Luego cuando esté en beta publicaré también el código fuente y trataré de dejar el tema lo suficientemente zanjado como para olvidarme de él una larga temporada.

Para poder ejecutarlo hace falta tener el .NET framework 3.5, que os lo podeis bajar de la página oficial y también hace falta tener el XNA framework 3.0, que os lo podeis bajar de la página oficial o directamente bajar el paquete mínimo. Para finalizar el juego está colgado en esta dirección de momento.

En fin, espero que os sea leve si os poneis a jugar un rato y en caso de que querrais comentar cualquier cosa podeis dejar un comentario en esta entrada. Un saludo.


El desarrollo de un pacman un mes después…

Agosto 25, 2009

Hace un més comentaba que estaba empezando a hacer un clon del pacman, como ejercicio de auto-realización y de mejora de mi propio currículum como desarrollador de juegos. El caso es que pensé que teniendo la versión original, para aprender mientras jugaba como funcionaba este, podría hacer un clon 100% fiel… Pero poco a poco iban surgiendo pequeños problemitas, que me iban dejando claro que había metido la gamba hasta el fondo. Al final un día buscando orientación para realizar la IA de los fantasmas me encontré con este dossier.

Descubrí porque tardaron 18 meses para hacer este juego, y no fue solo que programarlo en ensamblador sea un puto infierno… No, eso habría sido bastante lamentable. El caso es que cada fase tiene unos parametros que van cambiando y yo lo había enfocado más o menos como si fuera todo el rato igual. Para cuando me quise dar cuenta, vi que tendría que hacer demasiados cambios y no era algo que me apeteciera. Así que opté por la rama más fácil y conformarme con hacer un clon cutre, que aunque no fuera 100% fiel al original, al menos diera un poco el pego como juego en 2D.

Porque al final lo importante de hacer esto es todo lo que he ido aprendiendo y lo que me queda por aprender, ya que lo único que me falta por implementar para dar por terminado el juego, es la IA de los fantasmas. Podría haberme puesto a añadir las escenas “intermedias” entre algunas fases, pero me parece que es curro de más, que no va a aportarme nada interesante a estas alturas, ya que mis principales metas para con este juego se habrán alcanzado de sobras cuando la IA esté terminada. Una vez terminado, veré si puedo colgarlo por aquí en mi web y si tengo tiempo a ver si puedo escribir un pequeño post-mortem sobre mi experiencia con ello.


Haciendo un pacman en xna

Junio 24, 2009

Como desarrollador de videojuegos con cierta experiencia siempre he sentido un vacío en mi interior, ya que en mi lista de juegos realizados ninguno tiene animaciones, por mi falta de competencia a la hora de obtener gráficos animados, pues yo soy programador y el dibujo por ordenador no es mi fuerte francamente. Así que hace como 2 años me propuse hacer el pac-man bajo XNA para luego dar una charla sobre la experiencia. La idea de tomar el pac-man se debe a que es un juego relativamente sencillo en cuanto a sus animaciones. Sin embargo mis estudios me dejaron sin tiempo para esta clase de aficiones, por lo que el proyecto nunca llegó a buen puerto.

Así que en mayo por culpa de la cercanía de los examenes y porque meses atrás me había puesto a recolectar imagenes de la que sacar ideas para realizar los gráficos, me puse finalmente manos a la obra y logré sacar por completo el tema de los recursos relacionados con la fuente, tiles y sprites del juego. Y poco a poco fui programando un poco al tuntún cosas para primero lograr pintar el laberinto y luego tener al jugador pudiendose mover por este. Y eso logré antes de los examenes, hasta que ya fue inevitable que tuviera que ponerme con lo otro.

Y hace 2 días, ahora que ya han terminado los exámenes, me puse a mirar el código que llevaba, el cual por cierto apenas había comentado porque casi siempre dejo eso para el final y por ello me he comido mi propio marrón con patatas. No obstante ya me estoy haciendo a la idea de cual es la arquitectura farragosa del juego y justo hoy terminé de modificarlo para que el tunel que comunica ambos lados de la pantalla funcione como debería. Ahora lo siguiente es ponerme a programar el tema de las colisiones para los puntos y empezar a plantearme como hacer la IA de los fantasmitas.

Al principio pensé que el juego sería relativamente fácil, pero no paran de surgir pequeños detallitos apenas apreciables como por ejemplo que el jugador al comer los puntos se frena un poquito y por eso puede huir de los fantasmas en los pasillos vacios. Además no se como voy a poder afrontar el comportamiento de los fantasmas, que no parece un tema trivial precisamente. Sea como fuere, todavía me queda un buen camino por delante y aunque tampoco estoy dando un uso muy intenso a XNA de momento no me siento especialmente incomodo con esta API, aunque supongo que podría estar mejor el tema.


Extendiendo Small Basic en C#

Mayo 19, 2009

Bueno y he aquí uno de los puntos más interesantes de Small Basic al pertenecer al .NET Framework, que podemos extender la API con nuevas funcionalidades desde otros lenguajes de la misma plataforma. Para el artículo he elegido C#, que es el que a mi más me gusta de todo .NET, ya que no tiene una sintaxis abigarrada como Visual Basic, por ejemplo.

Así que poniéndonos manos a la obra, lo primero es tener el Visual Studio 2008 o el Visual C# 2008 Express, cualquiera de los dos nos valdrá. Es posible que también funcione con las versiones del 2005, pero ya que Small Basic utiliza el .net framework 3.5, lo suyo sería utilizar como mínimo el Visual C# 2008 Express, que además es gratuito y no cuesta demasiado bajárselo uno mismo. Así que abrimos el Visual C# y seguimos los siguientes pasos:

  1. Creamos un nuevo proyecto de tipo “Biblioteca de clases“.
  2. Añadimos como referencia el fichero “SmallBasicLibrary.dll” que se encuentra dentro de la carpeta de Small Basic.
  3. Creamos una nueva clase estática:
    [SmallBasicType]
    public static class MiExtension { /* … */ }
  4. Añadimos con using en el código la librería: Microsoft.SmallBasic.Library
  5. Configuramos las propiedades del proyecto para generar la documentación en XML: Propiedades -> Generar -> Archivo de documentación XML.
  6. Generamos la solución entera y copiamos la dll y el xml generados en la carpeta release de nuestro proyecto, a la carpeta lib del directorio de Small Basic (“C:\Archivos de programa\Microsoft\Small Basic\lib”). Hay que tener en cuenta que por defecto no existe la carpeta lib, por lo que hay que crearla de forma manual.

Y con estos pasos ya podemos empezar a trabajar para crear una extensión. Pero hay que tener en cuenta algunas cosas como que no está soportada la sobrecarga de funciones, por lo que no podemos tener dos funciones con el mismo nombre aunque reciban distintos parámetros. Todas las variables responden al tipo Primitive de la API interna del lenguaje, por lo que no podemos trabajar directamente con tipos de datos como int o string. Sabiendo esto aquí tenemos un esquema básico de como sería una extensión y sus tres diferentes tipos de elementos:

using Microsoft.SmallBasic.Library;
namespace MisExtensiones {
  [SmallBasicType]
  public static class MiExtension {
    // Para definir variables:
    public static Primitive Propiedad=new Primitive();

    // Para definir métodos:
    public static void Metodo1() {
      // Código del método...
    }
    public static void Metodo2(Primitive param) {
      // Código del método...
    }
    public static Primitive Metodo3() {
      // Código del método...
    }
    public static Primitive Metodo4(Primitive param) {
      // Código del método...
    }

    // Para definir manejadores de eventos:
    public static event SmallBasicCallback Evento=null;
  }
}

De esta forma al generar la dll del proyecto y añadirla al directorio de librerías de Small Basic, tendremos una nueva librería llamada MiExtension, con en este caso una propiedad, un manejador de eventos y cuatro funciones. Las posibilidades de esto son por decirlo de algún modo infinitas. Podemos añadir todo lo que queramos al lenguaje, teniendo en cuenta que no podemos repetir identificadores ya que ello confundiría a Small Basic. Por cierto que en este caso para la variable no hemos usado una propiedad de C#, pero podríamos haberla usado perfectamente y con ello podríamos limitar su escritura para hacerla solo de lectura, ya que hay cosas que no tiene sentido que el usuario las cambie de valor.

Con esto ya está de momento cubierto el lenguaje y la mayoría de sus puntos, por lo que a partir de aquí, hasta que salga una nueva versión de Small Basic, me pondré a hacer alguna que otra extensión y/o juego que colgaré en esta web cuando lo tenga listo y terminado. Aunque de momento hasta que termine junio, estaré con los exámenes para terminar la carrera y ello me va a tener bastante liado, por lo que seguramente no haya mucho movimiento por aquí hasta la última semana de junio. Así que a estudiar, jej.


Manejando ficheros excel desde C#

Abril 27, 2009

En las últimas semanas he estado trabajando con C# y una de las cosas que he tenido que hacer fue manejar ficheros Excel. Hay dos formas para trabajar con ellos, usando el interop de Microsoft o tratando el fichero como una base de datos con ADO.NET, el problema es que con lo último no podemos borrar cosas, con lo que nos encontramos con un problema un tanto estrambótico. Así que para hacer lo que yo quería hacer tuve a mi pesar que usar interop.

Lo primero que hay que hacer es añadir la referencia a la librería COM “Microsoft Excel 11.0 Object Library”, que corresponde más o menos al Office 2003. Una vez hemos configurado nuestro proyecto para poder invocar la aplicación Excel desde nuestro programa el código es el siguiente:

// A library to handle excel files in a simple way.
// Copyright (C) 2009  Gorka Suárez García
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see .
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Excel;

namespace Excel {
    /// <summary>
    /// This class is used to handle an excel file to write and read from it.
    /// Author: Gorka Suárez García
    /// </summary>
    public class ExcelHandler {
        /// <summary>
        /// The excel application instance.
        /// </summary>
        private ApplicationClass app;

        /// <summary>
        /// The excel book.
        /// </summary>
        private Workbook book;

        /// <summary>
        /// The path of the excel file.
        /// </summary>
        private string path;

        /// <summary>
        /// Constructs a new ExcelHandler object.
        /// </summary>
        public ExcelHandler() {
            this.app = null;
            this.book = null;
            this.path = null;
        }

        /// <summary>
        /// Destroys the ExcelHandler object.
        /// </summary>
        ~ExcelHandler() {
            if (this.app != null) {
                this.app.Quit();
            }
        }

        /// <summary>
        /// Opens an excel file.
        /// </summary>
        /// <param name="path">The file to open.</param>
        public void Open(string path) {
            this.path = path;

            this.app = new ApplicationClass();
            this.app.Visible = false;
            this.app.ScreenUpdating = false;
            this.app.DisplayAlerts = false;

            this.book = this.app.Workbooks.Open(this.path, Missing.Value, Missing.Value, Missing.Value,
                                                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                                                Missing.Value, Missing.Value, Missing.Value, Missing.Value,
                                                Missing.Value, Missing.Value, Missing.Value);

            if (this.book == null)
                throw new Exception("Can't open the excel book file.");
        }

        /// <summary>
        /// Writes a value in a cell.
        /// </summary>
        /// <param name="sheet">The sheet to write.</param>
        /// <param name="cell">The cell to write.</param>
        /// <param name="value">The value to write.</param>
        public void Write(string sheet, string cell, string value) {
            Worksheet wsheet = this.getSheet(sheet);
            Range range = wsheet.get_Range(cell, cell);
            range.Value2 = value;
        }

        /// <summary>
        /// Reads a value from a cell.
        /// </summary>
        /// <param name="sheet">The sheet to read.</param>
        /// <param name="cell">The cell to read.</param>
        /// <returns>The value from the cell.</returns>
        public string Read(string sheet, string cell) {
            Worksheet wsheet = this.getSheet(sheet);
            Range range = wsheet.get_Range(cell, cell);

            if (range.Value2 != null)
                return range.Value2.ToString();
            else
                return "";
        }

        /// <summary>
        /// Clears the content of the excel book.
        /// </summary>
        public void Clear() {
            Worksheet sheet = null;
            for (int i = 1; i <= this.book.Worksheets.Count; i++) {
                sheet = (Worksheet)this.book.Worksheets[i];
                sheet.Cells.Clear();
            }
        }

        /// <summary>
        /// Closes the excel file.
        /// </summary>
        public void Close() {
            this.book.SaveAs(this.path, XlFileFormat.xlWorkbookNormal, Missing.Value, Missing.Value,
                             false, false, XlSaveAsAccessMode.xlShared, false, false, Missing.Value,
                             Missing.Value, Missing.Value);
            this.book.Close(true, Missing.Value, Missing.Value);
            this.app.Quit();

            this.app = null;
            this.book = null;
            this.path = null;
        }

        /// <summary>
        /// Gets all the names of the sheets inside the excel book.
        /// </summary>
        /// <returns>A list of the sheets names.</returns>
        public string[] GetSheetsNames() {
            List names = new List();
            Worksheet sheet = null;

            for (int i = 1; i <= this.book.Worksheets.Count; i++) {
                sheet = (Worksheet)this.book.Worksheets[i];
                names.Add(sheet.Name);
            }

            return names.ToArray();
        }

        /// <summary>
        /// Gets a sheet we're looking for.
        /// </summary>
        /// <param name="name">The name of the sheet.</param>
        /// <returns>The sheet we're looking for.</returns>
        protected Worksheet getSheet(string name) {
            int index = this.getSheetIndex(name);
            if (index == 0)
                throw new Exception("Invalid sheet name.");

            Worksheet sheet = (Worksheet)this.book.Worksheets[index];
            return sheet;
        }

        /// <summary>
        /// Gets the index of a sheet we're looking for.
        /// </summary>
        /// <param name="name">The name of the sheet.</param>
        /// <returns>The index of the sheet we're looking for.</returns>
        protected int getSheetIndex(string name) {
            Worksheet sheet = null;
            for (int i = 1; i <= this.book.Worksheets.Count; i++) {
                sheet = (Worksheet)this.book.Worksheets[i];
                if (sheet.Name == name) return i;
            }
            return 0;
        }
    }
}

El objeto de app de tipo ApplicationClass es una instancia del programa Excel, por lo que si vamos al administrador de tareas mientras se ejecuta nuestra aplicación, veremos que “Excel.exe” está ejecutándose. Luego el objeto de tipo Workbook es una instancia del libro contenido en un fichero de tipo excel. Y por último path es simplemente la ruta del fichero, que la guardaremos para salvar los cambios al cerrar el fichero. Así que teniendo esto en cuenta para abrir un documento excel lo haríamos así:

public void Open(string path) {
  // Asignamos la ruta del fichero.
  this.path = path;
  // Instanciamos la aplicación excel.
  this.app = new ApplicationClass();
  // Indicamos que no será visible,
  this.app.Visible = false;
  // que no se va a actualizar la pantalla
  this.app.ScreenUpdating = false;
  // y que no va a mostrar los mensajes de alerta.
  this.app.DisplayAlerts = false;

  // Abrimos un libro excel pasándole la ruta y el
  // resto de valores los dejamos sin asignar.
  this.book = this.app.Workbooks.Open(this.path,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value);

  // Si el libro no se ha abierto lanzamos una excepción.
  if (this.book == null)
    throw new Exception("Can't open the excel book file.");
}

Para poder escribir algo en el fichero, tendremos que indicar la hoja y la celda como cadenas. El valor también será una cadena, sin embargo a efectos internos del fichero, será tratado como un número, un texto o una fórmula si encaja con el formato que excel considera apropiado para esa clase de datos. Por ello podemos asignar a una celda una fórmula siempre que cumpla el formato, para que excel lo detecte.

public void Write(string sheet, string cell,
                  string value)
{
  // Obtenemos la hoja del libro.
  Worksheet wsheet = this.getSheet(sheet);
  // Obtenemos la celda.
  Range range = wsheet.get_Range(cell, cell);
  // Asignamos el valor a la celda.
  range.Value2 = value;
}

Para obtener una celda tenemos que utilizar una función que nos devuelve un conjunto de celdas determinado por un rango de direcciones. Dentro de esta clase, la propiedad Value2 contiene o bien un array de celdas o una celda simplemente si solo hemos pedido un “conjunto” de una celda. De modo similar para leer tendríamos lo siguiente.

public string Read(string sheet, string cell)
{
  // Obtenemos la hoja del libro.
  Worksheet wsheet = this.getSheet(sheet);
  // Obtenemos la celda.
  Range range = wsheet.get_Range(cell, cell);

  // Devolvemos el valor de la celda.
  if (range.Value2 != null)
    return range.Value2.ToString();
  else
    return "";
}

Hay que tener en cuenta que si una celda no está inicializada o no tiene valor, nos devolvería un null, con lo que en este caso devolvemos una cadena vacía como representación de eso. Ahora que sabemos leer y escribir, lo suyo es saber salvar y cerrar el fichero.

public void Close() {
  // Salvamos el contenido del libro.
  this.book.SaveAs(this.path,
    XlFileFormat.xlWorkbookNormal, Missing.Value,
    Missing.Value, false, false,
    XlSaveAsAccessMode.xlShared, false, false,
    Missing.Value, Missing.Value, Missing.Value);
  // Cerramos el libro.
  this.book.Close(true, Missing.Value, Missing.Value);
  // Y salimos de la aplicación excel.
  this.app.Quit();

  this.app = null;
  this.book = null;
  this.path = null;
}

Otras operaciones varias en el código de la clase son:

  • Clear: Que borra todas las hojas del fichero.
  • GetSheetsNames: Que devuelve un array con los nombres de las hojas del fichero.
  • getSheet: Que busca una hoja en base a su nombre y la devuelve.
  • getSheetIndex: Que devuelve en qué índice está una hoja en base a su nombre.

Para hacer esas operaciones hay que tener en cuenta cosas como que en Worksheet tenemos la propiedad Cells, que tiene el método Clear() para borrar el contenido de una hoja. Que en Workbook tenemos la propiedad Worksheets que es un array con todas las hojas del libro excel, y como tal array tiene la propiedad Count para saber el número de hojas del libro. Sin embargo dicho array va desde 1 hasta Count incluido. Y por último dentro de Worksheet tenemos la propiedad Name, con el nombre de la hoja.

Y esto es lo más básico para trabajar con fichero Excel usando interop desde .NET, se pueden hacer más cosas pero aun así hay algunos problemillas de esta API, como que no siempre se cierra bien la aplicación o que a veces tarda un buen rato en ser cerrada, o que si salimos de forma abrupta de nuestra aplicación, se puede quedar el programa Excel abierto en el limbo. Pero peor sería hacerlo todo desde cero, eso seguro.


Serialización a XML en C#

Abril 23, 2009

Bueno, hace unas cuantas semanas que no pongo nada en el weblog básicamente porque estoy bastante liado últimamente. El caso es que actualmente estoy trabajando como becario para la universidad y en una de las cosas que estoy haciendo estoy programando en C#. Así que llegó un momento en el programa que tenía que guardar las cosas a XML y recordé que tenía, en el anterior blog que hice, un ejemplo de trabajar con el tema de la serialización en XML. Pero como el blog lo borré, tuve que buscar el proyecto que lo tenía guardado por mi disco duro. Finalmente tras trastear un poco descubrí algunas cosas nuevas que resultaron interesantes.

Lo primero que tenemos que hacer una vez tenemos nuestra estructura de datos, en este caso particular simboliza una supuesta persona y sus datos, tenemos que indicar que se puede serializar. Poniendo simplemente [Serializable] delante de la definición de la clase ya podríamos guardar y cargar la clase en un fichero, sin embargo existe una forma donde podemos tener más control:

using System;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

[XmlRootAttribute("MiClase", Namespace = "", IsNullable = false)]
class MiClase { /* ... */ }

Con XmlRootAttribute podemos indicar como se va a llamar la etiqueta que en XML simbolizará nuestro objeto guardado. El caso es que simplemente poniendo esto ya podemos serializar objetos a XML, pero por defecto tomará como datos para guardar y cargar todos aquellos campos públicos y propiedades públicas que tengan tanto un get como un set implementado. ¿Pero qué pasa si no queremos que se guarden todos los datos públicos? Pues que tenemos herramientas para ello, pues tan solo hay que poner [XmlIgnoreAttribute()] delante de la definición del dato público para evitar que se maneje en la serialización. Luego si por la razón que sea queremos que la etiqueta se llame de otra forma tenemos para datos simple la marca [XmlElementAttribute("NombreDato")] y para arrays tenemos [XmlArray("NombreArray"), XmlArrayItem("NombreElemento", typeof(int))], en este caso sería un array de enteros. Dicho esto pasaremos a verlo en un ejemplo, en el que he puesto los campos todos como públicos en vez de usar propiedades para que sea más pequeño, pero si en vez de ser públicos los hubiéramos puesto como private y creado sus respectivas propiedades, habrían funcionado igual:

[XmlRootAttribute("person", Namespace = "", IsNullable = false)]
public class Persona {
    [XmlElementAttribute("name")]
    public string Nombre;

    [XmlElementAttribute("lastname")]
    public string Apellidos;

    [XmlIgnoreAttribute()]
    public int Auxiliar;

    [XmlArray("phones"), XmlArrayItem("number", typeof(int))]
    public int[] Telefonos;

    [XmlElementAttribute("biometrics")]
    public DatosBiometricos Datos;

    private void init(string nombre, string apellidos) {
        this.Nombre = nombre;
        this.Apellidos = apellidos;
        this.Auxiliar = 0;
        this.Telefonos = null;
        this.Datos = new DatosBiometricos();
    }

    public Persona() {
        this.init("", "");
    }

    public Persona(string nombre, string apellidos) {
        this.init(nombre, apellidos);
    }
}

public class DatosBiometricos {
    [XmlElementAttribute("weight")]
    public int Peso;

    public DatosBiometricos() {
        this.Peso = 0;
    }
}

Una vez configurado que vamos a guardar y que no, nos creamos una función para salvar los datos en un fichero, en este caso será una función estática que llamaremos desde un código de ejemplo en el Main del programa. Básicamente lo que hace salvar es crear un objeto serializador, otro para escribir texto en un fichero, configurar como vamos a escribir en el fichero, guardar los datos y cerrar el fichero.

public static void Salvar(string ruta, Persona objeto) {
    //----------------------------------------------------------------------
    // Primero creamos el serializador que va a generar el XML a partir de
    // la clase que le hemos pasado. Después creamos el stream de salida
    // de nuestro programa, que escribirá el fichero.
    //----------------------------------------------------------------------
    XmlSerializer serializer = new XmlSerializer(typeof(Persona));
    XmlTextWriter stream = new XmlTextWriter(ruta, Encoding.UTF8);

    //----------------------------------------------------------------------
    // A fin de permitir una mejor claridad si lo abrimos desde el bloc de
    // notas, le indicaremos al stream, que emplee un estilo de formato
    // identado con tabulaciones.
    //----------------------------------------------------------------------
    stream.Formatting = Formatting.Indented;
    stream.Indentation = 1;
    stream.IndentChar = 't';

    //----------------------------------------------------------------------
    // Finalmente mandamos que cree el XML y se cierre el stream.
    //----------------------------------------------------------------------
    serializer.Serialize(stream, objeto);
    stream.Close();
}

Y cargar será parecido, ya que tenemos que crear también el objeto serializador y otro para leer del fichero. Creado el objeto lector, leemos el objeto almacenado en el fichero, cerramos el lector y devolvemos este nuevo objeto cargado desde el fichero XML.

public static Persona Cargar(string ruta) {
    //----------------------------------------------------------------------
    // Primero creamos el serializador que va a interpretar el XML, para
    // poder crear una clase a partir de este fichero. Después creamos el
    // stream de entrada a nuestro programa, que leerá el fichero.
    //----------------------------------------------------------------------
    XmlSerializer serializer = new XmlSerializer(typeof(Persona));
    XmlTextReader stream = new XmlTextReader(ruta);

    //----------------------------------------------------------------------
    // Creado el serializador y el stream, mandamos transformar el
    // contenido que vamos leyendo del stream, a un objeto del programa.
    //----------------------------------------------------------------------
    Persona aux = (Persona)serializer.Deserialize(stream);

    //----------------------------------------------------------------------
    // Cerramos el stream y devolvemos el objeto que hemos obtenido.
    //----------------------------------------------------------------------
    stream.Close();
    return aux;
}

Y por último aquí está el código de ejemplo en el que se crea una instancia de la clase, se muestra por pantalla, se guarda, se carga y se vuelve a mostrar por pantalla el objeto obtenido desde la carga:

public class Program {
    public static void Main(string[] args) {
        Persona p1 = new Persona("Alexander", "Lessman");
        p1.Telefonos = new int[] { 1, 2, 3 };
        p1.Datos.Peso = 80;
        Persona p2 = null;

        Console.WriteLine("Contenido de la primera persona:");
        MostrarPersona(p1);

        Console.WriteLine("Salvamos en un XML el contenido de la primera persona...n");
        Program.Salvar("persona.xml", p1);

        Console.WriteLine("Cargamos el contenido del XML en la segunda persona...n");
        p2 = Program.Cargar("persona.xml");

        Console.WriteLine("Contenido de la segunda persona:");
        MostrarPersona(p2);
    }

    public static void MostrarPersona(Persona p) {
        Console.WriteLine(" + Nombre:    " + p.Nombre);
        Console.WriteLine(" + Apellidos: " + p.Apellidos);
        Console.WriteLine(" + Peso:      " + p.Datos.Peso);
        Console.WriteLine(" + Telefonos: ");
        foreach (int telf in p.Telefonos)
        {
            Console.WriteLine("   -> " + telf.ToString());
        }
        Console.WriteLine();
    }

    // Método para salvar a un XML...

    // Método para cargar desde un XML...
}

Por último un pequeño aviso. Hay que tener en cuenta que no todos los objetos del .Net Framework pueden serializarse a XML, mismamente yo tuve problemas con LinkedList, así que tuve que crear una propiedad “fantasma” en la que se trabajaba con un array de T.

En fin, que esto es más o menos lo básico para poder serializar objetos a XML desde C#, espero que os pueda ser de ayuda algún día. El ejemplo completo os lo podéis bajar de aquí.