[XNA] Detección de colisiones por descomposición del espacio en una rejilla de vóxeles

Imaginemos que nuestro juego tiene cientos de agentes desplazándose por el mundo virtual del mismo, y debemos comprovar si están consiguiendo colisionar con el agente que representa el jugador… ¿Cómo lo haríamos? La opción más fácil es comprovar si el bounding de cada uno de estos enemigos colisiona con el bounding del jugador… pero esto podría ser costoso, refiriéndonos al rendimiento. Una forma más óptima de hacerlo es descomponer el espacio, lo cual consiste en testear las colisiones entre objetos que se encuentren en la misma zona del espacio.

De descomposiciones del espacio existen los siguientes tipos (hay variantes… pero estos son los principales):

  • Octrees
  • Rejilla de vóxeles

Ambas soluciones son buenas, pero hay que tener cuidado en su uso. En este artículo veremos específicamente la rejilla de vóxeles.


Rejilla de Vóxeles

La definición pura y dura de voxel lo describe como un elemento volumétrico dentro de una rejilla imaginaria en el espacio tridimensional (la imagen de debajo es muy aclaradora en este sentido). Esto ya nos indica como vamos a relacionar esto con la detección de colisiones… nuestros objetos en el espacio 3D estarán virtualmente dentro de un voxel u otro, y sólo procederemos a testear la colisión en el caso en el que dos objetos se encuentren en el mismo voxel. ¿Parecido con el Quadtree? Bastante, la diferencia está en que aquí no hay voxels anidados…

Es mas, en la imagen previa, vemos una «montaña» de voxels, pero nosotros podríamos considerar que sólo tenemos un nivel de voxels, con lo cual en este caso la comprovación de si dos objetos se encuentran en el mismo voxel resulta trivial. Más que esa montaña de voxels, pasaríamos a tener lo siguiente:

Eso es «trampear» un poco con el concepto de Voxel, lo se, pero los programadores somos así 😛 El resultado es que los puntos rojos son los objetos que estarían en el espacio tridimensional vistos desde arriba, y el punto verde sería el agente del jugador. Si dividimos la pantalla en estos «voxels», sólo será necesario testear una colisión!  Como tantas otras veces, comentar que obviamente esta solución no es exclusiva para XNA, el algoritmo nos servirá para cualquier tecnología.

Implementación en XNA

Lo que a mi me gusta es que las cosas que hago sean fáciles de debugar, de modo que si hay algún bug o problema, encontrar la solución sea rápido. Como hemos dicho vamos a trabajar con una descomposición del espacio, pues qué mejor que «pintar» esa descomposición en pantalla. Esto lo haremos trabajando con primitivas (dibujaremos las líneas de esta descomposición). Para hacer esto utilizaremos la siguiente clase Grid:

Esta clase ha sido creada a partir de una modificación de un código existente en la web del Creators Club. Con esto ya tendremos la «rejilla» pintada por pantalla. El siguiente paso interesante tambien para debugar, sería mostrar en la pantalla en qué parte de esta descomposición se encuentra el agente u objeto que representa el jugador. Esto lo haremos con el siguiente método, el «cálculo» es muy sencillo y rápido como se ve:

private Point GetNumCelda(Vector3 posicionObjeto)
{
    Point numCelda;

    numCelda = new Point((int)posicionObjeto.X / anchoCelda, (int)posicionObjeto.Z / anchoCelda);

    return numCelda;
}

Además, pondremos un agente en la pantalla de forma que pueda moverlo el jugador, esto lo haremos con el código que venimos utilizando en los artículos previos (no volveré a escribirlo para no repetirme). El resultado de la ejecución es el siguiente:

Nota: En este ejemplo la descomposición se ha hecho en celdas muy pequeñas, en un videojuego las celdas podrían y deberían ser más grandes, en proporción con el tamaño de nuestros agentes, la cantidad de objetos cuyas colisiones queramos testear, etc.

Tengamos en cuenta, que para saber en qué celda se encuentra el agente, sólo esamos la posición de su «centro», no sus dimensiones. Esto quiere decir que nuestro objeto agente podría estar enmedio de dos celdas y por lo tanto podríamos considerar que se encuentra en dos celdas, y no en una, incluso en cuatro, si se encuentra en una esquina de una celda. Para no complicar la obtención de la información de en qué celdas estamos, lo que haremos es comprobar la colisión con la celda «centro» y todas las adyacentes, de la forma que indica el dibujo siguiente:

Un momento, se nos olvida algo… contra qué vamos a comprobar las colisiones? Necesitamos uno o varios modelos, que a ser posible se muevan por la pantalla, para hacerlo. Había pensado en el modelo siguiente:

Además del modelo en formato DirectX (.X) necesitaremos almacenar este en algun sitio, una buena forma de hacerlo es en una lista genérica, en la clase Game:

List<ExtendedModel> patos;

Después sólo tenemos que inicializarlos, con posiciones distintas y direcciones algo aleatorias, esto lo haremos en el método LoadContent() de la clase Game:

patos = new List<ExtendedModel>();

Model modeloPato = Content.Load<Model>(@»Modelspato»);

            for (int a = 0; a <= 5; a++)
                for (int i = 0; i <= 5; i++)
                {
                    ExtendedModel m1 = new ExtendedModel(GraphicsDevice);

                    m1.position = new Vector3(anchoCelda * i – 500 * a, 30.0f, i * 200.0f – 400 * a);
                    m1.model = modeloPato;
                    m1.scale = 300.0f;
                    m1.altura = 40;
                    m1.anchura = 40;
                    m1.speed.X += (float)rnd.Next(10) / 10;
                    m1.speed.Z += (float)rnd.Next(10) / 10;

                    patos.Add(m1);
                }

Y creo que aquí se hace necesario explicar mínimamente la clase ExtendedModel (que no es parte del Framework de XNA obviamente).

Bueno, no tiene ningún secreto, esta clase contiene principalmente un modelo de XNA del tipo Model, y lo que hace es dibujarlo por pantalla mediante el método Draw. Además le he puesto algunas propiedades como posición, velocidad, escala, etc, que me son de ayuda para hacer ejemplos rápidos de XNA, no diría que sea un modelo de codificación o diseño óptimo para un juego, así que ojo con su uso 😉

Ahora, sólo nos queda darle un poco de lógica a nuestro «juego», en el método Update() de la clase Game, pondremos el código siguiente:

// Boundingbox del agente
agente.bounding = new BoundingSphere(new Vector3(agente.position.X, agente.position.Y, agente.position.Z), agente.anchura);

// Miro donde está el agente
Point numCelda = GetNumCelda(this.agente.position);

// Lógica del pato
foreach (ExtendedModel pato in patos)
{
    pato.bounding = new BoundingSphere(new Vector3(pato.position.X, pato.position.Y * 2, pato.position.Z), pato.anchura);
    pato.rotation = pato.rotation + new Vector3(0.0f, 0.05f, 0.0f);
    pato.position += pato.speed;

    if (pato.position.Z < -600 || pato.position.Z > 600)
        pato.speed.Z *= -1;
    if (pato.position.X < -600 || pato.position.X > 600)
        pato.speed.X *= -1;

    pato.position.X = MathHelper.Clamp(pato.position.X, -600, 600);
    pato.position.Z = MathHelper.Clamp(pato.position.Z, -600, 600);

    // Inicialmente asumo que no hay colisión
    pato.boundingColor = Color.Purple;

    Point celdaPato = GetNumCelda(pato.position);

    // Miro si el pato está en la misma zona del espacio que el agente
    if (celdaPato == new Point(numCelda.X – 1, numCelda.Y – 1)
        || celdaPato == new Point(numCelda.X, numCelda.Y – 1)
        || celdaPato == new Point(numCelda.X + 1, numCelda.Y – 1)
        || celdaPato == new Point(numCelda.X – 1, numCelda.Y)
        || celdaPato == new Point(numCelda.X, numCelda.Y)
        || celdaPato == new Point(numCelda.X + 1, numCelda.Y)
        || celdaPato == new Point(numCelda.X – 1, numCelda.Y + 1)
        || celdaPato == new Point(numCelda.X, numCelda.Y + 1)
        || celdaPato == new Point(numCelda.X + 1, numCelda.Y + 1))
    {
         // Lo está? pues perfecto, comprovamos la colisión
        if (pato.bounding.Intersects(agente.bounding))
              pato.boundingColor = Color.Red; // En este caso, simplemente hacemos que el boundingSphere del pato se pinte en rojo
    }

Una de las cosas que me preocupa en esta implementación es ese If, bastante «gordo», y que además se ejecuta un montón de veces por segundo. Para que esta implementación le salga «rentable» a nuestro procesador, el «mundo 3D» cuyas colisiones queremos comprobar tiene que ser bastante grande y contener bastantes elementos a comprobar.

Os dejo con el resultado de la ejecución, y como siempre, el código.

[View:http://www.youtube.com/watch?v=n4Fx7ozxpWQ:550:0]

Llevar el ejemplo «más allá»

Lo interesante sería ahora dibujar otra grid verticalmente, y tener en cuenta la dimensión Y a la hora de calcular las celdas adyacentes, esto sería implementando el concepto descomposición del espacio voxels más «real».

[Webcast] Shaders en XNA

Javier Cantón es MVP de DirectX y XNA, así como coordinador del XNA Community. Tendremos la suerte de contar con él en esta conferencia online el próximo jueves 24 de septiembre. En definitiva, es una charla que los fanáticos de XNA no nos podemos perder.

La conferencia constará de una introducción a la programación en GPU usando Shaders, y como se puede aplicar estas técnicas desde XNA usando el lenguaje HLSL para enriquecer visualmente nuestros videojuegos. Se centrará en las técnicas que trabajan en espacio de imagen (Image Space) ya que estas pueden ser añadidas a un proyecto fácilmente sin influir negativamente en el rendimiento de un juego. Se mostrará el funcionamiento de las técnicas de este tipo más usadas en los últimos años en el sector de los videojuegos.

Fecha y hora: Jueves, 24 de septiembre de 2009 19:00 (GMT +1 Madrid, París)
Duración: 60 minutos
Modalidad: Online

Registro y acceso:
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032427061&EventCategory=4&culture=es-ES&CountryCode=ES

[XNA] Descripción de trayectorias con interpolaciones cúbicas

En el post anterior vimos las interpolaciones lineales, una forma muy correcta de desplazar elementos en nuestro «mundo» tridimensional. Lo «malo» de estas, es que la trayectoria entre keyframe y keyframe es una línea recta, y eso hace que estas trayectorias puedan parecer algo «duras» -aunque en ocasiones eso puede ser precisamente lo que estamos buscando-. No obstante, existe una forma más «suave» de desplazamiento. Esto se puede ver claramente con la siguiente representación gráfica:

Nos hemos hecho ya a la idea de que existen distintos tipos de interpolación, vamos a ver ahora la interpolación por medio de spline (a partir de ahora splines…). Los splines se utilizan para aproximar formas complicadas en forma de curvas. Su cálculo es rápido… por lo que son ideales para la programación de juegos. De estos hay tres tipos:

  • Splines cúbicos
  • Splines Bézier
  • B-Splines


Aplicación en XNA

El siguiente código está basado en el fantástico tutorial de Kyle Hayward acerca de interpolacion de movimientos para el desplazamiento de cámaras (que a su vez está parcialmente extraido de una implementación en JAVA).

Respecto el código del ejemplo anterior, deberemos modificar muy poco código, sólo la forma en la que se calcula este tipo de interpolación, así sustituiremos el código del método GenerarInterpolacion() por el siguiente:

// Cada punto de un spline cúbico se ve afectado por cualquier otro punto
// así que será necesario generar las ecuaciones cúbicas para cada punto de control
// teniendo en consideración la curva completa. De eso se encarga el método calculateCubicSpline

Vector3[] positions = new Vector3[this.keyFrames.Count];

for (int i = 0; i < this.keyFrames.Count; i++)
{
    positions[i] = this.keyFrames[i].position;
}

Spline[] pos_cubic = calculateCubicSpline(this.keyFrames.Count – 1, positions);

for (int i = 0; i < this.keyFrames.Count – 1; i++)
{
    for (int j = 0; j < this.steps; j++)
    {
        float k = (float)j / (float)(this.steps – 1);

        Vector3 center = pos_cubic[i].GetPointOnSpline(k);

        detailedInterpolation.Add(new Spline(center));
    }
}

La chicha matemática está en el método calculateCubicSpline(), que deberemos añadir a la clase SplinesManager:

/// <summary>
/// Calcula el spline cúbico de los puntos de controls
/// Los segmentos son representados como: a + b*u + c*u^2 + d*u^3
/// Algoritmo obtenido de:
http://www.cse.unsw.edu.au/~lambert/splines/
/// </summary>
/// <param name=»n»>el número de puntos de control</param>
/// <param name=»v»>array de vectores</param>
/// <returns></returns>

private Spline[] calculateCubicSpline(int n, Vector3[] v)
{
    Vector3[] gamma = new Vector3[n + 1];
    Vector3[] delta = new Vector3[n + 1];
    Vector3[] D = new Vector3[n + 1];
    int i;
    /* We need to solve the equation
     * taken from:
http://mathworld.wolfram.com/CubicSpline.html
       [2 1       ] [D[0]]   [3(v[1] – v[0])  ]
       |1 4 1     | |D[1]|   |3(v[2] – v[0])  |
       |  1 4 1   | | .  | = |      . |
       |    ….. | | .  |   |      . |
       |     1 4 1| | .  |   |3(v[n] – v[n-2])|
       [       1 2] [D[n]]   [3(v[n] – v[n-1])]
      
       by converting the matrix to upper triangular.
       The D[i] are the derivatives at the control points.
     */

    //this builds the coefficients of the left matrix
    gamma[0] = Vector3.Zero;
    gamma[0].X = 1.0f / 2.0f;
    gamma[0].Y = 1.0f / 2.0f;
    gamma[0].Z = 1.0f / 2.0f;

    for (i = 1; i < n; i++)
    {
       gamma[i] = Vector3.One / ((4 * Vector3.One) – gamma[i – 1]);
    }
    gamma[n] = Vector3.One / ((2 * Vector3.One) – gamma[n – 1]);

    delta[0] = 3 * (v[1] – v[0]) * gamma[0];
    for (i = 1; i < n; i++)
    {
        delta[i] = (3 * (v[i + 1] – v[i – 1]) – delta[i – 1]) * gamma[i];
    }
    delta[n] = (3 * (v[n] – v[n – 1]) – delta[n – 1]) * gamma[n];

    D[n] = delta[n];
    for (i = n – 1; i >= 0; i–)
    {
        D[i] = delta[i] – gamma[i] * D[i + 1];
    }

    // now compute the coefficients of the cubics
    Spline[] C = new Spline[n];
    for (i = 0; i < n; i++)
    {
        C[i] = new Spline(v[i], D[i], 3 * (v[i + 1] – v[i]) – 2 * D[i] – D[i + 1],
           2 * (v[i] – v[i + 1]) + D[i] + D[i + 1]);
    }
    return C;

Si ejecutamos este código, el resultado va a ser que ahora el agente se mueve por el circuito tomando las curvas suavemente.

[View:http://www.youtube.com/watch?v=jLqfB6W0WDg:550:0]

 

[XNA] Descripción de trayectorias mediante interpolaciones lineales

La interpolación lineal es un método sencillo para describir trayectorias entre distintos puntos, usando polinomios lineales. Esto, aplicado a la programación gráfica, significa que nos será muy útil para trazar trayectorias a objetos, cámaras o luces de modo práctico y rápido.

[View:http://www.youtube.com/watch?v=TejebjO-8ts:550:0]

Para describir las «curvas» se utiliza la definición de unos pocos puntos, a los que llamaremos keyframes. Entre ellos, mediante la técnica matemática de los splines, realizaremos la interpolación. El siguiente dibujo ilustra más lo explicado:

En el dibujo, los puntos rojos representan los keyframes, entre ellos, realizaremos una interpolación lineal que nos dará la posición de los puntos verdes, lo cual resultará finalmente en el trazado de la trayectoria que estamos buscando.

Cómo se calcula la interpolación lineal entre dos puntos conocidos

La interpolación lineal consiste en trazar una recta que pasa por (x1,y1) y (x2,y2), y = r(x) y calcular los valores intermedios según esta recta en lugar de la función y = f(x)

Saltándonos un poco el proceso matemático (esto se puede consultar directamente en la wikipedia), tenemos que:

Lo mejor de todo es que XNA ya tiene una función propia para interpolar, llamada Lerp (método estático dentro de la clase Vector3). Eso sí, siempre es bueno saber de dónde vienen los números… ahí queda la fórmula pues.


Cómo aplicaremos estos cálculos a nuestro videojuego

Si recoráis en el «episodio anterior» (siempre quise decir eso)  vimos como definir un sistema de checkpoints sobre un circuito. Lo que ahora haremos, pues, va a ser mover el agente que utilizamos en ese ejemplo a través del circuito, de forma «autónoma», de forma que atraviese todos los checkpoints. Esto lo haremos, por supuesto, ¡mediante la trayectoria descrita en la interpolación lineal! (y es que no hay nada más bonito que ver la aplicación real de las matemáticas).

Lo que pasa, es que como somos muy valientes, vamos a complicar un poco más el circuito, para que se vea claramente que no hay «trampa». El anterior circuito era circular, y en un circuito así no haría falta complicarnos la vida con las interpolaciones… nos bataría con la parametrización de una circunferencia.  Pero la vida no acostumbra a ser tan sencilla como lo es una circunferencia, y no conozco ningún circuito que sea tan simple. Así que utilizaremos un circuito más complicado, como ya he dicho.

Para ello, lo primero que haremos será abrir el proyecto anterior (el código está definido y explicado en el ejemplo de los checkpoints, que ya he mencionado), y definiremos nuevos checkpoints para el nuevo circuito (así es como se ve renderizado desde 3DMax):

 

Definiremos los checkpoints del siguiente modo:

checkPointsManager = new CheckPointsManager();

CheckPoint check1 = new CheckPoint(new Vector3(0, -100, -550), 75);
CheckPoint check2 = new CheckPoint(new Vector3(525, -100, -550), 75);
CheckPoint check3 = new CheckPoint(new Vector3(800, -100, -100), 75);
CheckPoint check4 = new CheckPoint(new Vector3(1200, -100, -200), 75);
CheckPoint check5 = new CheckPoint(new Vector3(1350, -100, -600), 75);
CheckPoint check6 = new CheckPoint(new Vector3(1000, -100, -1000), 75);
CheckPoint check7 = new CheckPoint(new Vector3(-1000, -100, -750), 75);
CheckPoint check8 = new CheckPoint(new Vector3(-1250, -100, -200), 120);
CheckPoint check9 = new CheckPoint(new Vector3(-500, -100, 0), 75);

checkPointsManager.Add(check1);
checkPointsManager.Add(check2);
checkPointsManager.Add(check3);
checkPointsManager.Add(check4);
checkPointsManager.Add(check5);
checkPointsManager.Add(check6);
checkPointsManager.Add(check7);
checkPointsManager.Add(check8);
checkPointsManager.Add(check9);

Esto situará algunos checkpoint en el circuito.

Diseño de un sistema que calcule las interpolaciones lineales entre los keyframes

Ahora debemos pensar en un diseño que nos permita almacenar los Keyframes y la interpolación entre los mismos. Yo os propongo un SplinesManager, cuya estructura es muy similar al checkPointsManager:

No acostumbro a usar las clases anidadas, pero en este caso me lo he permitido, ya que la clase Spline sólo la usará el SplinesManager. Por cierto… esta interpolación lineal nada tendría que ver con los splines… pero lo dejo así para el próximo artículo-tutorial, en que se verá otro tipo de interpolación más sofisticada.

De esta clase destacan en importancia la lista de keyframes:

        /// <summary>
        /// Keyframes principales de la trayectoria
        /// </summary>

        List<Spline> keyFrames;

        /// <summary>
        /// Posición intermedia a cada keyframe
        /// </summary>
       
List<Spline> detailedInterpolation;

Los comentarios son bastante aclarativos. La primera lista contiene los keyframes que introducirá el programador. La segunda lista es más detallada, contendrá la interpolación detallada entre todos estos keyframes. Desde la clase Game definiremos nuestros keyframes (en el código adjunto existen más keyframes, pongo sólo estos por brevedad):

            SplinesManager.Spline spline1 = new SplinesManager.Spline(new Vector3(0, -200, -650));
            SplinesManager.Spline spline2 = new SplinesManager.Spline(new Vector3(300, -200, -750));

            splinesManager.AddKeyframe(spline1);
            splinesManager.AddKeyframe(spline2);

¿Cómo calculamos la interpolación lineal entre estos keyframes?

En el principio del artículo hemos visto que existe un modo matemático de obtener la información. En XNA, Microsoft nos ha simplificado la vida con la función Vector3.Lerp de la que ha he hablado. El método GenerarInterpolacion() se llamará una vez se hayan añadido todos los keyframes (y sólo se ejecutará una vez). Generará la interpolación detallada con el nivel de detalle especificado en la variable steps. En este caso le he dado un valor de 100. Esto quiere decir que entre keyframe y keyframe habrá 100 posiciones definidas. En este caso el nivel de detalle es alto… podría ajustarse dependiendo del tipo de juego y la velocidad a la que queremos que se muevan los objetos.

        /// <summary>
        /// Genera interpolación detallada entre los keyframes
        /// </summary>

        public void GenerarInterpolacion()
        {
            detailedInterpolation.Clear();

            for (int i = 0; i < keyFrames.Count-1; i++)
            {
                for (int j = 0; j <= this.steps; j++)
                {
                    Vector3 posicion;

                    // Método builtin en el Vector3 que realiza una interpolación lineal entre dos vectores
                    // Se «come» el trabajo matemático por nosotros
                    Vector3.Lerp(ref keyFrames[i].position, ref keyFrames[i + 1].position, (float)j / (float)(this.steps – 1), out posicion);

                    // Guardamos detalle
                    detailedInterpolation.Add(new Spline(posicion));
                }
            }
        }

Para debugar bien, dibujaremos los checkpoints en pantalla, eso nos facilitará bastante la vida. El método Draw contiene el siguiente código:

        /// <summary>
        /// Pinta los keyframes en pantalla (para debug)
        /// </summary>
        /// <param name=»projection»></param>
        /// <param name=»view»></param>
        public void Draw(Matrix view, Matrix projection)
        {
            foreach (Spline spline in keyFrames)
            {
                spline.Draw(projection, view);
            }
        }

Como vemos, lo único que hace es recorrer todos los keyframes y dibujarlos por pantlla (pintando un boundingSphere en su lugar). La ejecución dará lugar a nuestro agente recorriendo el circuito, pasando por todos los checkpoints definidos previamente:

Podéis ver un vídeo del resultado de la ejecución del código aquí. El código, como siempre, está disponible para descargar en este enlace.

En un próximo artículo me gustaría hacer que el agente se desplace de una forma menos lineal, haciendo cierta curva entre keyframe y keyframe.

 

[XNA] Sistema de checkpoints 3D

¿Cómo funcionan los juegos de carreras? De una forma muy compleja xD La pregunta más bien debería ser: ¿Cómo validamos que los coches recorren el circuito sin hacer trampas? Posible respuesta: mediante un sistema de checkpoints. Es decir, cada cierta distancia, validar que el vehículo del jugador está avanzando en la dirección correcta, y que además no se ha salido de la carretera para tomar atajos «excesivos». Con atajos, me refiero (ver la imagen de debajo), a que el jugador no vaya del punto 1 al 4 por el centro del circuito y gane la carrera pasándose de listillo… este sistema de checkpoints también puede ser útil para indicarle al jugador si va en dirección contraria.

Implementación

Vamos a centrarnos únicamente en el sistema de checkpoints (quitando por unos momentos importancia a renders, cámaras, etc). Dicho esto, el código resulta bastante sencillo. Tendremos dos clases: CheckPoint y CheckPointsManager. Un CheckPoint no es más que un boundingSphere y un ID (para distinguir unos checkpoints de otros y tenerlos ordenados). Contiene además un método de renderizado, que utilizaremos para «pintar» los checkpoints en pantalla -esto sólo tiene sentido si estamos debugando, ya que durante la ejecución del juego los checkpoints deberían ser «invisibles» para el jugador-.

El CheckPointsManager contiene todos los CheckPoint de un circuito (en el atributo List<Checkpoint> checkpoints). Además indica si hemos completado el circuito (bool finished), así como información de uso interno para la lógica de la clase, como por ejemplo cuál ha sido el último checkpoint válido o incorrecto que hemos atravesado.

¿Cómo utilizamos estas clases? Lo primero será, en la clase Game (clase principal del juego) inicializar la clase CheckpointsManager, e irle añadiendo checkpoints. Esto es tan fácil como…:

checkPointsManager = new CheckPointsManager(GraphicsDevice); 

CheckPoint check1 = new CheckPoint(new Vector3(0, -100, 300), 100);
CheckPoint check2 = new CheckPoint(new Vector3(400, -100, 200), 100);
CheckPoint check3 = new CheckPoint(new Vector3(700, -100, -300), 100);
CheckPoint check4 = new CheckPoint(new Vector3(400, -100, -1000), 100);
CheckPoint check5 = new CheckPoint(new Vector3(0, -100, -1200), 100);
CheckPoint check6 = new CheckPoint(new Vector3(-400, -100, -1000), 100);
CheckPoint check7 = new CheckPoint(new Vector3(-700, -100, -300), 100);
CheckPoint check8 = new CheckPoint(new Vector3(-400, -100, 200), 100);

checkPointsManager.Add(check1);
checkPointsManager.Add(check2);
checkPointsManager.Add(check3);
checkPointsManager.Add(check4);
checkPointsManager.Add(check5);
checkPointsManager.Add(check6);
checkPointsManager.Add(check7);
checkPointsManager.Add(check8);

 Como se puede observar, un checkpoint tiene un vector de posición 3D (X, Y, Z), y un valor numérico, que es el radio del checkpoint (el espacio físico que ocupará el boundingbox). Con esto tenemos el CheckPointsManager inicializado… Este código de inicialización lo pondríamos en el método Initialize de la clase Game (¿evidente, no?)

Con esto tenemos los checkpoints del circuito… pero nos falta lo más importante, el agente que el jugador va a desplazar entre los checkpoints. Y además también añadiremos un sencillo circuito, para que se vea más claro el desplazamiento de los objetos en pantalla. Fácil, fácil, en la clase principal del juego (Game), declaramos estos objetos:

ExtendedModel agente;
ExtendedModel circuito;

 Después sólo nos queda inicializar ambos…

 // Iniciamos el agente

agente = new ExtendedModel();
agente.position = new Vector3(0.0f, 0.0f, 300.0f);
agente.scale = 10.0f;
agente.fAlt_Avatar = 80.0f;
agente.fAmple_Avatar = 80.0f; 

// Inicializamos el circuito
circuito = new ExtendedModel();
circuito.scale = 10.0f;
circuito.position = new Vector3(0, -300, -500) ;
circuito.rotation = new Vector3(MathHelper.ToRadians(-90), 0, 0);

Un momento… ¿que es el objeto ExtendedModel? Es una clase personalizada que contiene un objeto de tipo modelo, así como información adicional que nos es de interés como la posición, escala y rotación. Es una forma de simplificar el posicionamiento y rotación de los modelos. La definición completa de la clase es la siguiente:

Una vez inicializados estos objetos, habrá que cargar los modelos de cada uno de ellos en el método LoadContent de la clase Game. Trivial:

circuito.model = Content.Load<Model>(@»Modelscircuito»);
agente.model = Content.Load<Model>(@»Modelsbola»);

Para los más profanos de XNA, comentar que estos modelos han sido diseñados y exportados con 3DMAX 2009, y grabados al formato FBX (que junto al formato X, son los formatos de modelos que por defecto «entiende» el Content Pipeline de XNA). Aunque cuando podáis ver como son los modelos en este ejemplo (una simple esfera y una variación de toroide), veréis que no es necesario ser un gran diseñador 3D ni dominar MAX para modelar nuestras pequeñas pruebas como programadores.

 Ahora lo interesante es que el agente se desplace por la pantalla con los movimientos del cursor de teclado.. (o más específicamente, con las letras A/S/D/W/X). Para ello, introduciremos el siguiente código en el método Update() de la clase Game.

KeyboardState kbState = Keyboard.GetState();

if (kbState.IsKeyDown(Keys.A))
{
    agente.position.X -= 4.0f;
}
if (kbState.IsKeyDown(Keys.D))
{
    agente.position.X += 4.0f;
}
if (kbState.IsKeyDown(Keys.W))
{
    agente.position.Z -= 4.0f;
}
if (kbState.IsKeyDown(Keys.S))
{
    agente.position.Z += 4.0f;
}

 Se diría que hasta aquí todo es «normal», hace rato que no hablamos de los checkpoints… pues volvamos a ellos. La clase CheckPointsManager tiene un método Update(). Este método refresca la lógica de los checkpoints, y es la que indica si nuestro agente se está desplazando correctamente o no…  (este código habrá que ejecutarlo desde el método también llamado Update de la clase Game):

 this.checkPointsManager.Update(this.bounding);

¿Qué es el parámetro bounding que acepta el método update? Es un BoundingSphere, utilizado para la detección de colisiones entre el agente y los checkpoints. Los boundingBox y boundingSpheres son muy utilizados para la detección de colisiones en 3D, dan para varios artículos ellos solos… muy resumidamente, los BoundingBoxes y BoundingSpheres son objetos que el jugador no ve, que engloban algo cuyas colisiones queramos detectar (por ejemplo un checkpoint y el jugador). Entonces es muy fácil detectar si hay colisión entre dos BoundingSpheres o BoundingBox, ya que estas clases tienen un método Intersects, que aceptan otro objeto del mismo tipo como parámetro. Una imagen vale más que mil palabras:

 

La primera imagen muestra dos boundingSpheres sin colisión (una de ellas «encapsula» a nuestro agente), y la segunda muestra los mismos boundingsphere con colisión (hemos desplazado el agente, y su boundingsphere con él).

Volviendo al tema que nos ocupa… los checkpoints. La llamada al método Update de la clase CheckpointsManager devolverá un valor bool, que nos permetirá saber si el jugador se mueve en dirección adversa o reversa. Aquí podremos hacer con esta información lo que queramos (una opción sería descalificar al jugador si va en sentido contrario). En nuestro caso mostramos unos mensajes por pantalla dependiendo de este resultado:

// Control de los checkpoints
bool vamosBien;

vamosBien = this.checkPointsManager.Update(this.bounding);

if (!this.checkPointsManager.finished)
   
if (vamosBien)
        mensajePantalla = «Circulas bien!»;
      else
        mensajePantalla = «Circulas en dirección contraria!!!»;
else
    mensajePantalla = «Eres el amo, has completado el circuito!»;

 
Ya solo nos queda dibujar todo esto por pantalla… esto lo haremos colocando el código siguiente en el método Draw() de la clase Game.

GraphicsDevice.Clear(Color.CornflowerBlue);

agente.Draw(view, projection);
checkPointsManager.Draw(view, projection);
circuito.Draw(view, projection);

boundingRender.Render(bounding, GraphicsDevice, view, projection, Color.Purple);

base.Draw(gameTime); 

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.FrontToBack, SaveStateMode.SaveState);

int ultimoCheckpoint = 0;

if (checkPointsManager.lastValidCheckpoint != null)
    ultimoCheckpoint = checkPointsManager.lastValidCheckpoint.ID;

spriteBatch.DrawString(this.fuente, «Último checkpoint: « + ultimoCheckpoint.ToString(), new Vector2(10, 10), Color.Orange);
spriteBatch.DrawString(this.fuente, mensajePantalla, new Vector2(10, 40), Color.Orange);
spriteBatch.End();

Esta clase de CheckPointsManager viene a ser pues nuestra «caja negra» de los checkpoints. Podemos añadirle cuantos checkpoints queramos, y olvidarnos de la gestión de los mismos, pues esta clase se encargará de ello.

El resultado final es el siguiente http://www.youtube.com/watch?v=96tuLF6CCW4:

 

 

El código se puede descargar mediante este enlace.

[Webcast] Introducción al desarrollo web con ASP.NET

El próximo 22 de septiembre daré un webcast para el UOC DotNetClub, el objetivo del mismo es introducir tanto a programadores noveles, desconecedores del desarrollo web (o que desarrollen con otras tecnologías distintas a las de Microsoft) como diseñadores, a la tecnología ASP.NET.

En esta charla se dará a conocer tanto a diseñadores como programadores de
otras tecnologías el mundo del desarrollo web con la tecnología de
Microsoft: ASP.NET. Conoceremos el entorno de desarrollo gratuito
Microsoft Visual Studio. Veremos como funcionan los webforms, los
controles web, el framework AJAX, enlace a datos, XML, Cookies,
validación de formularios, MasterPages, etc.

URL de registro al evento (webcast online): http://uoc.dotnetclubs.com/eventos


[3D] API O3D de Google para navegadores de Internet

Google está luchando por convertir su API O3D en el estándar del 3D para los navegadores. Había visto otros intentos parecidos pero este sin duda los supera a todos con creces. Compatible con sistemas Windows, Linux y Mac, sólo requiere la instalación de un pequeño plugin para funcionar. El código está basado en una extensión de JavaScript . Los contenidos del sistema pueden ser importados desde distintas aplicaciones, como 3D Max, Maya y SketchUp.

Aunque nadie sabe cual será el futuro de esta tecnología, las posibilidades son múltiples… desde representación de escenas 3D a videojuegos, y lo que la imaginación nos permita… ahí van algunas capturas de los ejemplos. Realmente sorprendente.

[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…

[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.

[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 console Windows-based development computer Zune
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.