lunes, 28 de julio de 2008

Retorno al 3D

JKS [jksware@gmail.com]

Hace un tiempo atrás h0ax (hoax@fresnomail.com) publicó un primer capítulo de lo que pretende ser un tutorial de gran escala para que los programadores interesados en el mundo del 3D pudieran juguetear con objetos y texturas en tres dimensiones mediante la aplicación de OpenGL, una engine más que potente y conocida. Pero como todas las cosas, OpenGL fue hecha en su versión inicial hace muchos años, y concebida – aunque esta última aplicación puede cambiar – para ser usada fundamentalmente por los programadores de C++ y su comprensión puede llevar más que un curso de “Apréndalo usted mismo en 21 días”.


Sin ánimo de hacer competencia, sino de ayudar a la comunidad a encontrar opciones para la programación enfocada en 3D, pienso revelar una serie de pequeños artículos que pretenden, eso sí, robándole el menor tiempo posible, encaminarlo en el maravilloso mundo de las colisiones, normales y vectores que es la base ingenieril de todas la maquinaria tridimensional de un juego. Repito que sólo me concentraré en la parte tridimensional, sin enfocar otras muchas problemáticas comunes que salen a la luz en un juego o aplicación que requiera el uso de renderización de objetos en tres dimensiones, no yéndonos más allá en lo que podría ser la introducción a cómo crear un juego, cualquiera que este sea, desde el punto de vista de interface de usuario y jugabilidad per sé.

¿QUE ENGINE USAS?

Hace poco en uno de los encuentros de BlackHat en 23 y J – que próximamente será reubicado – escuché algo parecido entre Chenry y h0ax. Maravillándome por dentro de la respuesta siguiente, me acometí a pensar que era, en términos generales, lo que había que hacer, por sólo mencionar programación, y no las matemáticas que esto trae de fondo, para realizar un juego de primera persona en 3D partiendo desde cero, o cero coma uno:
- Utilizamos mayormente DirectX. – dice Chenry refiriéndose a unos colegas – ¿Y tú?
- Hice una mía sobre OpenGL – respondió h0ax.

Por más que esto me costó procesarlo, y aunque no lo reproduzco aquí fielmente y en el orden necesario que sucedió, quedé nuevamente impactado por el hecho de programar, casi desde cero, una engine para un tipo de juego que lleva, al menos en la más básica de las estrategias, jugándose por casi una década: un first person shooter.

Reconozco que una vez hecho la programación de dicha engine, nuestro amigo h0ax dispone de una completa utilidad y libertad de movimiento – en términos lógicos, claro – para hacer lo que le venga en ganas con dicha engine. ¿Pero cada vez que alguien quiere hacer un juego tiene que programarlo desde cero, o casi cero, eso si no contamos la enumeración de dispositivos, aparatos gráficos, aparatos de input como el keyboard, el ratón o el joystick y demás, todo a nivel medio o bajo? Por supuesto que no.

LA INVENCIÓN DE LA RUEDA

La rueda – aunque no soy historiador – se inventó hace muchos siglos. No existe necesidad de volverla a inventar, procrear ni medir. Nuestras bicicletas y autos ya vienen con ruedas “prefabricadas”, por lo que cada vez que un ingeniero de la General Motors se quiere lanzar a diseñar el “carro del año”, no tiene que perder tiempo pensando cómo hacer la rueda, porque esta en definitiva, no difiere en principio en mucho de las demás ruedas: es circular, compuesta de un derivado del corcho, y la razón entre perímetro y diámetro es de 2 * pi. Lo mismo sucede con Ford, Mercedes-Benz, Land Rover y Toyota.

Entonces por qué, si es más fácil “copiar y pegar”, en términos de diseño de una engine, ciertas compañías de software requieren que sus juegos se hagan casi desde cero: en la mayoría de los casos, por qué las engines disponibles para empresas, que tienen un altísimo nivel, son caras y no están sujetas a cambios.

Un ejemplo válido es el siguiente: cuando Blizzard hace Starcraft – el original ó 1.00 – lo monta sobre una plataforma que no da a conocer cuál es. Probablemente la programaron casi desde la base, por lo que adaptaron toda la jugabilidad a un contenido plenamente bidimensional visto desde un ángulo estratégico – ya que en aquel momento no tenían ni siquiera la menor idea del récord de ventas que el mismo iba a generar. Pero evidentemente, al menos diez años después que planean lanzar la segunda parte de la saga Starcraft, no van a utilizar nuevamente el viejo engine. De hecho, la mayoría de los cambios, según declaraciones de sus propios diseñadores, consisten en agregar nuevos personajes y equipos – es decir algunas opciones de jugabilidad – pero el contenido es el mismo, una batalla épica. Por ello, han tenido que adaptar todas las viejas opciones en 2D a estas nuevas – que casi todos hemos visto en sus video-demos – en 3D.

Para ellos – una megaempresa como Blizzard con un megaproyecto como Starcraft, que cuenta con los recursos económicos, humanos y mucho pero mucho tiempo – es mejor e incluso más fácil, concentrar el cambio en el engine de base que en la reprogramación de su nivel lógico de inteligencia artificial e interfaz: cómo va a encontrar una pared determinado personaje, y cómo va a hacer para darle la vuelta; pero cómo un player ordena mover a los caracteres dentro del mapa, es prácticamente la misma historia de la versión anterior. Para nosotros – unos pobres infelices que apenas sabemos del mundo exterior – es mucho más ahorrativo de recursos y tiempo programar en base a lo ya hecho y pensado, porque nos permite concentrarnos en la estrategia y la interacción, más que en dispositivos, texturas, aparatos y flags.

3DSTATE PARA TODOS LOS GUSTOS

Busqué en Google – 3 años hace – lo que constituye el contenido real de este tutorial bajo el query “3d programming + Windows”. En realidad, no se cómo ha cambiado los resultados de la búsqueda de aquel entonces hacia el momento actual, pero esa vez me devolvió el inteligente buscador la siguiente opción de primera: www.3dstate.com. Cuando hice clic encima del vínculo – contaba con navegación internacional como comprenderán – pude ver el sitio que anunciaba la programación en 3D para Windows más fácil y robusta disponible en la web. Si bien los gráficos no me impresionaban mucho – en mayor parte por haber estado jugando por aquella época casi enviciado juegos de última generación de nivel comercial – si lo hizo la cantidad de código necesario para generar un juego de pequeñas dimensiones: era mínimo el código y máximo el placer que sentía al cambiar un par de instrucciones de unos demos que por entonces descargué.

Para qué decir que podía programar en mi lenguaje preferido: ¡Delphi!. Pero si me interesaba también podía hacerlo en Visual C++ – siempre como primera opción – además de Visual Basic y C #. Incluso en el caso que ninguno de la lista fuese mi lenguaje predilecto podía hacer un par de modificaciones a un fichero y listo, accedía al núcleo de la engine 3D State – antiguamente conocida como Morfit – mediante una biblioteca dinámica “3dstate.dll” de apenas 1 MB. Esta biblioteca, cabe apuntar, a su vez se basa en la gigantesca de Microsoft, la DirectX, específicamente en lo referido a Direct3D. En la carpeta Tools de esta edición he mandado a incluir dicho DLL y el código necesario en todos los “idiomas” que mencioné.

NOTA: Para poder visualizar todos los ejemplos es requisito tener instalado una versión 9 o superior de directX, de ser posible la última de las 9.c, casi siempre incluida en los juegos comerciales. No es necesario en cambio tener instalado su paquete SDK de creación, sólo la runtime.

Si bien ahora reconozco que no todo en el mundo del 3D es “panes y flautas”, si bien me vino un empujón de parte de los autores de este ingenioso engine que les presento. Quizá, y sólo quizá si no hubiera habido algo tan simple alrededor para una persona desconocedora por completo de dicha modalidad de programación, entonces no me hubiera inclinado por el 3D finalmente verán que hice. Ahora veo que para hacer algo realmente serio y de calidad comercial siempre hay que caer en un nivel un tanto más bajo, pero para comenzar a programar en este mundillo, 3Dstate es una opción más que recomendada.

PASOS DE INICIALIZACIÓN (ALIAS HELLO WORLD)

1. Lo primero que haremos para inicializar nuestro mundo es poner en la cláusula uses al principio de la hoja de código la unit STATED. Esto hará que nuestro programa se vincule con las funciones y procedimientos de 3D State Engine. Debemos asegurarnos de que la unidad STATED – o como sea que la deseemos llamar – sea accesible por nuestro compilador, por lo que se recomienda copiar el archivo de código al mismo directorio del código fuente de nuestra aplicación, además de posteriormente copiar el archivo 3dstate.dll a la que esta hace referencia.

2. Después de haber agregado esta unit (Stated.pas), podemos empezar a escribir el código para cargar nuestro mundo. Debemos entonces poner lo siguiente en el procedimiento OnShow del Form (TForm1.FormShow):

procedure TForm1.FormShow(Sender: TObject);
var
  LoadWld : Integer;
  ErrorMsg : string;
begin
  LoadWld := STATE_engine_load_world('Leccion1.wld', '', 'Bitmaps', USER_DEFINED_BEHAVIOR);

  if(LoadWld <> OK) then
  begin
    ErrorMsg := 'El mundo ha fallado al cargarse. Abortando';
    MessageBox(Handle,PChar(ErrorMsg),'Error',MB_OK or MB_ICONWARNING);
    Close;
    exit;
  end;
  camera := STATE_camera_get_default_camera;
end;

Si nuestro archivo se encuentra en plena disposición para ser cargado y no tiene ningún error entonces se pasará a cargar la cámara; si por el contrario se encuentra algún error se mostrará el mensaje anterior.

La función STATE_engine_load_world carga el archivo *.wld o similar. Los parámetros son:

a) Nombre del archivo. Los archivos mundo virtual soportados por esta engine son de extensión *.wld o *.state. Se debe ser cauteloso a la hora de guardar los archivos en el WorlBuilder-Webmaker – que no es más que el programa que genera estos archivos, aunque también se puede hacer por medio de un plugin a 3D Studio Max u otros editores de 3D conocidos – ya que la extensión predeterminada es en formato ASCII, que ocupa mucho espacio aunque es editable mediante notepad o similar – para la versión final de una aplicación recomiendo encarecidamente que el mismo sea binario.

b) Directorio en que se encuentra el escenario 3D. En este caso se encuentra en el mismo directorio que nuestra aplicación.

c) Directorio en que se encuentran los archivos de imágenes. El directorio es creado automáticamente por el WorlBuilder-Webmaker dentro de la carpeta del *.wld. El método clásico de organización siempre recomienda guardar las imágenes en un directorio por separado “Bitmaps”, por el que los archivos siempre sean editables desde el exterior sin necesidad de recompilar o volver a generar el escenario 3D.

d) El cuarto parámetro indica el modo de visualización de los objetos: USER_DEFINED_BEHAVIOR y EDITOR_MODE. El primero de estos dos es el modo estándar de visualización y del cual hablaremos en la mayoría de los siguientes tutoriales. Este modo tiene el control total sobre el mundo y en esencia solamente puede manejar los objetos dinámicos y no los estáticos. El modo editor (EDITOR_MODE) puede manejar los dos tipos de objetos, aunque consume más CPU. Como veremos existen muchas similitudes en este aspecto con otras engine.

NOTA: Cada vez que un archivo de escenario 3D soportado por 3D State Engine se carga en una aplicación se autogeneran dos archivos que guardan datos del momento de ejecución. Si ocurre algún problema estos guardan la razón de porqué se generó. Estos dos son: error.log y 3DState.log que se pueden encontrar en el mismo directorio de ejecución del programa.

3. Nos hemos referido ha camera como una variable del tipo DWORD. Así que debemos declararla en la sección Private de nuestra form:

Camera: DWORD;

NOTA: En este ejemplo hemos tomado la handle – o manejador, traduciéndolo duramente al español, que no es más que un puntero – de la cámara con la función STATE_camera_get_default_camera tomando la camara estándar de ese mundo, pero debo aclarar que en un escenario donde exista más de una cámara se debe especificar el nombre de la misma con la función STATE_camera_get_using_name().

4. Ahora es necesario declarar cual de las cámaras será las que representará el mundo en nuestra form. Para eso agregaremos unas cuantas líneas al método al cual llama el evento FormPaint de la ventana en cuestión.

procedure TForm1.FormPaint(Sender: TObject);
begin
  if (STATE_engine_is_empty() = YES) then
    exit;

  STATE_engine_render(handle, camera);
end;

Lo primero que hemos hecho es asegurarnos realmente de que el mundo está bien cargado, de manera contraria 3D State Engine se cerrará con nuestra aplicación a partir del código que especifico en el próximo paso. La segunda función hace el trabajo fundamental de todas nuestras operaciones, representa la imagen que nosotros hemos creado a partir de todas las propiedades asignadas al mundo virtual y a los objetos.

A diferencia de otras engines – como aquellas de nivel bajo y medio que hacía referencia – no necesitaremos preocuparnos por un buffer y por la resolución de la pantalla. Simplemente entregaremos el handle del aparato de renderización de nuestra ventana a la engine.

5. Este es el último paso que debemos complementar a nuestro trabajo antes de compilar el proyecto en Delphi o cualquiera que sea nuestro compilador por primera vez. Lo que haremos será cerrar la form y liberar de memoria el contenido de 3D State Engine y nuestro mundo virtual recién cargado. (Como se puede notar en los ejemplos incluidos que están en cada uno de los lenguajes se incluye algo más de código, pero lo expuesto aquí es lo fundamental para que se ejecute al menos sin ningún tipo de interactividad )

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  STATE_engine_close;
  Action:= CaFree;
end;

NOTA: El archivo 3DState.log que se genera automáticamente cada vez que se abre un archivo soportado no sólo informa de los errores sino también de cuanta memoria y tiempo de CPU fue utilizada en cada uno de los procesos llevados a cabo para representar una imagen, borrar la pantalla, entre otros.

ALGUNAS RECOMENDACIONES FINALES

Primero que todo dedique su tiempo al estudio de las matemáticas básicas de colisiones, perspectiva y rotaciones, traslaciones y demás: es algo que nunca cambia. Segundo, haga un espacio en su programa entre el engine que use y la lógica del juego: no cometa el error de mezclar el contenido que usted ha programado que no requieren el acceso a bibliotecas externas o hacen de mediación con el usuario, con el resto del código que sí lo requiere. Por tanto, dedique unidades de código diferentes a el cómo a va a “pensar” su juego y el cómo va a traducir esto a acciones visibles. Y tercero, una vez que se halla leído todos los manuales habidos y por haber, incluyendo este :D, empiece a estudiar código: ni siquiera importa si ha sido programado para otro engine, la lógica de un juego no cambia mucho, mucho menos si se trata del mismo pan con diferente relleno.

En próximos tutoriales se verá como se mueven las cámaras, o a su defecto, los objetos que nosotros incluimos en el mundo 3D, y alguno que otro consejo relacionado con la práctica de programación. Para aquellos interesados en acceder al sitio www.3dstate.com para bajar por completo todos los editores y la última engine – de la que hasta el momento no dispongo – deberán pasar por un webproxy ubicado en el exterior, ya que al menos desde las conexiones que lo he visto en Cuba no accede desde hace unos 6 meses.



Artículos relacionados


No hay comentarios: