[Webcast] Introducción a C#

El próximo 2 de octubre de 2009, a las 12:00 del mediodía, Jose Ibáñez, coordinador del UOC DotNetClub, realizará una charla introductoria al lenguaje de programación C#. Se trata de una charla/taller ideal para todo aquel que quiera introducirse en el desarrollo de aplicaciones bajo la tecnología Microsoft. Net

En esta charla se introducirá a todos los asistentes el lenguaje C# de Microsoft partiendo desde cero, tratando de ver los aspecto más importantes y necesarios para iniciarse en dicho lenguaje. Veremos el manejo de Variables, Constantes, Arrays,Expresiones, Operadores, Sentencias, Clases, Estructuras, etc.
 

Para registraros, podéis utilizar este enlace:
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032427065&Culture=es-ES

[Evento] Videojuegos: tendencias y oportunidades

El próximo 27 de octubre viviremos en Madrid un prometedor congreso sobre el negocio del videojuego, ahora que esta industria ya está reconocida por el ministerio de cultura, y es una posible receptora de subvenciones y otros tipos de apoyos y ayudas. El evento está organizado por Intereconomía, y colabora, entre otras instituciones DOID, la asociación de desarrolladores de ocio interactivo.

Para más información e inscripciones, podéis visitar la web oficial del evento.

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