Tutorial de Ray casting 1: Creando nuestro propio framework.

Escrito por el 5 noviembre, 2017

Escribí el último tutorial hace 4 años, tiempo que he desperdiciado en un montón de causas sin que ninguna me haya hecho millonario, como es mi sueño. Así que retomaré el camino de experimentar en la web haciendo tutoriales mientras aprendo nuevas chorradas que nunca podré aplicar en mi trabajo. Al menos hace bonito en el currículum.

Bueno, sin mas preámbulos, el objetivo de este tan ansiado tutorial que me ha llevado cuatro años preparar, es el de modelar un motor gráfico pseudo 3D, como el de aquél afamado videojuego de Id Software, llamado Wolfenstein 3D, y que yo nunca jugué, porque por edad, estoy mas próximo al primer Half-Life (y en el que aún estoy un poco verde para plagiar con javascript, pero todo se andará). Éste es el aspecto que tenía aquél mito del mundo del videojuego, que si bien no fue el primer FPS de la historia, sí que fue por influencia, el padre del género.

Y esta imagen es nuestro particular homenaje. No se le parece mucho, pero todo se andará.

El objetivo de este primer tutorial es hechar a andar este juego. Como novedad, ahora he metido el código en github. El personaje se menea con las típicas teclas WASD.

¿Y cómo funcionan este tipo de motor gráfico? Pues como siempre, con una gran idea feliz. Los ordenadores de la época eran una castaña calculando las decenas de miles de operaciones trigonométricas necesarias para dibujar gráficos. Hoy en día son las GPUs de las tarjetas gráficas las que se encargan de hacer los cálculos de la forma mas eficiente posible, pero entonces era las CPUs las que lo hacían. Fueron videojuegos como Wofenstein 3D y sobre todo sus sucesores como el famoso Doom, también de Id Software, los que hicieron que las tarjetas gráficas empezaran a despegar.

Recapitulando, si los videojuegos usaban la CPU para todo, y ésta no era muy potente, ¿cómo pudieron renderizar un mundo 3d en tiempo real sin que el juego diera trompicones? Fácil: no lo hicieron. Y aquí está la gracia, porque como se puede apreciar en el video, si no es 3D, lo parece. ¿Cómo diantres lo hicieron? Pues para tocar las bolas al personal, lo diré en el siguiente tutorial. Aunque en el título de este artículo doy pistas.

En este tutorial, nos dedicaremos a explicar de forma muy resumida el ecosistema de un videojuego, y programaremos unas cuantas clases con el código que estamos repitiendo en tooooodos los tutoriales anteriores, véase el control del teclado, el cálculo del tiempo delta, el manejo del canvas, atc. Me he cansado, así que ¿que tal si hacemos nuestro propio framework de javascript orientado a hacer juegos chorras? Sí, los hay ha patadas en la web, como pixi, Phaser, o un montón mas que podeis seguir aquí.

Pero perder el tiempo reinventado la rueda es un don que me caracteriza, y aunque mis sucesivos jefes me han amenazado con el despido un montón de veces por ello, es algo que jamás dejaré de hacer. Y en mi blog tengo excusa: Es mío y me lo follo como quiero. Quizá quede mas bonito si digo que el objetivo de hacerlo todo a manopla es el afán académico que me impulsa a escribirlo. Si cuela, quédate con esto último.

JLAB Framework

¿Qué es un framework? Pues no es mas que el conjunto de librerías-clases-funciones comunes que todo dios necesita para programar alguna aplicación en algún contexto determinado. Por ejemplo, para la parte frontend de una web, tenemos a angular, react o vue, todos de javascript. Para el backend en java tenemos spring. Y para php, Laravel Symfony y Zend. Y tantos otros, los hay a patadas. Para los videojuegos también. Unity, para programar videojuegos en multiplataforma, o todos los que puse antes, para javascript.

Y anotad esta fecha, porque hoy publicaré la versión pre-alpha de mi propio framework que revolucionará el mundo de los videojuegos. Jlab framework. Lleno de muy malas ideas, y totalmente ineficiente, pero con la increíble ventaja de que las mierdas relacionadas con la lectura de teclado, el manejo del canvas, las operaciones matemáticas, y tó la pesca aburrida y repetitiva, quedan encapsuladas en sus propias clases, desacopladas del juego, y reutilizables en otros proyectos. No más juegos embarullados donde la mierda de código específico del juego queda mezclada con las mierdas necesarias del ecosistema web. Y además ayuda mucho a reducir complejidad de las clases. Todo son ventajas.

En esta primera versión, el framework está compuesto por cuatro o cinco clases. Pero en el futuro, conforme vaya necesitando mas funcionalidad, pues indudablemente irá creciendo. Tampoco descarto en cambiar totalmente el funcionamiento de una clase y su interfaz, dejándola incompatible con juegos ya programados. Por lo tanto, no aconsejo que uséis este framework para realizar videojuegos propios. Usad uno profesional, o por qué no, haced uno vuestro, copiando ideas de otros, guiño guiño.

Las ideas que hay detrás de las clases del framework ya las he explicado en otros tutoriales. Por lo tnato, aconsejo echar un vistazo rápido a mis viejos tutoriales, al menos al del space invader, donde están los conceptos clave de un videojuego, al de tiles, que explico lo que es un tile, y al del pong, donde uso vectores y una primitiva clase de teclado, todos ellos conceptos necesarios para entender este juego y donde aquí no entraré en profundidad.

La primera entrada de datos. El teclado

Todo videojuego necesita órdenes del jugador, y la interfaz hombre máquina es a través de un teclado y un ratón, al menos hasta el día de hoy. En mi juego, solo necesitaremos un teclado.
Tiene esta pinta:

Uso orientación a objetos con prototipos de la versión anterior de javascript. No estoy muy puesto con la nueva versión ecmascript 6, que ya trae clases nativas. A medio plazo me obligaré a practicar y daré el salto, pero hoy me siento un poco mas cómodo con este sistema. Perro viejo, nuevos trucos… ya sabeis. Al menos hago uso del modo estricto, use strict del principio.

¿Cómo funciona la clase? Pues ya lo vimos. Recojo los eventos de teclado del body, y mapeo un objeto donde su key code es el atributo, y el booleano, su valor. Dicho booleano me dice si una tecla concreta está pulsada, o no. Para que manejar los key codes no sea una pesadilla, he creado otro objeto javascript, Keyboard_Map, que es una especie de enumerador, donde guardo el key code de cada tecla. Y uso ese mapeo en el código de mi juego, haciéndolo mucho mas legible.

La clase las tengo envbueltas en una función auto ejecutable (es decir, se ejecutará en cuando el navegador la lea por primera vez). La idea que hay detrás es hacer el patrón módulo, haciendo que las variables privadas y basurillas propias queden encapsuladas en un closure inaccesible desde el exterior.

Breve explicación de la arquitectura del framework

Y aquí supongo que habrán comenzado los sudores fríos de los lectores menos avezados. Como todo el juego sigue esta forma de declarar las clases, me pararé un poco para explicar cómo funciona.

Esto es una función que imprimirá la variable msg en la consola del navegador.

Este otro código hará lo mismo, pero tiene forma de función autoejecutable:

¿Cuál es la ventaja de esta segunda forma? Pues que dentro de la función tengo un closure, un espacio donde declarar variables y funciones de forma privada (no existen fuera del closure). Si quiero que el mundo “exterior” conozca qué pasa dentro, tengo dos caminos.

Primer camino: Devuelvo algo:

El coódigo imprimirá hola mundo en pantalla, pero nadie podrá llamar a las función printer, porque está delcarada en un closure innacesible desde fuera. Ese closure devuelve la funcíon, sí, pero hará uso de una variable message que sólo existe dentro del closure. Es decir, cuando recupero la función en la variable externa hello_world_printer la función resultante ya tiene un comportamiento predefinido que no podré cambiar. Ésto es una herramienta muy potente si se usa bien, y una locura initeligible si se usa mal.

Segundo camino: Le inyecto un objeto para que lo setee con nuevas propiedades o funciones.

Este camino es lo que hago arriba con el teclado. Yo le paso mi variable JLAB, que está en la super variable global window, para que el módulo la extienda y le meta nueva funcionalidad. En este caso, le mete una función nueva, KeyBoard, que en realidad es una clase, y el enumerador Keyboard_Map con el mapeo de keycodes de todo el teclado. Para extender, hago uso de una función mía llamada JLAB.extend, que es una idea que copié de jquery: Es una función que extiende el objeto que le pasas en el primer parámetro, con los objetos contenidos en el resto de parámetros. Es decir, le copia su contenido, sin machacar las propiedades o métodos que ya tuviera declarados previamente el objeto. Bueno, le machaco las propiedades si tienen el mismo nombre, y se las añado si no. Hace poco me enteré que ecmascript ya lo trae de fábrica: Object.assign. Pero bueno, cosas del directo.

La función extend la tengo en el archivo principal del framework, que es el único que tengo que adjuntar en el html, como después veremos. Él se encargará de cargar el resto de archivos javascript. Tiene esta pinta:

Es otro patrón módulo, que le mete a la super variable global window mi objeto padre JLAB con sus primeros dos métodos:

  • loader: Se encargará de cargar scripts, ejecutando un callback cuando termine.
  • extend: Extiende el objeto pasado en el primer argumento, con el los objetos contenidos en el resto de argumentos.

Volviendo al keyboard, podemos ver cómo funciona el extend al final del todo:

Lo que hace es crear a mi JLAB, que hará de namespace principal, un namespace INPUT nuevo, si no lo tiene declarado ya. A ese namespace JLAB.INPUT, le añadiremos los dos objetos explicados previamente. Y alejop. Mi variable JLAB tiene un namespace INPUT que contiene Keyboard_Map y Keyboard que cualquiera en el resto de la aplicación puede usar.

La salida: El canvas

Todos nuestros juegos se dibujarán en algún sitio. En páginas web, tenemos un elemento HTML llamado canvas con un porrón de instrucciones de dibujo gráfico en 2d y 3d. Otra alternativa es SVG, que tiene sus propios puntos fuertes, ya que al ser elementos que se meten en el DOM de la página, se pùeden manejar mediante estilos CSS y eventos como cualquier otro elemento HTML. Yo opté por el canvas, que para juegos tiene alguna ventaja.

Como se puede ver, la clase es una chorrada. En vez de tener doscientos argumentos, he optado por seguir el método donde le pasas un objeto options, con un montón de propiedades configurables. Mi módulo tiene su propias variables predefinidas en un options interno, y lo que hace la clase es extender este options privado, con el options del constructor, machacando las opciones que el cliente de la clase desee cambiar. La clase Canvas2D proporciona métodos para recuperar su contexto, su ancho, su alto, el elemento canvas, la limpia, e incluso calcula el punto medio.

Vectores

De momento la única clase útil relacionada con la lógica es el vector2D, con todos sus métodos comunes, que es poco mas que un calco que la del pong, probablemente con algún método de más o de menos. Tiene esta forma, que paso olímpicamente de explicar, porque ya lo hice en aquél viejo tutorial, y que aconsejo encarecidamente de revisitar, porque la potencia y flexibilidad que da usar vectores, es fundamental para realizar cualquier juego de la forma más simple posible.

Tiempo delta

El tiempo delta es el tiempo transcurrido desde la última ejecución del bucle principal del juego, para saber qué distancia hay que desplazar las entidades de nuestro juego. Es un concepto fundamental, que expliqué por primera vez en el tutorial del space invader. He extraído su gestión a una clase, facilitando mucho su uso:

El método start arranca el timer, tick recupera el tiempo delta desde el último tick, reseteándolo, y tack recupera el tiempo desde que se hizo el último tick, sin resetearlo. elapsed sirve para saber si ha pasado algún número determinado de segundos desde el último tick, sin resetearlo.

También hay otra clase matemática, con funciones matemáticas típicas como interpolaciones, randoms y alguna chorradilla más, que ni siquiera uso en este primer tutorial. Cuando use alguna en los futuros tutoriales, la pondré.

El mapa

Y ahora empezamos con las clases del juego. En primer lugar, empezaremos por el escenario. El mapa es un conjunto de tiles, contenidos en un array bidimensional. Un uno representará una pared, y un cero, es un tile donde el jugador se puede mover. Usaremos las filas como coordenada X, y las columnas como coordenada Y. Además, la clase tendrá métodos para saber su ancho, su alto, el valor de un tile, y un método para saber si un tile es caminable. Aquí no hay alta ingeniería.

El jugador

A continuación veremos la primera clase que hace uso de ese mapa: El jugador. Cuando se mueve, o mejor dicho, cuando la clase recibe peticiones para moverse, se ha de mirar el mapa para saber si el destino es un tile caminable. Así que el mapa, entre otras opciones, se lo pasamos en su constructor. Tampoco tiene mucho misterio, apenas hace nada más.

El jugador tiene una posición inicial, que es un vector, que indica en qué tile está. También tiene una dirección, que es un vector normalizado (su módulo es 1). Para moverse, sólo ha de sumar su vector de dirección multiplicado por la velocidad a la que se mueve y el tiempo delta, a su vector posición. Pero antes de moverlo, ha de mirar si la posición donde va es caminable. Así que la operación la realiza sobre copias.

Las peticiones de movimiento (arriba, abajo, derecha, izquierda) se las pasamos como un objeto, en vez de leer del teclado directamente. Esa responsabilidad la tiene otro, y así conseguimos desacoplar complejidad.

Nuevo concepto: EL render

Ahora vamos a hacer algo también nuevo: Desacoplar el renderizado del juego, del propio juego. Es decir, mi juego no necesita dibujarse en pantalla para funcionar. Toda la lógica de dibujo la he sacado fuera. Si quisiera, puedo hacer un renderizado basado en códigos ascii, sustituírlo por el renderizado en canvas, y el juego funcionaría igual sin tener que tocar nada.

Como se puede ver, es una chorrada. Mi primer renderizado recibe el mapa y el jugador, que son los elementos que voy a pintar. Esta clase crea el Canvas2D, con el tamaño de tile que le dicen a su constructor. Y simplemente los dibuja. ¿Por qué no he metido el dibujado en la clase del jugador y en la clase del mapa, como antaño? Pues precisamente porque en el futuro, voy a tener que renderizar en pseudo 3d. Y no me interesa estar metiendo tropecientos mil métodos de dibujo en esas clases. ¿Que tal si la responsabilidad de dibujar, la tiene una clase especial que sólo se encarga de dibujar? Fantástico. Incluso podría crear una clase PlayerRender2D y MapRender2D, que usara este MiniMapRender, en vez de tener dos métodos privados llamados _render_map y _render_player. Así consigo que la responsabilidad de dibujar al jugador, sea independiente de la del mapa, y a la vez independiente del render del juego, que sólo se encargaría de llamar a cada clase renderizado particular. Pero hilar tan fino no me hacía falta para este ejemplo. En futuros juegos más complejos, probablemente lo haga.

El jugador no es más que un círculo azul con una flecha apuntando en la dirección donde mire, y el mapa son cuadradillos negros donde hay pared, y blanco si no hay nada. Destacar, como en el tutorial de tiles, que la posición del jugador es un vector de números flotantes, así el jugador puede moverse dentro de un mismo tile, en vez de moverse completamente de tile a tile. Para saber en qué tile está, es quedarse con la parte entera de cada coordenada, y para saber en qué parte del tile está, es mirar la parte decimal. Dibujarlo, es multiplicar sus coordenadas por el ancho del tile que el juego quiera tener. Muy sencillo.

Y al igual que el juego del pong, la forma mas sencilla de dibujar, es trasladar el puntero al centro de la posición del jugador, y dibujarlo como si estuviera en la posición 0, 0. Y luego, deshacemos esas traslaciones.

La clase principal: El juego

Ésto es todo el juego. Facilísimo de entender leyéndolo. Declaro el mapa, el jugador, el teclado, el timer y el render. Le meto dos métodos públicos, getMiniMapCanvas que devuelve el canvas para que el HTML pueda dibujarlo, y start que inicia el time y arranca el bucle principal del juego.

Y luego, los métodos privados. El bucle principal _loop, que hará el update y render contínuamente. Y ese update y ese render no es más que llamar a sus respectivos update y renders donde está delegada la lógica. El update además recupera la información de las teclas pulsadas en el teclado, para comunicarle las peticiones de movimiento al jugador. Ahora el código del juego ya no tiene las tropecientas mil líneas que no aportaban nada, como antaño.

Y ésta es la parte que exponemos al mundo. En ella usamos el JLAB.loader para cargar todos los scripts que necesita el juego. Cuando lo haga, lo arrancamos y devolvemos la instancia del juego recién arrancada. Esto lo hacemos para que el HTML, que pondremos a continuación, pueda recuperar el elemento CANVAS donde se está dibujando el juego, y poder meterlo al dom de la página.

El HTML

Simplemente es ésto:

Usamos el loader para cargar el fichero anterior, que a su vez cargará los scripts que necesita, como ya vimos. Y le pasamos un callback, para que en cuanto cargue, dibuje el canvas en pantalla. Sí, es un primer callback, que llama a otro callback. Parece un poco enrevesado, pero si le das una pensada, no lo es tanto. Si juntara todos los códigos en un solo fichero, me ahorraría ambos callbacks, pero como he tomado este camino de cargarlos dinámicamente, ahora huyo hacia adelante.

Sí. Esto no se parece un pimiento al wolfenstein. En realidad sí, pero sólo que estás viéndolo desde una perspectiva 2D cenital, desde arriba. ¿Cómo puedo verlo de otra forma? Pues quizás te lo estarás imaginando: Cambiando el render por otro que sea 3d, o mejor dicho, 2.5D, que es cómo llamaban los profesionales a este tipo de trampas por entonces. ¿Y qué se necesita para empezar a programar ese nuevo render? Un casteador de rayos, ray casting, que no hay que confundir con el trazado de rayos, o ray tracing. Pero es un concepto que veremos en el siguiente tutorial. No puedo prometer que pasen otros cuatro años.

Etiquetas: , ,

Comentarios (1)

  • Anda que bien que sigas subiendo tutoriales. Pense que ya eras millonario y andabas viviendo la vida loca jajaj

¿Tienes algo que decir?

Gestionado con Wordpress y Stripes Theme Entradas (RSS) | Comentarios (RSS)