martes, 23 de octubre de 2007

Editando al Editor

Ha pasado bastante tiempo desde que escribí la última vez. En parte porque Reumatología me tuvo bastante ocupado hace unas semanas; y también porque escribir en este Blog es realmente mucho más rápido que avanzar en el juego. A pesar de todo, he trabajado bastante en el proyecto, y hoy publico los resultados.

En estricto rigor, del juego no he tocado ni una sola línea de código. Todos los esfuerzos están centrados, por ahora, en el editor de código. La última vez que hablé de esta herramienta, era aún una utilidad pequeña que mostraba sólo texto y tenía poca funcionalidad (aunque ese poco supuso un enorme aumento en la velocidad con que pude diseñar el nivel). De todas formas, el paso siguiente consistió en agregar nuevas funciones y obviamente, una interfaz visual más amigable.

El plan inicial era mostrar el nivel en plano, es decir, representar cada subunidad por una grilla que tuviera la textura correspondiente y algún número que mostrara la elevación. Mediante esta interfaz debería poderse modificar la textura a nivel de subunidad, y la elevación a nivel de vértice. Con esto, el editor se volvería muy fácil y rápido de usar. El problema que siempre llega después de la iluminación es "Ya está la idea, ¿pero ahora cómo la implememento?"

Recordando viejos tiempos

Algunos años atrás, hice un juego de fútbol en tablero, así como también un Battleship; en C Sharp, y ambos programas utilizaban una clase "Tablero Gráfico" genérica, que me sería sumamente útil para mis propósitos. Esa clase la creó un ayudante del curso, y era capaz de detectar los clicks, traducir las coordenadas en filas y columnas, marcar con colores las casillas, etc. Lo que yo necesitaba era algo parecido, pero con mayor funcionalidad, pues en vez de colores necesitaba texturas, además de discriminación entre vértice y área (para seleccionar la subunidad entera o sólo el vértice), y algunas cosas extras como capacidad de mostrar texto y lo que se me fuera ocurriendo en el camino. (Al momento de imaginar el programa me pongo a dibujar sobre un papel, y luego veo como implementarlo. Al momento de programar van cambiando las ideas y todo termina bastante distinto a como se planeó, es por eso que desde el inicio el diseño debe ser lo más flexible posible). Como ahora no trabajaba en C Sharp (Tanto el Avioncitos3D, como el editor de nivel están hechos en Visual Basic 6.0) no podía simplemente buscar la clase TableroGrafico y utilizarla. A lo más podría haberla leído y "traducido" a Visual Basic, pero al final, terminé decidiendo crear mi propia grilla desde cero, con las funcionalidades exactas que necesitara. Ahora bien, el C Sharp tiene funciones gráficas bastante más poderosas que las del Visual, ... La realidad es que las del Visual son horribles. Esto me dejaba sin la autosuficiencia del C Sharp para manejar los gráficos por sí solo, por tanto en mi caso debí buscar un "partner gráfico" para complementar al escueto Visual Basic.

Lo primero que pensé fue BitBlt... Es cierto que es bastante viejo, lento, etc. Pero se ve bastante bien para transferir los bitmaps de las texturas al formulario. De hecho, cuando imaginé el sistema, siempre tuve en mente a BitBlt para implementarlo.

Lo primero que hice fue leer sobre BitBlt de nuevo. Habían pasado muchos años desde que lo usé por última vez y me dediqué de lleno al DirectX. Cuando ya resfresqué la memoria, me di cuenta que no sería tan bueno como creía. El problema principal era que necesitaba copiar la imagen desde un Device Context, en el caso más simple un control Picture, pero nunca directamente desde un archivo. Al parecer la solución más simple sería usar DirectX7 y trabajar con Surfaces y BltFast, pero de esto no sabía mucho y no me agradaba la idea de mezclar tantas APIs en un solo proyecto. Al final me decidí por DirectX8, usando Transformed and Lit Vertices, tal como hice con el HUD en avioncitos.

Editando el Editor

Decidía crear mi grilla como una ventana de 8 por 8 subunidades, que se pudiera desplazar sobre el nivel y fuera mostrando información de elevación y textura de las 64 subunidades contenidas. Cuando dibujé los cuadros que representaríasn las subunidades, intenté hacerlo mediante TriangleStrips en vez de TriangleLists, lo que me ahorró muchos muchos vértices. Pensé que sería la solución ideal para rediseñar el Sistema de terreno del avioncitos (que usa 6 vértices por subunidad!!! una cantidad enorme, y se está quedando con frame rates muy muy pequeños). Sin embargo, al usar TriagleStrips surge el problema para mapear las texturas, que ya no podrían ser independientes, lo que atentaría contra la flexibilidad y realismo del juego. Por esta razón, decidí quedarme con los TriangleList y solucionar de otra forma (y en otro momento) el problema del Frame Rate.

Después de algunos días de trabajo (¿dos o tres?), el editor quedó con su "grilla de subunidades" operativa y una funcional adicional: Un previsualizador 3D del terreno. Usé la misma lógica del sistema de terreno del avioncitos y creé una ventana navegable que permite moverse por el terreno y visualizar en 3 dimensiones y de manera inmediata, los cambios hechos al archivo de terreno, incluso sin haber escrito aún en éste (Trabaja sobre una copia en la RAM, que sólo se graba al archivo en el disco cuando se está conforme con el resultado).

Hoy decidí subir algunos snapshots del Editor de terreno, que si bien no está totalmente funcional (Le faltan las herramientas para manejar texturas y más funcinalidad respecto a las elevaciones), de todas formas funciona y ya está siendo de utilidad para el diseño del juego.



La imagen anterior muestra la interfaz actual del editor, con la ventana de previsualización 3D a la derecha y el archivo de elevación en modo texto a la izquierda. La barra de menús permitió agregar comandos de edición ahorrando botones en la pantalla. En la vista 3D la cámara se puede mover libremente con los controles de posición.


Esta imagen muestra al editor en modo "diseño de texturas". En la ventana de la izquierda se encuentra representado en plano un trozo de la geometría, para así editar las texturas de cada subunidad. Nótese como las bandas blancas, en la visión plana, se corresponden con las que aparecen en el cerro visible en la previsualización 3D. La edición de los patrones de textura se refleja inmediatamente en la geometría 3D.


Este es un ejemplo de una operación que sin el editor sería muy complicada. Vamos a copiar el cerro indicado con la flecha y lo pegaremos en la ubicación de la marca blanca, de modo que ésta sirva como textura para tapizarlo. Para esta operación se recurre a la función Copiar/Insertar del menú Edición.


Aquí vemos el resultado de la operación: Una copia del cerro original (al fondo), que utiliza la textura de la marca blanca donde fue creado. Este patrón de texturas podrá editarse fácilmente en el modo de edición de texturas.

Esta es una muestra inicial de la evolución del editor, que pasó se ser una ventana simple con algunos botones; a una interfaz rica, con menús y vistas 3D. En el futuro pretendo mejorar la interfaz gráfica de edición de elevacion y texturas, para que crear terrenos llamativos se vuelva una tarea simple. ¡El proyecto sigue avanzando!

miércoles, 26 de septiembre de 2007

Editor, Cámara, Acción!!

La última vez que escribí en este blog, estaba en Coquimbo celebrando las fiestas patrias. En ese entonces planifiqué que el siguiente paso en Avioncitos 3D sería la creación de un editor de terreno. De esta forma, cuando volví comencé a trabajar de inmediato en el editor.

El concepto de "Editor de..." corresponde a un programa externo al juego, que permite crear y modificar de manera relativamente fácil algún archivo que utilice el programa principal. En este caso es un editor de los archivos que guardan los ElevationMap, los cuales hasta ahora eran creados a mano en bloc de notas (mspaint, bloc de notas... no es claramente lo último en tecnología... al parecer son herramientas demasiado básicas para crear un juego 3D, aunque hasta ahora han funcionado). Hay que recordar que inicialmente el Sistema de terreno creaba el relieve basado en valores de variables que eran modificables sólo durante el diseño del juego, ya que formaban parte del código mismo del programa. En una segunda etapa, agregué soporte para cargar estos valores desde un archivo de texto externo, el cual guardaba todos los datos de elevación para un terreno en particular (ElevationMap), lo que permitía la edición desde fuera del programa, la creación de múltiples archivos, la posibilidad de compartirlos, editarlos sin tocar código, etc. Este sistema es el de elección para un juego de este tipo, ya que en teoría permitiría la adición de nuevos terrenos sólo mediante la incorporación de nuevos archivos de texto.
Hasta que volví de Coquimbo, todo funcionaba así. El problema más importante era que los archivos de elevación seguían siendo simples archivos de texto, por lo que toda la información que contenían no era más que números en un formato especial, que luego el Avioncitos 3D interpretaba como terreno. Debido a esto, los archivos eran complicados de editar, ya que requerían bastante imaginación para concebir a los números como una imagen, y por otra parte, no permitía manipulaciones más complejas como copiar un cerro completamente y pegarlo en otra parte del terreno, etc. De esta forma, hacer el terreno completo a mano, metro por metro, sería una tarea demasiado engorrosa y con resultados poco agradables estéticamente. Debido a esto surgió la necesidad de un programa con una interfaz más amigable, el cual fuese capaz de interpretar la información de los archivos en algún tipo de imagen, y permitiera manipulación más compleja de los datos.

A la fecha el editor de terreno está aún lejos de su funcionamiento planificado, pero ya posee funciones básicas como cargar un archivo de terreno, guardar una copia, mostrar el archivo completo o una fracción deseada, modificar directamente los datos (mediante una interfaz de texto) y copiar y pegar unidades de terreno, lo que facilitó enormemente la creación de áreas más extensas de terreno sin la necesidad de definir cada elemento individualmente. A la fecha también cuenta con herramientas para reemplazar masivamente áreas de terreno y mezclar distintas zonas a elección. De momento todo sigue operando en modo texto y botones, pero la interfaz gráfica con arrastrar y soltar, y funciones todavía más intuitivas está en desarrollo.

Cámaras

Gracias al aporte de la nueva herramienta, me fue posible crear un terreno algo más elaborado (sin reemplazar el anterior), para probar la nueva funcionalidad del avioncitos 3D propiamente tal: Las cámaras.

En un juego 3D, es de suma importancia el sistema de cámaras. A diferencia de las versiones bidimensionales, donde la visión suele ser fija, en un entorno tridimensional ocurren eventos en todas partes, que no pueden ser visualizados si no existe la posibilidad de cambiar el punto de vista. Básicamente la cámara corresponde al lugar ficticio desde donde el usuario mira al mundo 3D, la dirección hacia la que mira y algunos otros datos como el campo visual, la distancia máxima de visión, etc. Aparte de estos datos básicos, la cámara en los juegos 3D debe cumplir otra función: moverse "inteligentemente".
Hasta antes de crear el sistema de cámaras, el juego contaba sólo con una cámara básica que yo podía mover a voluntad (sin cámara no se vería nunca nada). Gracias a esto logre tomar todos los Snapshots desde ángulos distintos, y funcionaba bien hasta que el avión comenzó a moverse. Obviamente, el jugador debe preocuparse de mover su avión, pero la tarea de mover la cámara no le corresponde. Es por esto que la cámara debe seguir automáticamente la acción, manteniendo una visión óptima para que la jugabilidad no se vea limitada. Todo esto debe ser lo suficientemente autónomo para que casi pase desapercibido por el jugador, pero debe a la vez permitir la modificación de algunos parámetros para que el jugador tenga control sobre la cámara en cada momento. Claramente hay situaciones donde una posición de cámara es superior a otra, por lo que debe permitirse este nivel de personalización.

He llamado "programas de cámara" a 5 modos distintos en que la cámara se comporta según lo que ocurra en el juego. Esto da lugar a 5 tipos de cámara elegibles, todos con algún parámetro configurable por el usuario según necesidad.
De las 5 cámaras posibles, 4 corresponden a vistas externas del avión, mientras que uno es una vista interna desde la cabina del avión (vista de HUD).
Los 4 tipos de vistas externas corresponden a cámaras que siguen automáticamente al avión, ya sea trasladándose o rotando sobre su propio eje, de modo de no perderlo de vista. Según el tipo de cámara, el programa respectivo determinará la acción de la cámara para mantener la vista dentro de los parámetros configurables, como son la distancia al avión en cada uno de sus ejes, la posicion relativa, el radio máximo de rotación, etc. De esta forma, la cámara permite, por ejemplo en modo fijo, mantenerse siempre atrás del avión a una distancia fija, que puede ser modificada por el usuario, de modo de siempre tener la vista al frente. Por otra parte, el modo libre permite elegir mover la cámara para elegir una vista del avión arbitrariamente, para que luego la cámara mantenga esa misma visión del avión, independientemente de sus movimientos. Las vistas externas 1 y 2 mantienen al avión en el centro del campo visual, pivoteando la cámara sobre sus propios ejes, para evitar así traslaciones que pudieran desorientar al jugador. La cámara es también capaz de desplazarse para no perder el encuadre a pesar de los movimientos extremos del avión. La vista de HUD muestra la visión real que tendría el piloto del avión sentado en su cabina y observando a través del HUD. Este es el acrónimo de Head Up Display, y consiste en la proyección holográfica de los datos relevantes sobre el vidrio de la cabina (en realidad es una pantalla transparente justo al frente del piloto). Supongo que todo el mundo ha visto un HUD, y la mayoría lo recordará como los clásicos monitos, miras y relojes que aparecen en los aviones de TopGun, Stealth, y cada película de aviones fighters.) De momento el HUD es sólo una imagen fija (adivinen... Correcto, es paint!) proyectada en frente de la cámara de HUD. Más adelante el HUD se hará funcional, de hecho ya estoy diseñando los modos de display, el sistema de armas y toda la simbología que mostrará, pero no es prioridad actual. Cuando aparezcan nuevas necesidades, crearé modos de cámara nuevos, por ejemplo para seguir a un blanco designado, seguir a un misil, a un avión enemigo, etc.... (Se hace camino al andar...)

A continuación algunas imágenes del juego en su estado actual, con el archivo de terreno procesado por el esbozo de editor; y los modos de cámara en acción.


En la imagen anterior se observa al Shark volando sobre las clásicas casas de siempre. De fondo se observa un complejo montañoso que encierra un valle angosto ideal para ocultarse. Nótese que agregué en el juego mismo información sobre el frame rate (los cuadros por segundo a los que corre el juego; mientras más, mejor) y la cámara activa, en este caso Libre.


En la imagen anterior se observa al Sw71 desde una "vista de pájaro" lograda mediante el modo de cámara libre. Nótese como el frame rate cayó dramáticamente a 30 fps, debido a la mayor cantidad de polígonos en escena.


La anterior es una de mis favoritas: El Shark volando sigilosamente por el valle creadoentre las cadenas montañosas. De fondo se observan las casas donde esperan el retorno del intrépido piloto (XD)...



La imagen anterior muestra al veloz Pyrage cruzando a baja altura por el valle angosto. La cámara se ubica a ras de piso, siguiendo angularmente la trayectoria del avión.



La última imagen muestra la vista de HUD. Hasta el momento la proyección del hud es sólo una imagen fija superpuesta. Posteriormente agregué la capacidad de cambiar el color de la imagen del HUD, para evitar problemas de contraste entre fondo y HUD (como en la foto). Todo esto mediante el uso del blending multiple de texturas en una pasada. Más adelante el HUD se volverá totalmente operativo.

Posteen comentarios, sugerencias, ideas, etc. Se agradece a los que ya han escrito.

Nos vemos en un próximo informe de avance... (Antes que empiece la posta!!!)

miércoles, 19 de septiembre de 2007

Terreno

Desde Coquimbo escribo esta actualización del Blog. Hasta ahora no he introducido nuevas mejoras al juego, por lo que este artículo representa el estado actual, que es como quedó antes de venirme a celebrar las fiestas patrias.

Cuando las entidades quedaron funcionando bien con su iluminación y cascada de texturas, decidí avocarme a construir el otro gran componente del escenario: El terreno. Sabía que no podía ser un terreno pequeño, ya que el avión recorrería grandes distancias en cada etapa. Además, a pesar de que el juego no es un simulador, por el sólo hecho de tener un avión como protagonista, merece un terreno con cerros y elevaciones.

La primera idea fue sencilla: Hacer todo con triángulos. En realidad no hay otra forma (por lo menos para mí). El problema era crear los miles de vértices que representen cada punto del terreno. Me decidí por hacer un rectángulo enorme (sólo 6 vértices), sobre el cual se superpusieran los cerros como entidades. Esta idea fue rápidamente desechada, ya que el terreno no tendría la flexibilidad para mostrar suelos ondulados, acantilados etc. Por esta razón, decidí crear todo el terreno como una sola unidad, donde las elevaciones correspondan a puntos más altos de esta única superficie. Pensé en que también debería ser autogenerado, es decir, que a partir de una matriz con datos de elevación, el programa automáticamente construyera los vértices, pusiera las normales y texturara cada subunidad de terreno. Me quedé pensando en como realizar todo esto. La solución vendría a aparecer recién e día siguiente.
Encontré en mi casa un trozo de pasto artificial, donde meses atrás vivió Pinxa, mi erizo de tierra. Por encima parecía una superficie contínua y sólida, pero al mirar por debajo se evidenciaba que no era más que una red de trabéculas plásticas, en cuya intersección se insertaba una mota de pasto plástico. La malla estaba formada de estructuras triangulares agrupadas en cuadrados, y tenía la particularidad de poder moldearse en cualquier forma. Entonces decidí usar esa misma arquitectura en mi terreno. Si pudiera asignar un valor de elevación a cada intersección trabecular (lo que corresponde a un vértice), podría crear elevación en el terreno. Ya que cada vértice era común para varias triángulos, al elevar un vértice en particular, todo el terreno que lo rodee se debería amoldar para seguir la forma del punto más alto, al igual que con el pasto plástico.
Entonces ya tenía la idea, ahora había que implementarla. Cuando comencé a pensar en código, no estaba en mi casa, por lo que no podía probar de inmediato. Escribí mi primera versión del sistema de terreno en una hoja de papel, para que al día siguiente pudiera recordarla. Cuando volví a mi casa tipeé lo que había en la hoja, llené la matriz de elevación (desde ahora el "mapa de elevación")con ceros, y lo puse a correr. El sistema generó miles de vértices en segundos, los puso en un VertexBuffer y luego mi render los dibujó. Era un rectángulo largo que representaba un terreno plano. Estaba funcionando.
El problema comenzó cuando intenté ampliar el número de subunidades de terreno. Llegué a construir tantos vértices que no hubo buffer capaz de almacenarlos. Con mi mejor tipo de vértice, cada uno ocupaba 40 bytes en el Buffer. Descubrí de manera desagradable que no podía poner más de 32767 bytes en cada buffer, y eso me limitaba a 819 vértices para todo el terreno. Tuve problemas tratando de dividir el terreno en muchos buffers, por lo que decidí descartar los buffers y almacenar toda la geometría en un array gigante, que no ha dado problema hasta ahora.
Para probar la elevación, cambié un par de datos en el mapa de elevación, simulando un desnivel en el terreno a modo de cerro. Renderizé todo en modo WireFrame para observar con detalle la malla de triángulos que formaba mi estructura. El resultado el el siguiente:



Luego probé con paneles sólidos, para ver cómo funcionaba la iluminación y mis normales autogeneradas. Decidí agregar más cerros, por lo que escribir directo en el mapa de elevación se volvió engorroso. Entonces creé una función que cargara el mapa de elevación basado en un archivo de texto. En bloc de notas escribí unos cuantos números y lo guardé como elevation.txt. Al correr el juego, automáticamente se tomaron esos datos de elevación y el mapa de elevación se autorellenó. Aparecieron todos los cerros que había definido en bloc de notas y la iluminación funcionó bien sombreando el relieve. Tomé un snapshot que se vio así:



Texturas de terreno

Cuando todo anduvo bien, fue hora de comenzar con las texturas. Agregué soporte para textura Stage 0, dibujé en paint algo que debiera parecer pasto y se lo agregué. Además agregué las casas que había hecho antes. Este es ese primer terreno texturado.



Se veia bastante bien, aunque demasiado brillante, así que decidí usar una textura de pasto real:



Posteriormente agregué el soporte de doble textura. La idea era que se pudieran mezclar dos texturas distintas para realizar transiciones de una textura a otra, agregar rugosidad a un terreno de un color ya determinado, cambiar el color de un terreno, etc. También agregué un sistema de mapeo de texturas, capaz de tomar una fracción de la textura original y usarla como textura en sí. Esto permitiría incluir muchas texturas en un mismo archivo y seleccionarlas en el mismo programa. La siguiente imagen sicodélica muestra una prueba del nuevo sistema, donde un triángulo de cada subunidad utiliza como textura a una imagen con varios colores, mientras que el otro triángulo usa como textura sólo a la porción roja de la misma imagen.



Finalmente hice que cada subunidad fuese capaz de mostrar una textura independiente. De este modo, los kilómetros de terreno ya no deberían lucir iguales. La información acerca de la versión de textura que usaría cada subunidad(en realidad referencia a una fracción distinta de un mismo archivo de imagen)está contenida en otro archivo de texto que llamé "Mapa de Texturas". De este modo, el terreno está definido en dos archivos de texto pequeños, que cuando tenga completamente definidos voy a fusionar en uno solo. También debo agregar la información sobre las entidades y su posición, lo que actualmente sólo se puede hacer desde el código.
En el siguiente snapshot se puede ver al terreno con una parte en pasto, otra con manchas blancas que corresponden a sitios de fusión con la textura stage 1; y muchas caras sonrientes. Estas últimas corresponden a la forma en que decidí que el programa manejara las subunidades para las que no hay información de textura disponible. Está lleno de estas subunidades "data not found" porque sólo designé texturas para las subunidades de la primera fila (era sólo una prueba). En realidad las caras sonrientes están formadas del blending de dos texturas: Una cara feliz (Smiley, Kirbys, o como le llamen)amarilla con lengua roja; y un fondo rojo. De esta manera se muestra como se mezclan dos texturas con facilidad. También se evidencia el sombreado en los cerros, la texturización independiente de cada subunidad, la integración con las entidades, y la generación a partir de archivos de texto. Es por esto que esta imagen, aunque no muy espectacular, muestra las capacidades máximas actuales del sistema de terreno recién creado.



La idea ahora es seguir mejorando el sistema de terreno, crear herramientas para editar con facilidad los archivos de texto (un editor de terreno o algo así)y aprender a usar un programa de edición más decente que paint para las texturas (se me está haciendo muy dificil trabajar con paint lo de las páginas de texturas). Todo eso cuando vuelva a Santiago, porque por ahora, a disfrutar mi último día de fiestas patrias. ¡Viva Chile!...

domingo, 9 de septiembre de 2007

Luces

Me he dado cuenta alegremente que ya tengo algunos comentarios en el Blog. Se agradece el aporte...

La última vez que escribí, había logrado poner dobles texturas en las casas, con musgo incluido. En ese momento me surgió la inquietud de la iluminación. Hasta el momento, todo el color provenía de los vértices y estaba predefinido. Eso funcionaba bien, porque era confiable, pero planteaba un problema con el realismo, dado que todos los objetos lucían una iluminación pareja, que les restaba calidad y a la vez hacía que se perdieran detalles de profundidad por falta de contraste entre colores idénticos.

Cuando decidí que necesitaba luces, hice lo mismo de siempre: Me puse a leer. Aprendí algo de teoría de iluminación y me di cuenta que no era tan dificil, pero sí que me llevaría un tiempo mayor del presupuestado. De todas formas valía la pena y comencé.

Dado que este blog tiene algún fin didáctico, voy a explicar un poco lo que aprendí en cuanto a luces y el problema que me planteaba:

Poner luces en realidad no cuesta nada. Sólo hay que definir algunas cosas, poner ampolletas por aquí y allá y el motor de iluminación de DirectX hace el resto. La única limitante es que no proyecta sombras, pero no es gran problema todavía. Lo que sí me incomodó un poco, es que necesitaba un tipo de vértice especial: uno que fuese capaz de contener una normal. Además de eso, dado que yo usaba dobles texturas, este tipo de vértices debía tener soporte para doble coordenada. Entonces tuve que volver a definir un tipo de vértice con esas características y cambiar la geometría genérica para que utilizara este nuevo tipo de dato. Después de algun tiempo el resultado quedó así:



La casa que aparece en negro utiliza el nuevo tipo de vértices y funciona basada en la iluminación. Como todavía no había definido nada de luces, la casa se ve negra, a diferencia de las demás que siempre se ven igual, independientemente de las luces.

Configurando la iluminación

Algunos párrafos atrás apareció la palabra "normal". Voy a explicar un poco lo que significa o lo que comprendí respecto a esto.
Cuando trabajamos con polígonos en Direct3D, definimos el triángulo por sus tres vértices. El triángulo sólido en sí vendría siendo el plano que contiene a estos tres vértices, pero sólo la región comprendida entre ellos. Por geometría es evidente que entre 3 puntos pasa un sólo plano, pero esto llevado a la realidad nos deja un problema, el plano tiene 2 "caras", es decir, puede estar mirando hacia nosotros o hacia el otro lado. Cuando trabajamos sin iluminación, esta ambiguedad no supone problema, dado que el triangulo se renderizará igual, sin importar hacia donde apunte la cara "real". En cambio al iluminar, sí tiene importancia este hecho, dado que la luz es incapaz de atravezar el triángulo, y por tanto sólo una de las caras se verá iluminada. (un cajita de fosforos cerrada puede estar muy iluminada por fuera, pero las mismas paredes por dentro sigue oscuras). Es por esto que debemos acabar con la ambigüedad, indicándole a DirectX cúal es la cara que mira hacia afuera. Para esto, le asignamos un vector perpendicular al plano, que apunte simepre en el mismo sentido. De esa forma DirectX será capaz de diferenciar las dos caras del mismo polígono (la que mira hacia la normal, y la que está en el lado opuesto). Ese vector, cuando mide sólo una unidad (así no interferirá cuando multipliquemos) se llama normal.
Ya sabemos para qué sirve, ahora debemos crearlo. Obviamente a mano no es la mejor idea, porque son muchos vectores y no quiero hacer la trigonometría yo solo. Decidí usar una función que creara las normales automáticamente a partir de los vértices de un triángulo. Para crear una función, necesitaba saber la teoría de cómo hacerlo matemáticamente, para luego escribir una expresión general que el VB fuera capaz de entender.

Los números detrás de la luz

Lo ideal en este punto es comenzar a trabajar vectorialmente. DirectX tiene una estructura de tipo VECTOR, que es perfecta para guardar una normal. La idea es crear primero un par de vectores que definan el plano del triángulo. Esto se logra trazando un vector entre los vértices 1 y 2, y luego 2 y 3. De esta manera, el triángulo dejó de ser un conjunto de tres puntos, y se convirtió en un par de vectores. Sólo necesitamos dos de estos, ya que los tres puntos en el triángulo son coplanares, por tanto, el plano que contenga a los dos vectores recién creados, será el mismo que contendría al tercero, por lo que no es necesario incluirlo en los cálculos. Aquí viene la parte interesante (creo..). Para obtener un vector perpendicular al plano que contiene dos vectores, lo único que debemos hacer es calcular el producto cruz entre ambos vectores. El vector resultante sería un vector de misma dirección y sentido que la normal que buscamos. El sentido está determinado por el orden de los vectores en la multiplicación, por lo que es muy importante pasar los vértices en el orden correcto, siempre en sentido horario mirando el triángulo desde "afuera". Con esta convención nos aseguramos que todas las normales apunten en el sentido de la cara que se debe iluminar. Hasta este punto tenemos un vector que es casi una normal, sólo que tiene una magnitud desconocida. La solución al problema es simplemente normalizar al vector, es decir, llevarlo a magnitud uno sin modificar dirección y sentido. El DirectX tiene una función que hace este trabajo automáticamente, aunque supongo que podríamos lograr el mismo resultado dividiendo el vector por el valor absoluto de sí mismo.
Finalizado todo esto, tenemos la normal del triángulo. El punto siguiente es asignarla a cada vértice, de ahí que se requiera un tipo de vértice con soporte para las coordenadas de la normal (puede ser un vector directamente, pero preferí definirlo como las tres coordenadas que lo definen). Una vez asignadas las normales, el triángulo está listo para ser iluminado.

Después de la teoría de las normales, sólo basta hacer una función en VB que realice este proceso con cada vértice. Hay que aclarar que no soy ni ingeniero, ni nada parecido, así que puede que esté hablando puras tonteras.. Al menos lo que dije es lo que aprendí para hacer este tipo de cosas.... Que se manifiesten los matemáticos si algo no está bien... Habrá que ver qué piensa la Camy de todo esto.... De todas formas, creanme que a mi me ha funcionado. Pueden verlo en la siguiente imagen:



Se puede apreciar claramente que la ex casa negra, ahora tiene color. Está bajo una iluminación ambiental tenue, y es claramente distinta a las demás casas que no se afectan por la luz.

En este punto del proyecto, ya tenemos todo lo complicado funcionando. Logramos generar normales para cada vértice de la casa, por que la iluminación ya está disponible. El paso siguiente es crear luces y ver cómo se comportan los modelos.
En la imagen de recién sólo tenía una luz de ambiente, que proviene de todas dirección y por tanto, sólo produce un efecto de brillo o atenuación general. La idea es agrega ahora luces direccionales, que simulen que la iluminación viene de una direccion en especial, como sería la luz del sol.

Agregué un par de luces direccionales y el resultado es el siguiente. Nótese la diferencia de tonos entre el frente de la casa y la pared. Esto no es un cambio de textura, son los efectos de la iluminación, donde la luz incide de manera más directa sobre el frontis que en la pared. En las casas sin iluminación este efecto no ocurre.



Como todo resultó bien para esa casa, decidí repetir el método para todas las demás casas. Cambié el tipo de vértice y repetí el proceso. El resultado fue claramente distinto a lo que esperaba....



Ese ejército de mantarrayas de otro planeta, se supone que eran mis casas... El error fue el mismo que la vez anterior. Al cambiar el tipo de vértice, debía modificar la manera en que se calculaba el tamaño de los buffers... Son cosas que pasan... Decidí guardar el snapshot de todas formas... (No serían malos enemigos.. Las mantarrayas asesinas.. Tema para el próximo juego)..XD

Corregí el error, apliqué texturas dobles a todas las entidades, encendí las luces y tomé un snapshot. Así quedó mi población con doble textura e iluminación. Por fin las casas estaban finalizadas como entidad. Ya podría comenzar a programar otra parte del juego.



Bonus Track

Antes de terminar, decidí crear una textura alternativa. Durante el verano había hecho un viaje a Pedernales, una localidad de la V región. Ahí encontré una casa que me gustó bastante, está hecha de pedernal, una roca que abunda en ese sector. Tengo una foto mía en la casa. Entonces tomé esa foto, corté un pedazo y la usé como textura. Tomé una puerta y una ventana de otra casa y compuse todo en paint.


Esta es la foto original de la casa en Pedernales.



Y así quedaron las casas, iluminadas, doble textura, con una capa de foto real y una de musgo hecho en paint. Con esto me bastó para dar por terminadas las casas y comenzar a pensar en el paso siguiente: El terreno...

viernes, 7 de septiembre de 2007

Entidades

A pesar del nombre sugerente de mi juego, claramente debía tener algo más que sólo aviones. Consideré que ya era tiempo de agregar otros elementos al escenario: Casas, edificios, vehículos, etc. Decidí llamar genéricamente a todo esto "entidades", para diferenciarlo del otro elemento del escenario, que vendría a ser el "terreno".

Para comenzar declaré un tipo de dato "Entidad estática", para que definiera en general a todas las entidades que no se movieran, y por tanto, que prescindieran de inteligencia artificial. Cada objeto de este tipo, contiene cuatro VertexBuffers, que almacenan la geometría para los 4 distintos niveles de daño que puede sufrir. Una vez creado el tipo, llegó la hora de crear la geometría en particular e instanciar el objeto.

Decidí empezar haciendo una casa: Es un objeto bastante simple de dibujar y me permitiría probar rápidamente si estaban funcionando mis ideas. Creé unos cuantos vértices, llené los buffers, instancié una casa genérica, y luego le pedí al programa que me creara tres casas personalizadas basadas en esa geometría genérica más unos cuantos parámetros de posición individuales para darles una ubicación particular.

El resultado fue este, las primeras entidades estáticas del juego:




Cuando todo comenzó a andar bien, completé los paneles triangulares del techo de las casas y creé una función para generar con facilidad todas las casas que quisiera. Gracias a esto apareció la primera "ciudad, población, condominio, o lo que sea"...



Las Texturas llegaron Ya.....

Cuando comencé a diseñar el juego, decidí que no lo texturaría, dado que: 1) Nunca había puesto una textura, jamás en toda mi vida... y 2) Aunque aprendiera rápido, sería un trabajo eterno mapear a mano vértice a vértice cada elemento. Hay que recordar que toda la geometría está diseñada a mano, dibujada en el primer papel que encuentro cerca y luego tipeada vértice por vértice en el programa. Si esto ya es un gran trabajo, agregarle coordenadas de textura sería mucho más complicado y podría quitarme las ganas de continuar...

De todas formas, cuando tuve las casas quise que se vieran mejor. Entonces aproveché un fin de semana y recopilé bastante información sobre texturizado en DirectX8. Cuando sentí que podía hacerlo, dibujé una textura sencilla en paint (Tal vez sería bueno conseguirme el photoshop.. aunque todo en paint lo hace bastante más artesanal :P) un poco inspirada en la casa en que vivía el año 97, cuando conocí VB :P. Apliqué la textura y el resultado fue el siguiente:



Quedé bien conforme con el resultado, lo que me motivó a leer más. Aprendí sobre blend de multiples texturas en una sola pasada. Entonces decidí implementar mi primera cascada de texturas. Para eso tuve que crear un nuevo tipo de vértice, con soporte para doble coordenada. Eso implicó rediseñar las casas, lo que me entregó una nueva entidad genérica. Cuando parecía todo listo, ejecuté el programa y el resultado fue bastante freak, como se ve a continuación:



Eso que parece un perro de otro planeta, se suponía que era una casa. El problema fue que calculé mal el tamaño de un VertexBuffer. Con un poco de paciencia logré encontrar y corregir el problema. Entonces tuve lista mi casa con soporte de doble textura. Ahora debía utilizarlo. Abrí el paint y con el spray dibujé unos musgos sobre un lienzo blanco. Configuré mi cascada de texturas para que los dibujara sobre la casa recién hecha, y el resultado quedó así:



Gracias a ese fin de semana las casas ganaron bastante realismo, y yo mucha motivación para darme el trabajo de texturizar todo lo que hiciera de ahí en adelante. Además, me surgió una nueva inquietud: Si las casas ya tenían texturas, ahora merecían iluminación, pero eso ya es tema de otra entrada en este Blog.

miércoles, 5 de septiembre de 2007

Buscando el movimiento

Cuando ya tuve los aviones diseñados y dibujados. El desafío siguiente fue lograr controlarlos. Lo que más tiempo me tomó fue diseñar e implementar el sistema de movimientos. Decidí crear un avión que se moviera de manera predecible al estilo arcade. Básicamente el avión se traslada sobre sus 3 ejes, pudiendo combinar estos movimientos. Para agregar algún efecto interesante, decidí incluir una animación breve de alabeo o cabeceo del avión, previo a comenzar la traslación en sí. Realmente esto me tomó mucho tiempo, y significó volver a familiarizarme con el añejo GetTickCount de la API de Windows.

Al terminar el proceso, cada avión cuenta con las mismas animaciones en sí, aunque variables en cuanto a velocidad, basadas en datos particulares de cada avión. Así, cada aeronave tiene sus propios parámetros de agilidad en cada una de las dos fases del movimiento (animación y traslación), permitiendo la variabilidad deseada entre los distintos tipos de avión.

Cuando el sistema de movimientos estuvo completo, decidí agregar soporte para Joystick. Esto sí resltó totalmente novedoso para mí, ya que nunca había intentado semejante empresa. Luego de algunas noches leyendo tutoriales y husmeando en MSDN, logré implementar un soporte sencillo para mi joypad de 12 botones, basado en DirectInput8, el que funcionó perfectamente. Mi joystick (lease "palanca") también funcionó correctamente, aunque la limitación a movimientos estereotipados no saca ventaja de las múltiples posiciones posibles en los ejes de un joystick, en oposición a los 3 valores discretos del joypad (Ejemplo: Izquierda, Centro, Derecha). Claramente este juego está hecho para un joypad... Es arcade, no un simulador.
Otro hecho interesante fue el soporte de ForceFeedBack, el cual utilicé solo a modo de efectos de vibración, ya que los joypad no tienen un ForceFeedBack real. (y no me quise tomar el tiempo de crear una característica no soportada).

Durante las pruebas del sistema de movimientos y el joypad, tomé un video que difundí entre mis amigos para captar sus impresiones sobre el juego. Desde entonces estoy recibiendo bastante feedback y logré captar la atención en el proyecto. Gracias a esto, decidí empezar este blog.


Este es el video del que hablo: Después de su grabación, el sistema de movimientos no ha sido modificado en su esencia, sólo le he realizado algunas optimizaciones de código, y decidí mover el soporte de joystick a un módulo propio, para tratar de poner orden en mi proyecto.

lunes, 27 de agosto de 2007

Comienza el proyecto

Comenzó Agosto, y con él mi último proyecto de programación: Avioncitos 3D. El origen de este juego se remonta a los días, no tan lejanos, cuando con RT jugábamos Strikers 1945 en su casa después de los ECOEs. Avioncitos 3D está inspirado en los juegos de este tipo, por lo que he decidido hablar un poco de lo que tienen en común y en lo qu se diferencian..

De partida Strikers 1945 es un juego de Arcade, emulado en MAME, similar a la mayoría de los juegos de aviones en scroll vertical. Cuenta con las típicas bombas que arrasan con todo en la pantalla, los powerups, y el jefe grande al final de la etapa. Lo que le otorga cualidades especiales respecto a los demás del género, tal vez sea el Super Shot y la gráfica un tanto superior. Realmente es un juego adictivo, donde más que volar un modelo real de avión, lo que se consigue es movilizar endemoniadamente el aeroplano entre nubes de balas enormes y coloridas. Si bien el avión realiza maniobras fisicamente imposibles (retrocede fácilmente), esto le otorga la gracia al juego: Evadir millones de disparos a gran velocidad. Como todos los juegos del género, cuenta con enemigos en tierra y en aire, donde en realidad. las balas les afectan simultáneamente a ambos, de modo que el efecto de profundidad es sólo simulado. Realmente los movimientos ocurren en un solo plano.

Con Avioncitos 3D he pensado en recrear la lógica del juego, volando un avión de manejo estereotipado a través de escenarios plagados de enemigos y proyectiles. Avioncitos 3D NO ES un simulador de vuelo. Es una versión de un juego de scroll, salvo que todo ocurre en tres dimensiones. Dada la intensidad de las escenas 2D, me pareció sumamente interesante lo que podría llegar a ser volar en un campo de batalla tridimensional.

¿Por qué avioncitos?

Cuando pensé en hacer mi propio juego, decidí que fuera de aviones porque: 1) Me encantan los aviones y todo lo que tengan relacionado. 2) Por que tenía ganas de tener mi propia versión de Strikers 1945 y 3) Porque los aviones son cuerpos sólidos que requieren mucha menos animación que personajes más biológicos.

Una vez decidido a comenzar a programar, la pregunta fue ¿Cómo hago un juego en 3D?.
A los que se dieron la lata de leer mi historia, les quedará claro que he tenido bastantes acercamientos a la tarea de programar, pero que nunca me he dedicado en serio a aprender ningún lenguaje. Al momento de comenzar el juego ya tenía decidido que sería en 3D, por lo que al menos la API estaba decidida; DirectX8, por ser aquella única en que logré renderizar algo alguna vez (Super Bong, por el año 2005.. Vean mi historia en la primera entrada.)
Después de mi aproximación inicial a DirectX8 con VB, lo dejé de lado y comencé con C#, lenguaje del que algo aprendí, pero bastante poco. Al momento de decidir hacer el juego de avioncitos, pensé en aprender DirectX9 y usarlo con C#, pero claramente esto significaría invertir más tiempo del presupuestado en aprender. Finalmente, después de un fin de semana completo bajando y leyendo manuales de DirectX8, opté por comenzar con esta API en entorno Visual Basic, que era a lo que más estaba acostumbrado. A medida que el programa creciera, pensaría en migrar a una API más reciente y a un lenguaje .Net.

Recordando viejos tiempos

Desde hace siglos no escribía una línea de código, hasta que un buen fin de semana busqué en CD del Visual entre las cajas de CDs viejos y lo instalé. Lo más elaborado que había creado en geometría hasta entonces, era un paralelepipedo que servía de paleta en Super Bong (un Pong en 3D). Sabía que de ahí a dibujar aviones había una diferencia enorme, por lo que pensé en cómo solucionaría el problema de los modelos. Por lo que había leído, la solución inteligente era usar un software de modelado 3D e importar los mesh com archivos .X . De todas formas, yo soy un poco más impaciente y no quise ponerme a estudiar modelado 3D primero(Según sé, cuesta bastante dominar el 3D Studio Max)así que decidí por dos opciones: Crear modelos a mano bastante sencillos, pero entendibles; o bien, crear en Visual Basic mi propia herramienta para diseñar modelos 3D de manera sencilla. Me gustó la idea, pero antes de lanzarme a crear mi propio software, debía recordar como se hacía a la antigua, por lo que me dispuse a crear un modelo simple, especificando la geometría a mano, vértice por vértice en un array de este tipo.

...Para los que no entiendan mucho del tema, al menos en DirectX, toda la geometría está basada en triángulos, por lo que para construir una figura, se deben especificar las coordenadas de cada vértice, los que el programa luego completa y da forma en un ambiente 3D. Hay una manera fácil de hacerlo (con un programa con que uno sólo dibuja, y luego automáticamente se generan las coordenadas); y una difícil y poco clever, que es imaginar el avión, luego imaginarlo dividido en triángulos, para luego imaginar cada vértice en un sistema cartesiano de tres ejes, y finalmente meter al PC todos esos números....

Primer día de trabajo

Cuando tuve inicializado el motor de D3D, comencé a diseñar mi modelo de prueba. Un avión sencillo basado en un paralelepípedo central como fuselaje (hasta ahora no había hecho más que esas figuras). Tendría un par de triángulos por lado, por tanto 8 primitivas para el fuselaje. Luego cuatro triángulos cerrando el "cilindro" por delante, le darían apariencia de avión. Cuando terminé de dibujar esto, el resultado me sorprendió bastante. Se veía mucho mejor de lo que creía, para ser una primera vez. Me emocioné y agregué un par de triángulos para crear cada Ala (dos planos triangulares montados uno sobre el otro con ligera angulación). Al final me decidí y agregué los planos de cola y el estabilizador horizontal. Algunos triángulos más sirvieron para simular el escape. Cuando pulsé F5, ya tenía mi propio avión hecho a mano. Le tomé fotos por todos los ángulos y terminé de convencerme que sería la manera a la que generaría toda la geometría del juego. A pesar de ser claramente un avión extraño y muy triangular, le tomé cariño y decidí adoptarlo como el primer avión del juego. Se ganó el nombre de Sw71. (Sw viene de Sewoldt, la compañía que lo fabrica XD).






Las imágenes de arriba corresponden a las primeras tomas del primer modelo del juego. Como vi que funcionaba, decidí crear un segundo avión. Esta vez sería un jet de ala en delta. Fue así como el mismo día (o tal vez uno después) apareció la segunda nave de la bandada el SwXX Pyrage. (nunca le di mucha importancia a los nombres. Es muy probable que cambien de aquí al final del juego. Es la gracia de ser mi propio jefe en el proyecto.)
Finalmente, el 9 de Agosto de 2007, creé el último modelo. El Sw28 Shark. Decidí cambiar el color del Sw71 a rojo, para dejar el amarillo al último avión. Este modelo mostró la evolución que ocurrió durante ese par de días. Tiene un grado de teselación mayor, lo que otorga bordes rectos más alejados de la apariencia triangular del Sw71. Las alas tienen considerablemente más trabajo (6 triangulos por ala), por lo que son estéticamente mejores. El Shark toma su nombre de la forma de su cola, la que es alta y triangular, remedando la aleta de un tiburón en el agua.. .(con un poco de imaginación tal vez...).
Cuando estuvieron los 3 aviones creados, apliqué un par de transformaciones, moví unas cuantas matrices y dejé los tres aviones juntos en la pantalla. El Sw71 se encontraba al medio, mientras era escoltado por el Pyrage y el Shark a los costados, cada uno describiendo un roll contínuo hacia afuera. Había logrado crear geometría y animación simple a partir de matrices. Era un indicio de que el sueño imposible, tal vez podría hacerse realidad.

El siguiente snapshot muestra la escena recién descrita, donde aparecen los tres aviones en formación. Un logro muy bienvenido después de tanta incertidumbre. Gracias a esta escena, me di cuenta que había valido la pena lo estudiado, y que sería factible continuar. Por tanto, esta historia continuará.