[XNA] Conectando a un servicio web desde XNA 4.0 con Windows Phone 7

Conectarnos a un servicio web desde nuestros juegos puede ser útil, por ejemplo para recoger información de cuanta gente juega, desde donde, cuando… entre otras cosas que se nos puedan ocurrir. Lo malo es que desde XNA, en Windows Phone, no podemos utilizar referencias web. Bueno, de hecho sí se podría… añadiendo una DLL de Silverlight a la solución, añadirle la referencia al servicio, referenciar este proyecto desde el juego y instanciar el proxy del webservice… pero da un warning muy feo, y no se si este pequeño «hack» tendrá cabida en el Marketplace. Así que la forma elegante de hacerlo es mediante los webrequest de toda la vida.

Para empezar la prueba lo primero que necesitaremos es un webservice al que conectarnos. Para ello yo me he creado un WebService de toda la vida con VS2008 y lo he colgado en mi IIS local, de manera que esta es la URL de mi servicio: http://localhost/service/service1.asmx. El servicio contiene el método HelloWorld que viene creado por defecto en cualquier servicio que hagamos con Visual Studio, que devuelve un simple string: «Hola mundo!!!». Asumiendo que todos sabemos crear un webservice como este, vamos a ver como recuperar sus datos desde el teléfono.

En el método Initialize de cualquier proyecto XNA, en la clase Game, instanciaremos un objeto HttpWebRequest y haremos un post. Observad que el post se realiza asíncronamente con BeginGetResponse, y es que en XNA para Phone no hay otra… no lo podemos hacer síncronamente. En esta llamada asíncrona se pasan como parámetros primero Response_Completed, que es el método que se llamará cuando termine el Response, y el objeto httpWebRequest, que es el que queremos recuperar en este método.

        protected override void Initialize()
        {
            string webServiceAddress = @»http://localhost/service/service1.asmx«;           
            string methodName = «HelloWorld»;

            string webServiceMethodUri = string.Format(«{0}/{1}», webServiceAddress, methodName);

            HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(webServiceMethodUri);
            httpWebRequest.Method = «POST»;

            httpWebRequest.BeginGetResponse(Response_Completed, httpWebRequest);
           
            base.Initialize();
        }

 Y esta sería la implementación del método Response_Completed. lo más interesante es que tenemos que parsear el XML resultante. Esto podría hacerse mediante una serialización / deserialización de XML en objetos, y nos ahorraríamos tener que hacer el parseo a mano, pero como ejemplo sencillo esto nos vale.

       void Response_Completed(IAsyncResult result)
        {
            HttpWebRequest request = (HttpWebRequest)result.AsyncState;
            HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);

            using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
            {
                string xml = streamReader.ReadToEnd();

                using(XmlReader reader = XmlReader.Create(new StringReader(xml)))
                {
                    reader.MoveToContent();
                    reader.GetAttribute(0);
                    reader.MoveToContent();

                    this.mensaje = reader.ReadInnerXml();
                }
            }
        }

Y ya sólo queda pintar el mensaje que hemos obtenido en pantalla… este código es trivial:

       protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            spriteBatch.DrawString(fuente, this.mensaje, new Vector2(30.0f, 40.0f), Color.Black);
            spriteBatch.End();

            base.Draw(gameTime);
        }

 El resultado de la ejecución sería el siguiente:

[XNA] Localización de videojuegos en XNA (multiidioma)

Si haces un juego con el objetivo de venderlo, en un mercado global como es el de hoy día… es imprescindible que tu videojuego sea multiidioma. Cuantos más idiomas tenga, por supuesto tanto mejor, y aunque se supone que todos somos capaces de entender el inglés, siempre se hace más cómodo jugar con el idioma nativo de uno: el cliente lo agradece! Como se verá en este tutorial, no es una tarea complicada. Una vez la aplicación está preparada para ser localizada a un idioma, es muy fácil añadir muchos más idiomas, consistiendo la única dificultad a partir de ese momento en traducir las cadenas de texto.

Este vídeo refleja el resultado de la ejecución del código que os podéis descargar con este ejemplo, se muestran textos y una foto en Castellano, Inglés y Catalán. La foto refleja claramente la cultura de cada país: Bocata calamares, Hamburguesa y Calçots respectivamente.

En cualquier aplicación .Net la localización se hace de forma muy parecida, y XNA no iba a ser menos. La traducción de los textos se basa en ficheros de recursos, de extensión “resx”. Para añadir uno de estos documentos iremos al proyecto –> Añadir -> Nuevo elemento –> Visual C# –> Fichero de recursos.

Al fichero le podemos poner el nombre que queramos, en mi caso le he puesto “Textos.resx”, y este será el fichero que contenga los textos del idioma por defecto del juego, ejemplo:

Textos

Una vez tengamos todos los textos en un idioma, necesitaremos crear nuevos ficheros Resx, uno para cada uno de los idiomas a los que queramos traducir la aplicación. En mi caso la he traducido al inglés y al catalán, así que añado los ficheros:

  • Textos.en.resx
  • Textos.ca.resx

Como ya habréis deducido, es necesario traducir sólo la columna “Value” en el fichero RESX, no así la columna “Name”, que hace la función de clave de acceso al recurso. En el fragmento de código podéis ver como se establece la cultura por defecto a español, al inicializar el juego. Podríamos inicializarla también al idioma que tenga el jugador… y eso es lo ideal, así no tenemos que preguntarle que idioma quiere, la cultura del jugador la obtenemos con CultureInfo.CurrentCulture (puede observarse comentado en el siguiente código):

   1: protected override void Initialize()

   2: {

   3:     font = Content.Load<SpriteFont>("font");

   4:  

   5:     this.EstablecerCultura("es");

   6:  

   7:     //CultureInfo.CurrentCulture.ToString();

   8:  

   9:     base.Initialize();

  10: }

El método “EstablecerCultura” no es más que un método propio, un helper que cambia la cultura que referenciaremos de los ficheros de recursos, así como cargar la foto en la cultura que toca.

   1: private void EstablecerCultura(string cultura)

   2: {

   3:     Textos.Culture = CultureInfo.GetCultureInfo(cultura);

   4:  

   5:     foto = Content.Load<Texture2D>("foto-" + cultura);

   6: }

Y ya casi estamos! Ahora sólo falta cambiar la cultura bajo petición del usuario, eso lo hago en el método Update():

   1: protected override void Update(GameTime gameTime)

   2: {

   3:     // Allows the game to exit

   4:     if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)

   5:         this.Exit();

   6:  

   7:     KeyboardState kbState = Keyboard.GetState();

   8:  

   9:     if (kbState.IsKeyDown(Keys.NumPad1))

  10:         this.EstablecerCultura("en");

  11:     else if (kbState.IsKeyDown(Keys.NumPad2))

  12:         this.EstablecerCultura("es");

  13:     else if (kbState.IsKeyDown(Keys.NumPad3))

  14:         this.EstablecerCultura("ca");

  15:  

  16:     this.titulo = String.Format(Textos.Titulo, Textos.Culture);

  17:     this.descripcion = Textos.Descripcion;

  18:     this.opciones = Textos.Opciones;

  19:  

  20:     base.Update(gameTime);

  21: }

Y finalmente lo dibujamos todo en pantalla… lo cual genera el resultado que podéis ver en el vídeo.

   1: protected override void Draw(GameTime gameTime)

   2: {

   3:     GraphicsDevice.Clear(Color.Black);

   4:  

   5:     spriteBatch.Begin();

   6:     spriteBatch.DrawString(font, titulo, new Vector2(10, 10), Color.Green);

   7:     spriteBatch.DrawString(font, descripcion, new Vector2(10, 40), Color.Green);

   8:     spriteBatch.DrawString(font, opciones, new Vector2(10, 70), Color.Green);

   9:     spriteBatch.Draw(foto, new Vector2(10, 120), Color.White);

  10:     spriteBatch.End();

  11:  

  12:     base.Draw(gameTime);

  13: }

 Quien quiera ir más allá, y necesite localizar sus juegos a idiomas que requieren gran cantidad de carácteres, con diccionarios distintos al nuestro (como japonés, coreano, chino, árabe…), echad un vistazo al ejemplo disponible en XNA Creators club respecto localización, donde además de las técnicas que os comento aquí hay un ejemplo de utilización del Content Pipeline para cargar sólo los carácteres que cada idioma requiere.

Como siempre podéis descargar el código fuente del ejemplo. Y ahora a jugar! (mmm esto me ha recordado al “precio justo” xD)