Level 03: Detección de Colisiones II

Tercer tutorial sobre Programación con XNA Framework 4.0. Esta vez escribí sobre algunos problemas que pueden presentarse en la detección de colisiones 2D y también sobre cuáles serían las posibles soluciones, como la detección a nivel pixel (Pixel Perfect).

Frogger Kombat

Este juego fue realizado en conjunto con algunos amigos durante una Game Jam, a finales del 2011. Se trata de una pelea de sapos, que son manejados por 2 jugadores simultáneamente. El objetivo del juego es saltar y aplastar al contrincante hasta quitarle todos sus puntos de vida.

Votación CODEAR 2012: DEMO

CODEAR es un concurso en donde compiten desarrolladores de videojuegos. Consiste en producir un juego según una temática. En este caso, la temática fue "Demo". La votación termina el 11 de Septiembre. Puedes ver y jugar a todos los juegos que compiten, haciendo click en la imagen.

Nuevo mini COREAR: Cinemática

Estoy participando en un mini COREAR, que es un concurso en el cual compiten músicos y sonidistas. Cada competencia tiene una temática, a partir de la cual se crean pistas de música para poder participar. Esta vez la temática es "Cinemática". La idea es tomar cualquier cinemática de cualquier juego y componer una pista de música que pueda reemplazar a la original. Si quieres escuchar lo que hice, haz click en la imagen. Para más información sobre este COREAR, haz click en el título.

Sumate a la comunidad de DUVAL!

DUVAL (Desarrolladores Unidos de Videojuegos de América Latina) es una comunidad cuyo objetivo es mantener un lugar donde pueda existir una comunicación libre e independiente entre los desarrolladores de videojuegos de la region, para compartir trabajos, conocimientos, armar grupos, etc.

jueves, 22 de marzo de 2012

Juego: Todajato (GGJ 2012)

"La fama es peligrosa, su peso es ligero al principio, pero se hace cada vez más pesado el soportarlo y difícil de descargar."
Hesíodo (Siglo VIII a.C.) - Poeta griego.
Antes que nada...
Agradecimientos: Nuevamente, gracias a Killabunnies por el aguante de siempre. Agradezco también a todos los que compartieron esta experiencia, en especial a quienes conformaron nuestro grupo de desarrollo e hicieron posible este pequeño juego: Tozy, Tomás y Javier.
Imagen 0. Portada de nuestro juego, Todajato.
¡Saludos!

Este juego fue realizado durante el famoso evento del Global Game Jam, correspondiente al ciclo 2012. Es la primera vez que participo y la verdad estaba algo nervioso, pero tenía muchas ganas de participar. Estuve esperando la oportunidad desde hace muchos meses.
Una de las sedes del evento, fueron las oficinas de Killabunnies en la cual participé junto a otras 18 personas más. Se conformaron 5 grupos en esta ocasión para trabajar en distintos proyectos.
Imagen 1. Algunos participantes desarrollando en la primer noche de la Jam.

Cada año, el evento tiene una temática distinta. Esta vez, se trató sobre una imagen de la cual debíamos encontrar alguna interpretación y plasmarla en un juego. Esta fue la imagen:

Imagen 2. Imagen de la Temática del GGJ 2012.

A partir de ella, se fueron planteando distintos puntos de vista. De entre todas ellas, se eligieron algunas y luego se conformaron los grupos de trabajo.
Nuevamente, mis conocimientos en programación de videojuegos no eran los suficientes, por lo que opté por diseñar la música y los sonidos del juego.

Imagen 3. Quien les escribe, pensando y esperando un poco de inspiración.


Logré componer 2 tracks para el juego: "Jungle" y "Tribu-To". Son simples, pero sinceramente me gustó mucho la percusión del segundo. Se puede escuchar ambos tracks y también descargarlos:



En fin, fue otra gran experiencia que me incentiva a seguir adelante y continuar practicando para poder ayudar mejor en las próximos eventos.

Espero que disfruten de este pequeño juego. Personalmente, me encantó y me pareció muy divertido. Realmente el grupo hizo un gran trabajo, que disfruté de principio a fin.
Invito también a que vean el resto de los juegos que salieron de este GGJ 2012:
Imagen 4. A comienzos del primer día del GGJ 2012.

  • Circles of Destiny: Luciano Vairoli, Andrés Wolkovicz, Tozy, Andrés Miranda.
  • Spider's Loop: Andrés Miranda, Gonzalo Agosta, Emiliano Spesot.
  • Panic Snake: Cesar Regueira, Marcelo Guardia, Mariano Obeid, Esteban Corva, Juan Pablo Ferreyra.
  • Elusive Spirit: Esteban Corva, Luciano Vairoli, Jerónimo Barroco, Ignacio Puccini.



Todajato V 1.0
Global Game Jam: Es el mayor encuentro mundial de creación de juegos. Dura 48 horas y tiene lugar cada año a fines de enero. El GGJ reúne a miles de entusiastas de los videojuegos, los cuales participan desde sedes repartidas por todo el mundo. El GGJ es un proyecto de la International Game Developers Association (IGDA).
"Geko, es un mago atrapado en el cuerpo de una iguana. Debe escapar del antiguo templo y de la jungla para poder dominar el mundo, pero requerirá que implemente una estricta dieta basada en maldiciones y partes de él mismo."

Imagen 5. Un Screenshot del segundo nivel del juego.

Creamos un juego basado en puzzles con algunos elementos de "acción". Controlas a un pequeño reptil que debe alcanzar un cierto peso en orden para abrir puertas a través de interruptores. Para obtener power-ups (soles que te hacen aumentar de peso y calaveras que tienen el efecto inverso) debes sortear crueles trampas. Si una estaca o una sierra hace contacto contigo, perderás una pierna. El número de piernas afecta la velocidad de movimiento. Pero las piernas pueden ser recuperadas. También, si comes mucho aumentarás mucho de peso como para evadir trampas o deslizarse a través de pasadizos.

Imagen 6. De izquierda a derecha: Javier, Tomás, Tozy y yo.
Autores:
  • Manuel Carazo (Tozy) - Diseño de juego y Diseño de niveles.
  • Tomás Gutiérrez - Programación.
  • Javier Oliver - Diseño gráfico general.
  • Dante Costilla - Música y efectos de sonido.
Controles: flechas arriba, abajo, izquierda y derecha.


¡Sigue adelante, siempre!

lunes, 19 de marzo de 2012

Level 01: Creación y Movimiento de un Objeto

"El ser inmóvil mueve como objeto del amor, y lo que él mueve imprime el movimiento de todo lo demás."
Aristósteles (384 a.C. - 322 a.C.) - Filósofo griego.

Antes que nada...

Aviso: Usaremos como base el mismo código fuente desarrollado en la entrada anterior.

¡Saludos!

La entrada anterior trató sobre cómo empezar un nuevo proyecto y un poco sobre el uso de texto, lo cual nos resultará útil para visualizar algunos datos importantes más tarde. En esta ocasión, daremos un poco de vida a nuestro proyecto creando una figura que luego podremos controlar el movimiento de la misma con el teclado. Dicha figura, representará al jugador. También se hablará un poco sobre las características del sistema de coordenadas y haremos algunos experimentos para ir familiarizándonos con él. Eso es todo por ahora. Lo sé... al fin algo entretenido.

Estos son los temas que se verán a continuación:
  1. Pensando en un objeto para el jugador.
  2. Diseñando una clase para el juego.
  3. Creación un objeto para el juego, representando al jugador.
  4. Caracterísitcas del Sistema de Coordenadas 2D en XNA.
  5. Movimiento del objeto creado a través del teclado.
Imagen 0. Manipularemos el movimiento de esta pequeña figura con el teclado.


Pensando en un objeto
Cada vez que tomamos el joystick y encendemos una consola para jugar un juego, controlamos (en la mayoría de los casos) a un personaje y quizás participamos de una atrapante historia llena de desafíos por delante. Ese personaje que nos representa podría tener sus aliados o enemigos que interferirán en el objetivo del juego. Cada uno de ellos tendría sus propias características que los difrencian entre sí. Pero también, hay otras características que podrían tener en común.

Veamos los siguientes personajes improvisados. A simple vista, ¿qué diferencias tienen y qué cosas podrían tener en común?


Pensemos que estos personajes son protagonistas de un juego de pelea, por decir un ejemplo. Seguramente cada uno de ellos tienen sus propios puntos de vida, los de fuerza, los de defensa. Además se podrían diferenciar por su velocidad, su tamaño, sus colores, etc, etc, etc.

Lo importante de esto, es entender que ambos personajes tienen sus propias características que pueden diferenciarlos o asimilarlos. Por ejemplo, lo primero que se vé es la diferencia de tamaños y sus colores. Pero quizás tengan los mismos puntos de vida o la misma velocidad.

Y... ¿para qué sirve este análisis?

Es muy importante tener estas cosas en cuenta, pues estamos utilizando un lenguaje orientado a objetos y sería importante sacar el mayor provecho que podamos. Tengan en cuenta que en un futuro no sólamente vamos a tratar con 1 o 2 objetos (estoy seguro que no), sino con muchos más.

No soy un visionario, pero pensando un poco en las posibles clases de objetos que vayamos a utilizar en un futuro, creo que hay 2 características que van a tener en común la mayoría (por no decir todos) de los objetos: una posición y una textura (sprite). Pensando también en que la mayoría tendrán movimiento, podríamos agregar como otra característica una velocidad. Pensando en herencia, personalmente creo que estos atributos serían los más básicos. A lo mejor me esté equivocando pero lo estaremos comprobando con el correr del tiempo.

Mientras, vamos a crear una clase que va a representar a un jugador. No se trabajará con personajes como los anteriores todavía (lamentablemente). Usaremos algo más sencillo para empezar: Figuras geométricas. Y para hacer la experiencia más sencilla todavía, solamente usaremos por ahora los "atributos base" mencionados en el párrafo anterior.

Asi que... ¡comencemos!



Creación de la clase Figura
Empezaremos creando una clase, el "molde" (como decía mi profesor), para crear los futuros objetos.

Los atributos (o campos) que tendrá nuestra clase Figura serán:
  • textura [Texture2D]: hace referencia al Sprite que usaremos para vizualizar al objeto en la pantalla.
  • posicion [Vector2]:  un vector bidimensional que contendrá los datos sobre la ubicación del objeto en la pantalla, dados una coordenada horizonal 'x' y una vertical 'y'.
  • velocidad [float]: guardaremos en esta variable el valor con la velocidad que deseamos que se mueva la figura.
Serán estos, entonces, los parámetros mínimos para poder crear un objeto de la clase figura. Vamos a la barra de menú y creamos una nueva clase: Project >> Add Class. Elegimos un nombre para la clase y aceptamos. En mi caso, este es su código:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;          // Espacio de nombre necesario para usar Vector2
using Microsoft.Xna.Framework.Graphics; // Espacio de nombre necesario para usar Texture2D

namespace Prueba01
{
    class Figura
    {
        private Texture2D textura;   // Textura que se usará para representar al jugador.
        public Vector2 posicion;            // Contendrá las coordenadas de su posición.
        private float velocidadMov;  // Representa al valor de la velocidad de su desplazamiento.

        public void Inicializar(Texture2D textura, Vector2 posicion, float velocidadMov)
        {
            this.textura = textura;
            this.posicion = posicion;
            this.velocidadMov = velocidadMov;
        }

        public void Dibujar(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(
                            textura,
                            posicion,
                                null, 
                            Color.White,
                            0f,         //MathHelper.ToRadians(45)
                            Vector2.Zero,
                            0.5f,
                            SpriteEffects.None,
                            0f
                            );
        }

        public int GetAncho()
        {
            return textura.Width;   // Se retorna el valor (en pixeles) del ancho de la textura.
        }

        public int GetAlto()
        {
            return textura.Height; // Se retorna el valor (en pixeles) del alto de la textura.
        }

        public float GetVelocidad()
        {
            return velocidadMov; // Se retorna el valor de la velocidad establecida.
        }
    }
}
Noten que al principio del código declaré 2 Namespaces (Microsoft.Xna.Framework y Microsoft.Xna.Framework.Graphics). El primero contiene una clase fundamental que usa un vector bidimensional, llamada Vector2, que nos servirá para establecer una posición en la pantalla. El segundo posee la clase Texture2D, necesaria para poder dibujar luego el Sprite del objeto en la pantalla.

En la entrada referida a la Introducción a XNA 4.0, hablamos un poco sobre su framework y sobre los Espacios de Nombres (NameSpaces) que contiene esa biblioteca. Usaremos varios de ellos a partir de ahora, asi que recomiendo que vuelvan a hechar un vistazo.

Vean que también se agregó un método Dibujar() que necesita como parámetro una referencia de la clase SpriteBatch. Esta clase es proporcionada por el segundo namespace mencionado anteriormente y poseé un método sobrecargado llamado Draw() que nos permitirá dibujar nuestra figura en la pantalla. Podríamos expresar ese método de esta forma:


spriteBatch.Draw(textura, posición, rectángulo de origen, color, rotación, origen, escala, efecto de sprite, profundidad de capa)

En donde:



  • textura [Texture2D]: es la textura del sprite que usaremos para representar a la figura.
  • posición [Vector2]: es la posición en coordenadas (x,y) del punto en la pantalla donde se dibujará el sprite.
  • recángulo de origen [Nullable<Rectangle>]: rectángulo que especifíca los téxeles de origen de una textura. Usamos null para dibujar toda la textura. De lo contrario, pasamos un rectángulo con las dimensiones deseadas. Se dibujará la parte de la textura que esté comprendida dentro de esas dimensiones.
  • color [Color]: indica el color para tintar un sprite. Usamos Color.White para no tintar el sprite, es decir que no se modificarán sus colores originales.
  • rotación [float]: indica el ángulo (dado en radianes) de rotación del sprite respecto a su centro.
  • origen [Vector2]: es el punto de origen de la textura. La rotación de la textura, tomará como referencia este punto. Por defecto, usamos siempre Vector2.Zero o "(0,0)" que representa el punto de la esquina superior izquierda de la textura. Hay que prestar atención cuando se modifica este parámetro, ya que puede generar algunas confusiones luego.
  • escala [float]: el valor de escala del sprite. Para dibujar el tamaño original, se usa como valor 1f. Pero en este caso usé 0.5f, por lo que se dibujará la figura con la mitad de su tamaño original.
  • efecto de sprite [SpriteEffects]: indica si se efectuará algún efecto al sprite. Los efectos que se pueden efectuar son 2: voltear el sprite horinzontalmente o verticalmente.
  • profundidad de capa [float]: representa la "profundidad" del sprite, en relación a otros sprites. Consideramos como una "capa": Imaginen que tienen una pila de hojas de papel (o capas) apoyadas en un escritorio y que las observan desde arriba. La hoja que está en frente de todas (la capa de tope) tendría el valor de 1f mientras que la que está al fondo de todas (la capa base) tendría un valor de 0f. Las hojas que están entre esas 2, tendrían un valor entre el intérvalo [0f - 1f]. Teniendo en cuenta esto, las capas que tengan el valor más cercano a 1, son las que estarán al frente y se verán primero, "tapando" a aquellas que tengan un valor menor (que tiendan a 0).



Creación de un objeto y Sistemas de Coordenadas 2D en XNA

Bien, ya creamos nuestra clase y ahora continuamos creando una figura para mostrarla en pantalla. Elegí este simple e inexpresivo cuadrado para que luego manipulemos sus movimientos con el teclado:



Figura 1. Este será el "personaje" que el jugador controlará.

Es un poco grande, pero el método Dibujar() que realizamos reduce la escala de cualquier textura a la midad. Es sólo por cuestión de comodidad.

Vamos a la clase principal Game1 y declaremos una instancia de la clase Figura. Declaremos también otra variable tipo float que contenga el valor de la velocidad con la que se desplazará el cuadrado.

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Figura jugador;                 // Instancia del objeto de clase Figura.
        float velocJugador = 5f;        // Valor que se le sumará o restará a la posición del objeto, por cada loop.
        KeyboardState estadoTeclado;    // Referencia que guardará el último estado del teclado, por cada loop.
     
        SpriteFont font;                // Se guardará el tipo de fuente que se usará en los textos.

Ahora continuemos con la creación del cuadrado que representará el jugador. Simplemente agregamos esto en el método Initialize():


protected override void Initialize()
        {
            jugador = new Figura();

            base.Initialize();
        }

Continuamos estableciendo el punto de origen y cargando la textura que usaremos para el cuadrado. Esto corresponde hacerlo en el método LoadContent():


protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Se establece la posición inicial del jugador (x,y)
            Vector2 posicionJugador = new Vector2(
                                                GraphicsDevice.Viewport.TitleSafeArea.X,
                                                GraphicsDevice.Viewport.TitleSafeArea.Y
                                                );
            jugador.Inicializar(Content.Load<Texture2D>("Texturas/Azul"), posicionJugador, velocJugador);
            font = Content.Load<SpriteFont>("SpriteFont1");
        }


Por último, nos queda dibujar a nuestro amigo cuadrado en la pantalla. Como ya tenemos desarrollado este método en la clase Figura, sólo tenemos que llamarlo dentro del método Draw() de la clase principal Game1. Entonces, a lo que ya teníamos, le agregamos lo mencionado y quedaría así:



protected override void Draw(GameTime gameTime)
        {
            string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
            string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);          
            
            GraphicsDevice.Clear(Color.CornflowerBlue);
            

            // ============ Comienza dibujado ===================
            spriteBatch.Begin();

            // Se dibuja el texto donde informa el valor de Ancho de la pantalla
            spriteBatch.DrawString(font,
                                "Ancho: " + anchoPantalla,
                                new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,
                                            GraphicsDevice.Viewport.TitleSafeArea.Y),
                                Color.White,
                                0f,
                                new Vector2(0f,0f),
                                0.8f,
                                SpriteEffects.None,0);

            // Se dibuja el texto donde informa el valor de Alto de la pantalla
            spriteBatch.DrawString(font,
                                    "Alto: " + altoPantalla,
                                    new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X+20,
                                                GraphicsDevice.Viewport.TitleSafeArea.Y+50),
                                    Color.DarkBlue,
                                    MathHelper.ToRadians(90),
                                    new Vector2(0,0),
                                    0.8f,
                                    SpriteEffects.None,
                                    0f);

            // Se dibuja la figura del jugador
            jugador.Dibujar(spriteBatch);
           
            spriteBatch.End();
            // ============ Termina dibujado ===================           
                 

            base.Draw(gameTime);
        }

A estas alturas, si ejecutamos el programa deberíamos poder observar al cuadrado en la esquina superior izquierda de la pantalla de juego.

Todavía no realizamos un método que pueda mover al objeto que creamos. Lo haremos luego, pero primero hablemos un poco sobre el Sistema de Coordenadas 2D en XNA.



Sistemas de Coordenadas 2D en XNA

El Sistema de Coordenadas 2D aplicado en XNA tiene ubicado su origen (0,0) en la esquina superior izquierda de la pantalla de juego. Los valores del eje X van creciendo desde ese punto hacia la derecha, hasta llegar a su punto máximo, que es el límite de la pantalla (en este caso, el límite es 800). Los valores del eje Y van creciendo desde el origen hacia abajo, hasta llegar a su punto máximo (en este caso, el límite es 480). La siguiente imagen representa todo lo mencionado:


Figura 2. Sistema de Coordenadas 2D en XNA


Podemos obtener esos valores a través de una propiedad llamada "Viewport", que pertenece a la clase "GraphicsDevice" incluída dentro del NameSpace "Microsoft.Xna.Framework".

Recordemos que en la clase Figura definimos el método Inicializar() y que necesita (además de la textura y la velocidad) un parámetro de tipo Vector2, el cual representa su posición inicial. Ahora, volviendo a la clase principal Game1, recordemos también que dentro del método LoadContent() creamos una referencia del tipo Vector2 con 2 valores (x,y) que representan a un punto del sistema de coordenadas bidimensional. Dichos valores fueron (0,0), por lo que el cuadrado aparece dibujado en la esquina superior izquierda de la pantalla.

Podemos modificar esos valores por otros, en caso de que se desee crear el objeto en otra posición. Por ejemplo, podemos crear al objeto en el centro de la pantalla. Para ello, tendríamos que modificar los parámetros al crear la referencia del tipo Vector2. En este caso es útil la propiedad Viewport que mencionamos anteriormente, ya que gracias a ella podemos obtener, entre otras cosas, el punto del centro de la pantalla:


Vector2 posicionJugador = new Vector2(
                                                GraphicsDevice.Viewport.TitleSafeArea.Center.X,
                                                GraphicsDevice.Viewport.TitleSafeArea.Center.Y
                                                );
            jugador.Inicializar(Content.Load("Texturas/Azul"), posicionJugador, velocJugador);

Si hacemos esta modificación, al ejectuar el programa tendríamos que ver a nuestro amigo azul en el centro de la pantalla. De esta forma:


Figura 3. Ubicación del punto del centro de la pantalla y Origen de la textura.


Bueno, no está exactamente dibujado en el centro de la pantalla. ¿Por qué? Porque al inicializar el objeto, le pasamos como parámetros las coordenadas del centro de la pantalla (400,240) con la ayuda de la propiedad Viewport. Este punto va a ser el origen del objeto. Recordemos una vez más que en el sistema de coordenadas 2D de XNA, los valores del eje X y del eje Y se incrementan desde el origen hacia la derecha y hacia abajo respectivamente, por lo que el método Dibujar() dibujará (valga la redundancia) la textura del objeto comenzando desde su punto de origen (parámetro que pasamos como posición en el método Inicializar()) y desplazándose en las direcciones mencionadas.

¿Dí muchas vueltas? Espero que se haya entendido, porque es algo que hay que tener muy en cuenta. Cuesta entender un poco hasta que uno se acostumbra. Experimenten con otros valores para que entiendan mejor el funcionamiento, no hay mejor forma.

Juguemos un poco con la rotación del objeto ahora. Hace poco hablamos del método Draw() que creamos en la clase Figura. Tengamos en cuenta 3 de todos sus parámetros: posición, rotación y origen. En estos momentos, tenemos a nuestra figura ubicada en el punto (400,240), que es el punto de su posición. La rotación tiene un valor de 0f, que es lo mismo que una rotación de 0º. Y por último, el punto origen de la textura es (0,0).
Cambiemos un par de cosas. En la entrada anterior vimos cómo rotar un texto con la ayuda de la clase MathHelper. Cambiemos entonces la rotación de nuestro amigo a unos 45º, de esta manera:


public void Dibujar(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(
                            textura,
                            posicion,
                                null, 
                            Color.White,
                            MathHelper.ToRadians(45), //anteriormente  0f
                            Vector2.Zero,
                            0.5f,
                            SpriteEffects.None,
                            0f
                            );
        }


Entonces lo que veremos al ejecutar el programa será lo siguiente:

Figura 4. Ejemplo de rotación de la textura en 45º, respecto al punto de su origen.

¿Qué pasa ahora si cambiamos el origen de la textura? Quiero tener el origen ubicado en el centro de la textura. En este caso, la textura es de 200x200 pixeles. La clase Figura que diseñamos, tiene 2 métodos: GetAncho() y GetAlto(), los cuales nos devuelven el valor del ancho y del alto respectivamente de la textura que estamos usando. Esto nos resultará útil ahora:

public void Dibujar(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(
                            textura,
                            posicion,
                                null, 
                            Color.White,
                            MathHelper.ToRadians(45), //esto estaba en 0f
                            new Vector2 (getAncho()/2,getAlto()/2),
                            0.5f,
                            SpriteEffects.None,
                            0f
                            );
        }

Veamos qué sucede:


Figura 5. Ejemplo resultante de la modifiación de los valores de origen de la textura.

Ahora sí podría decir que la figura está en el centro de la pantalla. Esto es lo que pasa cuando cambiamos el origen de la textura. Este es otro ejemplo con el origen de la textura modificado:


Figura 6. Otro ejemplo de la modificación del origen de la textura.

En fin, resulta demasiado útil saber cómo lidiar con estos aspectos en el sistema de coordenadas de XNA. Pero también es importante tener cuidado cuando combinamos esto con la detección de colisiones, tema que se desarrollará en la siguiente entrada.


Movimiento del Objeto con Teclado

Agreguemos este último e importante ingrediente al proyecto: controlar sus movimientos con el teclado.

Para ello, necesitamos del NameSpace "Microsoft.Xna.Framework.Input" que contiene clases que reciben entradas desde el teclado, el mouse y el joystick de la consola Xbox360. Es decir, estas clases son las que permiten a nuestro juego interactuar con el/los jugadores. Para empezar de a poco, sólo realizaremos el movimiento de nuestro objeto a través del teclado. La clase que nos permitirá hacer esto, se llama "KeyboardState". ¡Comencemos a dar vida a nuestro proyecto!

Primero, necesitamos crear una referencia a la clase KeyboardState, al inicio de la clase principal Game1:

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Figura jugador;                 // Instancia del objeto de clase Figura.
        float velocJugador = 5f;        // Valor que se le sumará o restará a la posición del objeto, por cada loop.
        KeyboardState estadoTeclado;    // Referencia que guardará el último estado del teclado, por cada loop.
     
        SpriteFont font;                // Se guardará el tipo de fuente que se usará en los textos.

Ahora que tenemos una referencia en dónde guardar los datos que enviamos con el teclado, necesitamos, justamente, hacer que se guarden. Recordemos que el Loop de Juego de XNA es un ciclo que afecta a 2 principales métodos de la clase principal Game1: Draw() y Update(). Sería correcto que guardemos los datos sobre qué tecla se presionó, en cada loop del juego. Por eso, lo indicado sería hacerlo dentro del método Update().
Una vez que guardamos dicho estado, deberíamos poder mover a nuestro amigo dependiendo de la tecla que hayamos presionado. Es decir, sería necesario tener un método que nos permita modificar la posición del objeto creado, luego de analizar el último estado del teclado. Aplicando todo lo dicho en nuestro código:
protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            estadoTeclado = Keyboard.GetState();
            MoverFigura(gameTime);

            base.Update(gameTime);
        }

        private void MoverFigura(GameTime gameTime)
        {
            // Analizamos qué tecla fue presionada y luego modificamos su posición

            if (estadoTeclado.IsKeyDown(Keys.Left))
            { jugador.posicion.X -= jugador.GetVelocidad(); }

            if (estadoTeclado.IsKeyDown(Keys.Right))
            { jugador.posicion.X += jugador.GetVelocidad(); }

            if (estadoTeclado.IsKeyDown(Keys.Up))
            { jugador.posicion.Y -= jugador.GetVelocidad(); }

            if (estadoTeclado.IsKeyDown(Keys.Down))
            { jugador.posicion.Y += jugador.GetVelocidad(); }

            jugador.posicion.X = MathHelper.Clamp(jugador.posicion.X, 0,
                                                GraphicsDevice.Viewport.Width - jugador.GetAncho() /2);
            jugador.posicion.Y = MathHelper.Clamp(jugador.posicion.Y, 0,
                                                    GraphicsDevice.Viewport.Height - jugador.GetAlto() /2);
        }


Eso es todo, es fácil de interpretar el código. Para mover a la figura, sólo debemos presionar las teclas arriba, abajo, izquierda o derecha según hacia dónde se nos apetezca moverlo. Al determinar qué tecla fue presionada, se modificará la posición del objeto dependiendo de la velocidad que indicamos al principio. Lo único extraño es el método "Clamp()", el cual restringe un valor dentro de un intérvalo dado. En nuestro caso, le indicamos que el objeto no se puede pasar de los límites de la pantalla. Otro método muy útil de nuestro ya muy querido amigo MathHelper.

Y ahora sí, si ejecutamos el código vamos a poder mover a la figura cuadrada por toda la pantalla. ¡Al fin algo se mueve!


Conclusión
Hemos pensado en un objeto que pueda controlar un jugador. Realizamos una clase con sus atributos más importantes y logramos moverlo en la pantalla. Hicimos algunas pruebas de rotación para entender y acostumbrarse un poco al sistemas de coordenadas 2D de XNA. Pueden descargar el código fuente haciendo click en el siguiente botón: 



En la próxima entrada crearemos otro objeto para manipularlo esta vez con el mouse y veremos uno de los temas más importantes: Detección de Colisiones. Tengo ganas de hacer un juego muy muy simple luego de eso, como para simplificar todo lo que hicimos hasta ahora. Es una posibilidad, veremos qué sucede.

¡Hasta entonces!


¡Sigue adelante, siempre!

martes, 31 de enero de 2012

Juego: Frogger Kombat

"Sorprendernos por algo es el primer paso de la mente hacia el descubrimiento."
Louis Pasteur (1822 - 1895) - Químico y Microbiólogo francés

Antes que nada...

Agradecimientos: A todos los que hicieron posible y compartieron esta gran experiencia que fue muy importante para mí. Un agradecimiento especial al grupo de Killabunnies por toda su buena onda.

Saludos!

Este es un juego que surgió de una Game Jam organizada el 26 de noviembre del 2011 por Killabunnies. El evento se realizó en la provincia de Santa Fe (Argentina). Participamos 8 personas en total, algunos de ellos compañeros de la facultad. Las tareas se repartieron 3 áreas: diseño de juego, programación y diseño gráfico.
Algunos no teníamos experiencia en desarrollo de videojuegos, pero todos colaboraros en algunas cosas. La experiencia fue muy buena, fue muy emocionante para mí, ya que era la primera vez que colaboraba en un proyecto de videojuego. Realmente aprendí muchas cosas.

Por mi parte, pude aportar con la música del juego. El proyecto se construyó usando C++ como lenguaje y con la ayuda de una API conocida como SMFL. Lamentablemente no tenía conocimientos sobre aquel lenguaje, por lo que no pude contribuir en la programación del juego.
Este fue uno de los motivos que me impulsaron a investigar y aprender sobre la programación de videojuegos. Y finalmente ese impulso me llevó a crear este blog para poder compartir sobre lo que me gusta y, en lo posible, contagiar un poco de mi pasión por los videojuegos.




Frogger Kombat V 1.0

Game Jam: Es un encuentro entre diseñadores, artistas, programadores y otras personas que tengan conocimientos sobre el desarrollo de videojuegos. El objetivo del Game Jam es producir un videojuego durante el transcurso de un tiempo prestablecido (generalmente son 2 días). El Game Jam suele tener temáticas y limitaciones en el desarrollo.

El Game Jam tuvo como temática el concepto de "Juego Multijugador  -  Dos Botones". Es decir, el proyecto tenía la condición de no usar más de 2 botones y, además, debería poder ser jugado por más de una sola persona simultáneamente.

Con esas condiciones y luego de intercambiar ideas en el grupo, se creó Frogger Kombat. El juego consiste en una pelea entre 2 ranas/sapos. El jugador se mueve saltando y deberá caer encima de su contrincante para poder reducir su vida hasta cero y dejarlo fuera de combate. Los movimientos de los anfibios hacen que la partida sea muy entretenida, ya que requiere calcular muy bien los saltos para atacar o esquivar. ¡Y los diseños de los personajes son espectaculares!

Pueden descargar el juego haciendo click en el botón "Descargar". ¡Espero que inviten a alguien más a jugarlo y disfruten pisoteándose entre los dos! ¡Espero sus comentarios!
 
Autores:
  • Alexis Roldán (Diseño Gráfico)
  • Andrés Miranda (Diseño Gráfico)
  • Dante Costilla (Música)
  • Esteban Corva (Diseño de Personajes)
  • Gonzalo Agosta (Programación)
  • Marcelo Guardia (Programación)
  • Marcos Cabaña (Programación)
  • Mariano Obeid (Programación)
 

¡Sigue adelante, siempre!

viernes, 27 de enero de 2012

Level 00: Nuevo Proyecto y Uso de Texto

"Los primeros 40 años de vida nos dan el texto; los 30 siguientes, el comentario."
Arthur Schopenhauer (1788 - 1860 - Filósofo alemán.

Antes que nada...

Aviso: El contenido puede estar incompleto. Iré actualizando en caso de que necesite hacer algunas correciones o agregar nuevos temas.

¡Llegó la hora de empezar!

Esta es la primer entrada relacionada a la programación de videojuegos 2D en XNA 4.0. En esta oportunidad, vamos a comenzar viendo temas básicos (...y algo aburridos, debo admitir) necesarios para poder comenzar con un videojuego. Estos son los siguientes temas que veremos esta vez:
  1. Cómo comenzar un Nuevo Proyecto
  2. Cómo cambiar el tamaño de Pantalla del juego.
  3. Qué es y cómo crear un SpriteFont.
  4. Cómo cargar un archivo al proyecto.
  5. Cómo mostrar un Texto en la Pantalla.
Así que bueno, ¡comencemos de una buena vez!



Comenzando un Nuevo Proyecto

Una vez instalado XNA Game Studio 4.0, busquemos la carpeta de Microsoft Visual Studio 2010 Express que también fue instalada. Luego ejecutemos la aplicación Microsoft Visual Studio 2010 Express for Windows Phone. Tendría que aparecer la siguiente ventana de inicio:
Imágen 1.0: Pantalla de inicio

Luego, para comenzar un nuevo proyecto, vamos a la pestaña File y hacemos click en New Project. Aparecerá lo siguiente:
Imágen 1.1: Selección de un Nuevo Proyecto

Elije un nombre para tu proyecto y una dirección donde se guardarán sus datos. En mi caso, mi proyecto se va a llamará Prueba01 y lo guardaré en el Escritorio de Windows.
Cuando todo esté listo para comenzar, seleccionan OK y les aparecerá el siguiente código automáticamente (el cual fue explicado brevemente en Press Start!: Introducción a XNA 4.0 ):

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Prueba01
{
    /// 
    /// This is the main type for your game
    /// 
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// 
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// 
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// 
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// 
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// 
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// 
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// 
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// 
        /// Provides a snapshot of timing values.
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// 
        /// This is called when the game should draw itself.
        /// 
        /// Provides a snapshot of timing values.
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

Para ejecutar el código de nuestro proyecto, apretamos F5. Si lo hacemos ahora, tendría que salir la siguiente pantalla:
Imagen 1.2: Ejecución de un Proyecto
 
Para nada emocionante, la verdad ¿eh?. Pues quieras creerlo o no, esto ya es tu primer juego y gana quien soporte más tiempo observar la amigable pantallita azul. Puedes invitar a toda tu familia... pero no cuentes conmigo.


Configurando el Tamaño de Pantalla


public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // this.graphics.IsFullScreen = true;
            
            //this.graphics.PreferredBackBufferWidth = 600;
            //this.graphics.PreferredBackBufferHeight = 800;
        }

Antes de comenzar, configuremos un poco el tamaño de pantalla. Si deseas ejecutar tu juego en pantalla completa, debes activar la propiedad IsFullScreen como "true" (línea 6). O si prefieres, puedes modificar el valor de su ancho y su alto (líneas 8 y 9 respectivamente) con las propiedades PreferredBackBufferWidth (ancho) y PreferredBackBufferHeigth (alto) con las medidas que elijas (por ejemplo 600x800 pixeles).

En este caso, decidí usar el tamaño de pantalla que tiene inicialmente. Por eso, las propiedades están a modo de comentarios.


Creación de un SpriteFont

Aviso: Aprende más sobre SpriteFont aquí.

¡Bien! Ya configuramos la pantalla, ahora veamos cómo podemos mostrar un simple texto en ella. En este caso vamos a mostrar el valor del ancho y alto de la pantalla. Aunque en un futuro, lo que aprendamos ahora, seguramente nos servirá para mostrar los puntos de vida de un jugador, o su puntaje, o un texto que narre una historia, etc. Por ahora,  vayamos por lo básico.

Antes de poder escribir algo, es necesario crear un SpriteFont (fuente de texto). Para ello, hacemos click derecho en Prueba01Content y seleccionamos "New Item", como muestra la siguiente imagen:

Imágen 2.0: Añadir un nuevo Sprite Font


Luego, elijan un nombre (SpriteFont1 es en mi caso) y seleccionen el item llamado "Sprite Font", el cual representará la fuente de texto que usaremos para mostrar.

Entonces se generará el siguiente código en una nueva pestaña:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">

    <!--
    Modify this string to change the font that will be imported.
    -->
    <FontName>Arial</FontName>

    <!--
    Size is a float value, measured in points. Modify this value to change
    the size of the font.
    -->
    <Size>14</Size>

    <!--
    Spacing is a float value, measured in pixels. Modify this value to change
    the amount of spacing in between characters.
    -->
    <Spacing>0</Spacing>

    <!--
    UseKerning controls the layout of the font. If this value is true, kerning information
    will be used when placing characters.
    -->
    <UseKerning>true</UseKerning>

    <!--
    Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
    and "Bold, Italic", and are case sensitive.
    -->
    <Style>Regular</Style>

    <!--
    If you uncomment this line, the default character will be substituted if you draw
    or measure text that contains characters which were not included in the font.
    -->
    <!-- <DefaultCharacter>*</DefaultCharacter> -->

    <!--
    CharacterRegions control what letters are available in the font. Every
    character from Start to End will be built and made available for drawing. The
    default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
    character set. The characters are ordered according to the Unicode standard.
    See the documentation for more information.
    -->
    <CharacterRegions>
      <CharacterRegion>
        <Start>&#32;</Start>
        <End>&#126;</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>


Lo más importante sucede en las líneas 14, 20 y 26:
  • La línea 14 se refiere al tipo de fuente que vamos a usar. En esta caso usé "Arial" como fuente.
  • La línea 20 indica el valor (en float) del tamaño que el texto tendrá. En este caso, el tamaño de fuente es de 14.
  • La línea 26 establece el valor (en float) del espaciado entre los caracteres del texto. En este caso es de 0.
Si deseas cambiar alguna de estas características, sólo modifica sus valores por los que quieras.


Carga de un archivo (SpriteFont)

Aviso: Aprende más sobre el método LoadContent() aquí.

Ahora que creamos un SpriteFont, podemos escribir algo en pantalla. Primero, declaramos una variable de instancia que hará referencia al SpriteFont que acabamos de crear. Lo hacemos agregándola de esta manera, al comienzo de la clase Game1 (ubicada en la línea 12 del código mostrado al principio) :
public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;        
     
        SpriteFont font;                        // Se guardará el tipo de fuente que se usará en los textos.
        


Luego, continuamos cargando el SpriteFont que creamos hace un rato, que estará referenciada por la variable font. Para ello, añadimos lo siguiente dentro del método LoadContent() de la clase Game01:

protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            font = Content.Load<SpriteFont>("SpriteFont1");
        }

El método Load() carga aquellos archivos que necesitará el juego, como por ejemplo archivos mp3, jpg, png... y en este caso necesitamos un SpriteFont. El método necesita como parámetro el nombre del archivo, que aquí es "SpriteFont1". Podría simplificarse la expresión del método de la siguiente manera:

instanciaDeArchivo = Content.Load <tipoDeArchivo>("NombreDelArchivo");

En donde:
  • instanciaDeArchivo: es una variable de instancia que hace referencia a un archivo en particular (unaImagen, unSonido, unaTextura). En este caso la variable se llama font.
  • tipoDeArchivo: es la clase de archivo que se desea cargar. Puede ser un archivo de la clase SpriteFont, Texture2D, Texture3D, Song...etc. Aquí usamos un SpriteFont.
  • nombreDelArchivo: es el nombre del archivo que fue cargado en la carpeta Content. En este caso es "SpriteFont1"Es muy importante que todo los archivos que vayas a usar (gráficos, donidos, texturas) los guardes en esa carpeta. Para ello, sólo arrastra con el mouse el archivo que quieras guardar en el proyecto y deposítalo dentro de la carpeta Content.


Escribir un Texto en Pantalla

Aviso: Recomiendo que mires antes un post sobre cómo es el Sistema de Coordenadas en 2D aplicado en XNA. De todas formas, explicaré un poco de esto en la siguiente Entrada. Muchas gracias al ingeniero David Mariscal Fernández por la información.

Todo listo para escribir algo en pantalla. Para ello añadimos lo siguiente dentro del método Draw() de la clase Game1:

protected override void Draw(GameTime gameTime)
        {
            string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
            string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);                     
            
            GraphicsDevice.Clear(Color.CornflowerBlue);
            

            // ============ Comienza dibujado ===================
            spriteBatch.Begin();

            // Se dibuja el texto donde informa el valor de Ancho de la pantalla
            spriteBatch.DrawString(font,
                                "Ancho: " + anchoPantalla,
                                new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,
                                            GraphicsDevice.Viewport.TitleSafeArea.Y),
                                Color.White,
                                0f,
                                new Vector2(0,0),
                                0.8f,
                                SpriteEffects.None,0f);

            // Se dibuja el texto donde informa el valor de Alto de la pantalla
            spriteBatch.DrawString(font,
                                    "Alto: " + altoPantalla,
                                    new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X+20,
                                                GraphicsDevice.Viewport.TitleSafeArea.Y+50),
                                    Color.DarkBlue,
                                    MathHelper.ToRadians(90),
                                    new Vector2(0,0),
                                    0.8f,
                                    SpriteEffects.None,
                                    0f);
           
            spriteBatch.End();
            // ============ Termina dibujado ===================           
                 

            base.Draw(gameTime);
        }

En las líneas 3 y 4, lo único que hice fue convertir el valor del ancho y alto de la pantalla a String y guardar esos valores en su respectivas variables.

Pero lo más importante de este método sucede entre las líneas 11 y 36. Todo aquello que esté escrito entre el método spriteBatch.Begin() y el método spriteBatch.End() se dibujará en pantalla por cada loop del juego. Es decir, mientras el juego funcione, se dibujará a cada momento todo lo que esté entre esos 2 métodos.

Se observa también que se usa el método DrawString() en las líneas 14 y 25. Este método sirve para dibujar un texto en pantalla y requiere (en este caso) de los siguientes parámetros:
  1. spriteFont: [SpriteFont] fuente para mostrar texto.
  2. text: [String] Cadena de texto a escribir.
  3. position: [Vector2] Ubicación (en coordenadas de la pantalla) en donde se escribirá el texto.
  4. color: [Color] El color que tendrá el texto.
  5. rotation: [float] Ángulo de rotación (en radianes) del texto que se girará respecto a su centro. En este caso usé uno de los métodos de MathHelper
  6. origin:  [Vector2] Coordenadas orígen del texto. El valor predeterminado es (0,0), que representa su esquina superior izquierda.
  7.  scale: [float] Factor de escala.
  8. effects: [SpriteEffects] Efectos que se aplican al texto (negrita, cursiva).
  9. layerDepth: [float] Profundidad de la capa. De forma predeterminada, 0 representa la capa anterior y 1 representa una capa posterior.
 Es importante saber que para representar la posición de un texto, una imagen... en fin un objeto, se usa la clase Vector2, que indica su posición en coordenadas (x,y). Hablaremos mejor de esto en las próximas entradas. 

Así entonces, si presionamos F5 se obtiene como resultado lo siguiente:

Imagen 3.0: Demostración de Texto en Pantalla

Vemos que el texto "Ancho: " no tiene rotación. Por eso el quinto parámetro (rotation) vale cero. En cambio el texto "Alto: " Tiene una rotación de 90º. Pude hacer eso con la ayuda de uno de los métodos de la clase MathHelper. Esta clase resulta muy útil para ahorrarnos el trabajo de hacer algunos cálculos. Por ejemplo: 

Pregunta: ¿Cuántos radianes son equivalentes a 90 grados?
Respuesta: No lo sé, pero si usas el método ToRadians() de la clase MathHelper y le pasas como parámetro esos 90 grados, te aseguro que te los convierte en radianes.

Si, ya sé que no soy muy amigo de las matemáticas. Pero Wikipedia si, asi que puedes ver un artículo sobre radianes e informarte en lugar de seguir mi mal ejemplo :) ... o puedes pedirle ayuda de vez en cuando a nuestro querido amigo MathHelper, como yo :D



Conlusión
Hemos visto algo muy aburrido para mi gusto (... en verdad ¬¬ ). El uso de texto no es la gran cosa, pero seguramente saber esto nos resultará muy útil más adelante. Puedes descargar el código fuente:



Y esto es todo por ahora. Les prometo que la próxima, hacemos algo más entretenido. Vamos a comenzar a controlar el movimiento de un objeto que represente al jugador. Y luego podremos ver temas más emocionantes, como la detección de colisiones.

Tengo varios proyectos ya armados y funcionando, pero el problema es armar las Entradas del blog. Realmente me lleva mucho tiempo organizar, corregir y tratar de hacer todo lo más entendible posible. Pero cuando tenga listas las entradas, las subo.

¡Hasta entonces!


¡Sigue adelante, siempre!