En el desarrollo de videojuegos siempre han existido recursos y triquiñuelas de diseño con las que agilizar tareas repetitivas que de otra forma serían un coñazo de implementar. En en el icónico Asteroids, las rocas estelares que salían de las esquinas lo hacían de forma aleatoria aunque siguieran un patrón continuado de velocidad y trayectoria que se amoldaba a una serie de parámetros para que tuviéramos alguna posibilidad de destruirlos o esquivarlos. Aunque iguales conceptualmente, los movimientos que debíamos efectuar en cada partida eran completamente diferentes.
Si bien este ejemplo es peregrino de cojones, sirve como buen punto de partida para hacernos una idea aproximada de lo que supone no regirse por un guión completamente rígido para así poder sorprender al jugador constantemente sin arriesgar con ello el propio equilibrio interno del sistema. Volviendo al Asteroids, la cadencia con la que se generan los asteroides es progresiva, aunque su forma y trayectoria es totalmente aleatoria. Un entorno caótico que a su vez está totalmente controlado. Esta filosofía tiene su reflejo en los métodos de programación y diseño que se utilizan durante el proceso de desarrollo para economizar, y a su vez, ampliar las miras de un producto de concepción inicialmente escueta.
Programación por procedimientos for dummies
También mal denominada ‘procedural’, hace referencia a un paradigma de programación para lenguajes de alto nivel en el que, a grandes rasgos, lo que se pretende es generar una serie de resultados a partir de una serie de entradas (lo que viene a ser un procedimiento) y utilizarlo para generar sistemas complejos de juego a partir de una serie de normas en vez de realizar cálculos y especificar el resultado que queremos de forma individual con el consiguiente gasto computacional, por lo que si se va a realizar una misma tarea cinco veces, mejor crear una sola expresión que se repita tantas veces como haga falta en vez de calcular las cinco operaciones por separado. El resultado, aunque no lo sepamos con exactitud, al menos será ‘válido’.
Ojo a los programadores de la sala, no me estoy refiriendo a programación funcional ni ninguna zarandaja técnica, sino al hecho de que puede resultar mucho más útil definir una serie de reglas rígidas para la generación de un programa con la intención de que, aunque el resultado sea cada vez diferente, se sustente en una serie de normas que le den consistencia a una supuesta (y agradecida en muchos casos) incertidumbre lúdica. Eso sí, no hay que confundir aleatorio con procedural. Me explico.
Un ejemplo práctico. Supongamos que en el videojuego que estamos creando pretendemos que nuestro héroe cruce un enorme y frondoso bosque en busca del castillo donde se encuentra la princesa de marras, pero para ello tenemos que derrotar antes al ogro sandunguero que posee la llave de su celda oculto en algún lugar del escenario. Según la idea que tengamos en mente podemos hacer dos cosas a la hora de implementar la idea:
– Desarrollo rígido: Cogemos y modelamos el puñetero bosque a mano, colocando cada arbusto, cada piedrecita y cada ñordo de cabra en unas coordenadas concretas del mapa independientemente de si estamos hablando de un entorno tridimensional desarrollado en Unity que en un tileset pixelado con algún Maker de andar por casa. Nos curramos un laberinto de la hostia echando más horas que un reloj y ubicamos en un punto del mapa el castillo y en otro al ogro.
– Desarrollo funcional: Nos sentamos un rato y pensamos en un algoritmo de generación de terrenos a partir de una matriz que interconecte un camino aleatorio rodeado de árboles hasta alcanzar tanto al ogro como al castillo, ubicándolos en dos lugares al azar del mapa a partir de una serie de normas que aseguren que existe una ruta transitable entre ambos puntos y el lugar de inicio. Tan solo tenemos que definir el tamaño del bosque y nuestro algoritmo hace el resto, con el aliciente de que podemos reutilizarlo para crear tantos bosques diferentes como nos de la gana o, como estarán empezando a suponer los lectores, crear un Minecraft, un Faster than Light… o un Spelunky.
La generación procedural necesita de una serie de valores de entrada que modifiquen la magnitud de las variables que se van a utilizar, de forma que siguiendo con el ejemplo, podríamos definir un valor de entrada de 1 a 10 que calibre el grado de ‘frondosidad’ del bosque. Y es a partir de entonces cuando surge la magia, pues de forma sabia se puede mezclar lo aleatorio, lo definido y lo moldeable para crear sistemas complejos sobre los que interactuar. ¿Hay alguien que no estuviera ya pensando en la generación de mundos del mencionado Minecraft?
Spelunky y la generación pseudo-aleatoria
Derek Yu es un tipo listo que no inventó la pólvora pero se las ideó para crear escopetas. El Spelunky que conocemos a día de hoy es un remake HD de la primera versión del juego que creó, utilizando Game Maker (ojo ahí), en 2008 para PC como freeware. Esto lo saben todos los sabihondillos, pero lo que no está tan en boca de todos es el hecho de que el desarrollador adaptó la idea vista en Spelunker, un videojuego para máquinas de 8 bits y salones arcade lanzado en 1983 que también recibió, además de varias secuelas, un remake con gráficos renovados en 2008.
De aquel juego se llevó la idea del espeleólogo descendiendo por peligrosas cavernas, las lianas y el hecho de que podemos hacernos daño si caemos desde mucha altura, las bombas, las gemas y demás objetos preciosos o el fantasma que aparece cuando pasamos demasiado tiempo en una misma zona. Lo que Derek Yu se sacó del sobaco fue un inteligentísimo sistema de generación de escenarios y una profundidad en el desarrollo del juego como pocas veces se ha visto. Un plataformas con el sambenito de ‘roguelike’ tan de moda está en los últimos tiempos en donde, por incongruente que parezca, la experiencia es tan aleatoria como milimétricamente planificada. Atención.
Como habrán comprobado los que ya han echado un buen puñado de horas al juego, en los niveles de Spelunky hay una serie de normas básicas que se cumple a rajatabla: siempre hay una vía libre entre la puerta de entrada y la de salida. Da igual que existan caminos sin salida, que aparezca un ídolo, el cofre especial para abrir con llave o una tienda donde comprar ítems; el camino debe existir.
Los niveles están compuestos por una matriz de 4×4 en la que se genera un camino fijo desde el nivel de entrada, situado en algún punto de la primera fila, hasta la última ubicada en la inferior. El siguiente mapa está generado a partir del código fuente del título original de 2008, pero su planteamiento es extensible al del remake:
Cada elemento de esa matriz es un bloque que a su vez está dividido en una cuadrícula de 10×8 elementos de un tileset. Existen cuatro tipos diferentes de bloques:
-
0: Es un bloque que no pertenece a la ruta de solución entre la entrada y la salida.
-
1: Es un bloque con salidas a la izquierda y a la derecha. Por defecto, el bloque donde se ubica la entrada es de este tipo.
-
2: Es un bloque con salidas izquierda, derecha e inferior.
-
3: Es un bloque con salidas izquierda, derecha y superior. El que hay encima suya siempre será de tipo 2.
No es complicado comprender el sistema. Para simplificar la mecánica, digamos que la generación de cada bloque se produce con la tirada de un dado de 5 caras: si sale un 1 o un 2, se genera el camino de solución hacia la izquierda, si sale un 3 o un 4, se genera el camino hacia la derecha, mientras que si sale un 5, la solución es hacia abajo. Si se llega a uno de los bordes laterales del escenario, automáticamente será un 5 también. Existen algunas restricciones más para dar coherencia al mapa final, pero se sobreentiende cómo va, ¿no?
A su vez, los elementos de la cuadrícula de cada bloque también tienen una serie de normas, de forma que aunque mantengan cada uno de ellos una estructura similar, las pepitas, los bloques desplazables, ítems y enemigos se colocarán de forma diferente cada vez siguiendo un sistema de distribuciones parecido al del párrafo anterior.
Con esto, Derek Yu consiguió que cada partida a su juego fuera completamente diferente, aunque gracias a la cantidad de condicionantes rígidos tenidos en cuenta, hay una serie de elementos comunes que siempre estarán ahí, ya sea la chica o el cofre con la llave, por citar alguna cosa sin destripar los enfermizos secretos que alberga el juego y que no tienen cabida en este texto sesudo, rico y para toda la familia.
¿Y qué pasa con todo esto?
Pues que con un poco de ingenio podemos conseguir, además de un considerable ahorro de cómputo y recursos, que nuestro videojuego sea la pera limonera para el jugador y expanda hasta el infinito y más allá su ciclo de vida. El hecho de que muchos videojuegos independientes lo utilicen es precisamente para estirar un desarrollo corto mediante muchas iteraciones consecutivas donde la curva de aprendizaje y la mejora paulatina en nuestros resultados sean la mayor compensación para el jugador.
Este tipo de programación tiene unas raíces mucho más locas que llevan al extremo el uso de semillas de generación como es el caso de Dwarf Fortress, que a partir de una cadena de entrada es capaz de construir de la nada un mundo con cientos de años de antigüedad, una orografía moldeada por el paso de los siglos y unos accidentes naturales consecuentes con su entorno, todo ello en un vomitivo y enternecedor ASCII, eso sí. También está lo que sucedió con The Elder Scrolls II: Daggerfall, que tan libre fue la creación de un entorno de miles de kilómetros cuadrados utilizando un sistema de generación pseudo-aleatorio que no había cojones de pasarse el juego de la cantidad de bugs que tenía.
La generación procedural es difícil de plantear pero mucho más barata y optimizable que una implementación manual, por no hablar de que abre una puerta que por desgracia cada vez está más cerrada en el desarrollo contemporáneo de videojuegos: la capacidad de sorprender. Si sabemos cuándo va salir ese zombie de la ventana o qué tipo de nave enemiga nos va a atacar en el siguiente punto, la situación se solventa mediante el ensayo y error, dejando a un lado la capacidad intrínseca del jugador para superar los desafíos a partir del conocimiento acumulado y su capacidad de deducción y previsión.
Fuentes
Imagen de cabecera | ma5h
Source Code de Spelunky (GM8) | http://spelunkyworld.com/original.html
Spelunky Generator Lessons | http://tinysubversions.com/spelunkyGen/
Artículo publicado originalmente en El Pixel Ilustre.