Jesús Bosch

XNA, programación gráfica y desarrollo de videojuegos por un Microsoft Student Partner

August 2009 - Artículos

[IA] Un Super Mario muy inteligente

Mario AI Competition 2009 es un concurso consiste en programar el mejor agente (o mario basado en IA) de una versión del popular Super Mario Bros, concretamente, el Infinte Mario Bros, desarrollado en Java.

Os dejo el vídeo de uno de los participantes (ha utilizado el algoritmo A* Pathfinding y alrededor de 40 horas en la implementación y pruebas). Impresiona ver el nivelazo de este Mario...

Posted: 29/8/2009 10:37 por Jesús Bosch | con 5 comment(s)
Archivado en: ,
[XNA] Reproducción de vídeo en XNA 3.1

La reproducción de vídeo es una de las novedades de XNA 3.1. La verdad es que me ha sorprendido lo fácil que resulta su implementación... me ha dado más problemas el formato wmv que la própia codificación. Al añadir un wmv al Content y compilar, aparecía el error siguiente: "Please make sure that the video is not DRM protected and is a valid single-pass CBR encoded video file". Para solucionar el problema, he bajado el Windows Media Encoder (descarga gratuita). Entre otras cosas, este programa permite convertir un vídeo a formato WMV, o cambiar el formato de un WMV existente.

Esta es la configuración con la que deberemos generar nuestros vídeos WMV:

 

Para reproducir el vídeo, usaremos las nuevas clases Video y VideoPlayer. ¿El nombre de las clases es evidente? Más lo es su uso... La inicialización del vídeo se hace como la de cualquier otro contenido del Pipeline:

video = Content.Load<Video>(@"test");

Cambiar el estado del vídeo es igualmente sencillo:

if (Keyboard.GetState().IsKeyDown(Keys.P))
{
                reproductor.Play(video);
}

if (Keyboard.GetState().IsKeyDown(Keys.S))
{
                reproductor.Pause();
}

 

Para "pintar" el vídeo... con el siguiente código en el método Draw quedaría hecho:

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

            spriteBatch.Begin();

            spriteBatch.DrawString(fuente, "Pulsa P para reproducir, y S para pausar", new Vector2(0, 0), Color.White);

            if (reproductor.State == MediaState.Playing || reproductor.State == MediaState.Paused)
            {
                spriteBatch.Draw(reproductor.GetTexture(), new Rectangle(0, 30, video.Width, video.Height), Color.White);
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
}

El resultado, ejecutando el ejemplo adjunto, sería este:

 

Editado:

Aquí el compañero Tyler me ha enviado un código de como encapsular el vídeo en primitivas geométricas. El código vale realmente la pena, no se encuentra mucha información al respecto en internet. El resultado de la ejecución es el siguiente:

El código de esta pequeña maravilla podéis descargarlo en este enlace.

Posted: 25/8/2009 20:27 por Jesús Bosch | con 4 comment(s)
Archivado en:
[XNA] Videojuegos multijugador

Si desarrollar un videojuego ya de por sí es un reto, el tema se complica mucho más si este es multijugador. Hay muchas cosas que controlar: latencia, pérdida de datos, orden de envío/recepción de paquetes, optimización de la cantidad de datos a enviar/recibir... en este artículo primero recopilo algunos consejos para el desarrollo de juegos multijugador (no necesariamente específicos de XNA), y posteriormente veremos un pequeño ejemplo en esta genial plataforma de Microsoft. (que nadie se asuste, aunque programar juegos multijugador es complicado, XNA facilita las cosas encapsulando de forma muy sencilla cosas que si tubiéramos que codificar por nuestra cuenta tendrían una considerable complejidad añadida).

 

Tipologías de red

Esto os recordará a las asignaturas de redes... para los videojuegos, las tipologías más comunes son las conexiones "peer to peer" (todos los mensajes se envían a todo el mundo por parte de cada máquina), y la tipología cliente/servidor (cada máquina-jugador envía los datos a un servidor central, y este redistribuye la información). Tengamos en cuenta que el servidor puede ser una máquina dedicada, o una de las máquinas de los propios jugadores, que también puede actuar como tal.

Es una decisión de diseño importante seleccionar la tipología de red, ya que por ejemplo, la tipología peer to peer puede llegar a consumir todo el ancho de banda disponible si hay muchos jugadores conectados. Hay que vigilar con este punto.

 

El problema de la latencia

Es un problema tan molesto como inevitable, la latencia siempre estará ahí, por muy buena conexión que tengamos, y por más que optimicemos nuestro juego... la red es algo que no controlamos nosotros (sobretodo si no es una red local). Por ello hay que tomar decisiones importantes durante la fase de diseño de la lógica de nuestros juegos: por ejemplo, qué haremos si durante varios ciclos del juego no conocemos la nueva opsición de los demás jugadores... una posible aproximación sería presuponer que los demás jugadores mantienen la misma velocidad y dirección en cada instante, respecto el último dato conocido de los mismos. Aunque obviamente esto puede dar lugar a imprecisiones, puede reducir los efectos de "parpadeo", o "desaparición" de jugadores por la pantalla. En el XNA Creators Club Online encontramos un ejemplo de código perfecto de una implementación para minimizar este problema.

 

Sacar provecho del multithreading

Es recomendable utilizar un thread específico para la comunicación (envío de información entre las máquinas de los jugadores), y otro para la lógica del juego (física, AI, etc). Con esto se exprimirá la capacidad de la XBOX 360 y el PC, y los problemas de latencia no afectarán tanto al rendimiento general del juego.  Respecto este tema, existe un excelente tutorial en ziggyware, que recomiendo.

 

Haz todas las pruebas que puedas

Si de por sí ya es importante hacer pruebas en nuestros juegos antes de lanzarlos al mundo, con los juegos multijugador estas hay que multiplicarlas. Por suerte con XNA se puede simular tanto la latencia como la pérdida aleatoria de paquetes con el objetivo de depurar mejor nuestro juego.


Tecnología multiplayer en XNA

XNA tiene "acceso directo" a los servicios Windows LIVE y XBOX LIVE de forma gratuita y casi transparente (dependiendo de la plataforma en la que se ejecute nuestro juego). Entre otras cosas, lo que nos ofrece es:

  •  Creación de hosts
  • Manejo de conexiones
  • Comunicaciones por voz entre los jugadores
  • No puedes conectarte a un servidor propio en Internet si estás dentro de LIVE (pero se pueden implementar métodos de conectividad alternativos, aunque no funcionarían en XBOX 360)

 

Conectando a Windows/XBOX LIVE

La conexión a este servicio de Microsoft, nos resultará gratuita, y su funcionalidad se encapsula en el namespace GamerServices. Hagamos una prueba básica... crearemos un proyecto nuevo, y en el constructor de la clase Game,  añadiremos la siguiente línea de código: Components.Add(new GamerServicesComponent(this)); Después, ejecutaremos el juego y pulsaremos la tecla INICIO. Y ya estamos conectados a LIVE! Deberíamos ver algo parecido a lo siguiente:

A partir de aquí podemos crear una cuenta LIVE, o identificarnos con la nuestra -si tienes una cuenta "hotmail.com" o "live.com" puedes puedes autenticarte directamente-. Cuando estemos autenticados, veremos algo parecido a lo siguiente:

 

El poder (y el precio) de LIVE

Como hemos visto en el sencillo ejemplo, acceder a los servicios de LIVE es sencillo, y muy potente además. No obstante, tiene por supuesto sus limitaciones, sobretodo si trabajamos sobre la XBOX 360. Estas limitaciones son que no puedes enviar datos a servidores externos. Esto previene que se haga un mal uso de la información de los jugadores, pero la desventaja es que no se puede almacenar información personalizada online, que podría ser utilizada para hacer juegos multijugador más complejos, o cuyas partidas no fueran síncronas.

Los requerimientos de membresía a los servicios de LIVE son los siguientes para las distintas plataformas soportadas:

Xbox 360 consoleWindows-based development computerZune
Run an XNA Framework Game LIVE Silver membership + Premium XNA Creators Club membership No memberships required No memberships required
Use System Link for Local Area Network gameplay LIVE Silver membership + Premium XNA Creators Club membership No memberships required No memberships required
Sign in to Xbox LIVE and Games for Windows - LIVE Servers LIVE Silver membership + Premium XNA Creators Club membership LIVE Silver membership + Premium XNA Creators Club membership Not available on Zune
Use LIVE to connect to other machines over the Internet while the game is in development LIVE Gold membership + Premium XNA Creators Club membership LIVE Silver membership + Premium XNA Creators Club membership Not available on Zune

 

Podéis encontrar más información al respecto en el MSDN: Getting Started with Networked Games. Respecto los precios... no son exageradamente caros. El Premium XNA Creators Club membership ronda los 99$ al año y el LIVE Silver membership no alcanza los 50$ al año. El Creators Club lo necesitaremos para desarrollar y publicar nuestro juego en XBOX Indie Games, y LIVE Silver Membership para desarrollar y testear nuestro juego multijugador.

Los jugadores que quieran jugar online ya disponen de un membership Silver o Gold, por lo que no tienen que pagar nada que no tengan ya -excepto el juego que tu les vendes :-) -.

 

Ejemplos de código

Podéis ejecutar el código de ejemplo disponible bajo windows y con el método de conexión System Link en vuestro PC corriendo bajo Windows. Los ejemplos de código que os recomiendo son los de XNA Creators Club Online.

Posted: 19/8/2009 11:22 por Jesús Bosch | con no comments |
Archivado en: ,
[XNA] Técnicas de detección de colisiones (2D)

Existen numerosas técnicas de detección de colisiones entre sprites (no exclusivas de XNA, son comunes en el mundo del desarrollo de videojuegos). Lo más fácil y eficiente (de cara a procesamiento y obtención de buenos resultados de performance) es utilizar rectángulos. Como se muestra en la imagen:

El problema obvio es la imprecisión en la detección de la colisión. Segun esto, un impreciso acercamiento entre los dos objetos ya supondría una colisión. Como demuestra la siguiente imagen:

En muy pocas ocasiones, pues, esta técnica tan simple nos valdrá. Aunque existen otras variantes más precisas, y todavía eficientes, basadas en la detección de colisiones mediante rectángulos. Una de ellas sería dividir cada uno de los sprites en rectángulos más pequeños, y englobarlos todos en uno más grande. Entonces, primero intentaríamos detectar la colisión mediante los rectángulos grandes, y solo si esta es afirmativa, intentaríamos detectar la colisión entre los rectángulos más pequeños (y estos son los que decidirían si hay colisión o no).Segun esta técnia, no habría colisión entre los siguientes sprites:

Si todavía queremos ser más perfeccionistas en cuanto a la optimización del rendimiento, podemos dividir la pantalla en N rectángulos "virtuales", y hacer un primer filtro comprobando si el rectángulo de nuestro sprite se encuentra en el mismo cuadrante que el sprite contra el cual queremos comprobar la colisión.

Ahora bien... todavía puede no ser suficiente este nivel de precisión... puede que necesitemos una precisión de píxel a píxel. A eso se le llama comunmente en el desarrollo de videojuegos "pixel perfect collision". Acerca de esto existe un tutorial excelente en Ziggyware, por lo que no voy a profundizar más en este tema en este artículo.

Lo bueno de la técnica "pixel perfect collision" es su precisión. Lo malo? Nada es gratis en esta vida... los problemas de rendimiento que puede causar cuando el número de sprites sea grande pueden darnos dolores de cabeza. Para reducir estos problemas... se puede combinar la técnica del pixel perfect collision con la de los múltiples rectángulos. Es decir, para comprobar si dos sprites colisionan mediante este método "costoso", antes comprobaremos si en rectángulos que interseccionan.

Crear estos rectángulos de forma precisa mediante codificación (y muchas veces prueba / error) puede costarnos demasiado tiempo y energía... así que podríamos utilizar para ello el Vector Sprite Editor que nos proponen de nuevo en Ziggyware.

Otra variante que me parece muy interesante, y menos documentada, es la técnica de pixel perfect collision utilizando un mapa de colisiones. Esto consiste en la asunción de que si hay que hacer una comprobación píxel a píxel de la colisión... por lo menos reduzcamos el número de píxels para que este cálculo sea menos costoso! Parece obvio, pero como se hace? La idea es sencilla, trabajando con una imagen de menor resolución, o mapa de colisiones.

Podemos observar que será mucho más fácil (y mucho menos costoso) realizar una detección de colisiones con la imágen de la derecha que con la de la izquierda, por el simple motivo de que tiene muchos menos píxeles, y por tanto menos comprobaciones y cálculos a realizar.

Para generar el mapa de colisiones, tenemos varias opciones:

  1. Se generan los dos bitmaps (detallado y pixelado), y se cargan ambos en nuestro sprite, usando uno para mostrarlo por pantalla, y el otro para calcular las colisiones.
  2. Dinámicamente se carga el bitmap del sprite y se pixela. Si usamos esta opción es muy importante que sólo procesemos cada imagen una vez... sinó sería peor el remedio que la enfermedad. Lo ideal será realizar este proceso al inicio del juego, o idealmente al inicio de cada pantalla, obteniendo un tiempo de carga inicial algo mayor, pero una experiencia en el juego más rápida.

 

En la web de Creators de XNA existen excelentes ejemplos de código acerca de la detección y respuesta a colisiones que os recomiendo.

 

Dicho esto... ya tenemos conocimiento de algunas de las técnicas más comunes para detección de colisiones en juegos 2D no basados en Tiles (o casillas).

Felices colisiones!

Posted: 8/8/2009 11:07 por Jesús Bosch | con 6 comment(s) |
Archivado en: ,
[XNA] Jugando con las Leyes de Newton con XNA (II de II)

 

Las leyes de Newton darían para 100 artículos pero tranquilos, no tengo tanto tiempo libre :-P En este artículo completaré el experimento iniciado en la primera parte, en base a la tercera ley de Newton: toda acción provoca una reacción equivalente (insultante resumen para los puristas de la física). Esto viene a decir que si un obteo A ejerce una fuerza sobre un objeto B, este recibe una fuerza equivalente en la dirección opuesta.

Por ello partiremos del código anterior. Si os acordáis, teníamos unos círculos que reaccionaban a la "fuerza ejercida por el ratón" -el ratón evidentemente no ejerce fuerza, fué una simulación-. En esta ocasión seguiremos trabajando con los círculos. Ahora múltiples círculos colisionarán entre sí, reaccionando según la tercera ley de newton.

Lo primero que haremos, para evitar que se nos desmadren los círculos, va a ser evitar que estos, a fuerza de impactos, puedan salir de la pantalla. Será como si existiese un muro que las impidiese salir del monitor. Esto se consigue con poco código. Necesitamos saber las dimensiones de la pantalla, y la posición del círculo, que ya tenemos. Si hay colisión con la pared, invertimos la velocidad, por eso de la acción-reacción :-)

            // Actualizamos el vector posición del objeto, dependiendo de todos los demás!!
            Vector2 nuevaPosicion = this.posicion + this.velocidad;
          

            if (nuevaPosicion.X >= 0
                && nuevaPosicion.X <= this.dimensionesContenedor.X - this.textura.Width)
            {
                // Establecemos la nueva posición de X
                this.posicion.X = nuevaPosicion.X;
            }
            else
            {
                // Invertimos el vector velocidad, ya que hay colisión con la pared
                this.velocidad.X *= -1;
            }

            if (nuevaPosicion.Y >= 0
                && nuevaPosicion.Y <= this.dimensionesContenedor.Y - this.textura.Height)
            {
                // Establecemos la nueva posición de Y
                this.posicion.Y = nuevaPosicion.Y;
            }
            else
            {
                // Invertimos el vector velocidad, ya que hay colisión con la pared
                this.velocidad.Y *= -1;
            }

Ahora si ejecutamos, los círculos rebotarán como locos si impactamos sobre ellos con el mouse. Pero esto es lo fácil... ahora nos falta detectar las colisiones entre los círculos, y producir la consecuente reacción. No nos asustemos, primero hay otros ajustes que podemos hacer. Antes estaba controlando el impacto entre el cursor y el círculo mediante la creación de un objeto de tipo Rectangle, con las dimensiones del círculo, y evaluar si hacía intersección con otro rectángulo basado en las dimensiones del puntero. Eso es poco preciso...

Necesitamos una solución más elegante, pero sobretodo más precisa, y que sea eficiente. En vez de comprobar la intersección de dos objetos Rectangle, calcularemos la distancia entre el círculo y el puntero. Si esta es menor o igual al radio del círculo... tenemos colisión! Vendría ser algo así como lo siguiente:

De cara al rendimiento, aquí estamos haciendo una comprobación de "todos contra todos". Esto normalmente no se haría así en un juego más optimizado, en este artículo de mi blog anterior (catalán) explicaba algunas de estas técnicas más comunes.

Por suerte ni siquiera debemos acordarnos de las matemáticas de vecotres para conocer la distancia entre dos vectores en XNA. Lo que se traduce en la siguiente línea de código:

// Si hay colisión, le damos caña al objeto
Vector2 centroCirculo = new Vector2(this.posicion.X + this.textura.Width / 2, this.posicion.Y + this.textura.Height / 2);
Vector2 posicionMouse = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);

if (Vector2.Distance(centroCirculo, posicionMouse) < this.textura.Width / 2)

 

Ahora viene lo realmente difícil.. la colisión contra la pared era sencilla, porque era un elemento recto, y basta con invertir la velocidad del objeto que colisiona -en nuestro caso el círculo-. Para reaccionar a la colisión entre dos círculos hay que considerar que cada uno de ellos tendrá un vector dirección distinto... lo cual, como decía, complica las cosas. Además, vamos a implementar una reacción a la colisión de tipo elástico, algo inexistente en el mundo real (en el espacio habría una situación parecida, pero nunca totalmente elástica). En el mundo real, gobernado por las leyes físicas, las reacciones a las colisiones son inelásticas, es decir, que la velocidad después del impacto no será la misma a la velocidad antes del impacto. Esto es porque se produce una liberación de energía calorífica con este (y que además produce el sonido que podemos oir del impacto).

Para resolver la colisión entre múltiples círculos utilizaremos este algoritmo diseñado por Sunil Changrani. Esto, en XNA, viene a ser lo siguiente:

        private void resolverColision(ObjetoFisico circuloA, ObjetoFisico circuloB)
        {
            float x1 = circuloA.posicion.X;
            float y1 = circuloA.posicion.Y;
            float dx = circuloB.posicion.X - x1;
            float dy = circuloB.posicion.Y - y1;

            float radio = this.textura.Width / 2;

            // Calculamos la distancia
            float distancia = (float)Math.Sqrt(dx * dx + dy * dy);

            // Normal de los vectores y puntos intermedios
            float normalX = dx / distancia;
            float normalY = dy / distancia;
            float midPointX = (x1 + circuloB.posicion.X) / 2;
            float midPointY = (y1 + circuloB.posicion.Y) / 2;
           
            // Impide que los círculos se solapen
            circuloA.posicion.X = midPointX - normalX * radio;
            circuloA.posicion.Y = midPointY - normalY * radio;
            circuloB.posicion.X = midPointX + normalX * radio;
            circuloB.posicion.Y = midPointY + normalY * radio;

            // Calcular la nueva velocidad de los círculos
            float spdVector = (circuloA.velocidad.X - circuloB.velocidad.X) * normalX + (circuloA.velocidad.Y - circuloB.velocidad.Y) * normalY;

            float dvx = spdVector * normalX;
            float dvy = spdVector * normalY;

            // Asignar los nuevos valores
            circuloA.velocidad.X -= dvx;
            circuloA.velocidad.Y -= dvy;
            circuloB.velocidad.X += dvx;
            circuloB.velocidad.Y += dvy;
        }

El resultado sería este, un montón de "bolas locas" rebotando entre ellas y por toda la pantalal al pasar el ratón por encima. Otro experimento interesante es modificar la constante coeficienteFriccion y ponerla a 0, con ello las bolas nunca perderán velocidad y rebotarán entre ellas "eternamente". 

El código puede ser descargado en este enlace.

Posted: 7/8/2009 17:39 por Jesús Bosch | con 15 comment(s)
Archivado en: ,