Dungeon Generation 1
Tal y como se ha explorado aquí, una forma de crear mejor contenido generado proceduralmente es imitar el procedimiento que sigue un diseñador. En el caso de generar la dungeon de un videojuego es extremadamente complejo. Entre otras cosas, hay que tener en cuenta: Topología de la dungeon, puzzles, distribución de enemigos, distribución de objetos y recompensas, estilo artístico, verificar que sea posible completarlo… Son tantas cosas que sería imposible hacerlo solo de una vez. Además, la naturaleza de cada una de esas propiedades son muy distintas, así que sería apropiado usar distintas técnicas de PCG. Por ello, una buena solución usar una técnica de generación secuencial, de forma que primero se crean los componentes más abstractos, y luego los siguientes componentes que construyen sobre los anterioes.
El primer nivel en la generación podría ser la topología o grafo. Suponiendo que la dungeon está compuesta por habitaciones y pasillos, los nodos del grafo serán las habitaciones y las aristas los pasillos. Además, haremos que el grafo sea en formato de árbol ya que es más simple. En el futuro lo haré para que incorpore ciclos usando reglas que se explican aquí. Pero aquí solamente se tendrán en cuenta las siguientes propiedades:
- Formato de árbol.
- Grafo conexo.
- Grafo planar.
- Máximo 4 aristas por nodo.
- Del nodo inicial salen 3 aristas.
- Existen puertas cerradas con llave.
- Existen nodos que contienen llaves.
- Las llaves se tienen que poder coger antes que atravesar la puerta.
A continuación de muestra un ejemplo de grafo que generaría. El nodo inicial del árbol será el inicio de la dungeon y el nodo final estará en una de las ramas. Los nodos que se encuentran entre el inicio y el final forman parte del camino principal, y todas las demás ramas son caminos secundarios. Al existir puertas cerradas, el jugador tendrá que explorar ramas secundarias para poder completar la dungeon. Las aristas rojas son puertas cerradas, y los nodos rojos contienen la llave. De esta forma, solamente las ramas con llaves son obligatorias para completar la dungeon, mientras que las otras ramas opcionales pueden contener recompensas para el jugador.
Para hacer que la dungeon sea de dificultad creciente al explorarla, se puede asignar un número a cada uno de los nodos que determina la dificultad de la sala, el cual puede ser simplemente su distancia con el nodo inicial. De esta forma, una etapa posterior de PCG puede elegir qué enemigos y objetos poner en la sala en función de su dificultad.
Por cierto, con “llave” en este grafo se entiende cualquier cosa capaz de desbloquear una arista, es una abstracción. A nivel de implementación, podría ser una habilidad que adquiere el jugador, una palanca con un puzzle que abre una puerta, recibir ayuda de un npc que le desbloquee el camino, reducir el nivel de peligro matando a todos los enemigos de la sala, o cualquier cosa imaginable que cumpla esa función.
Para generar la dungeon estaría bien poder controlar una serie de parámetros como el número de nodos, de llaves, de ramas, y una distancia mínima entre inicio y final. En clingo se puede codificar todo esto usando constantes:
|
|
En vez de dar un número fijo, se codifican rangos en algunos valores para que que a clingo le sea más facil encontrar la solución, además de que podría no encontrar una solución con parámetros fijos. Los predicados más importantes son:
- node/1: Indica qué nodos existen.
- edge/2: Qué aristas hay entre 2 nodos.
- connected/2: Qué nodos están conectados.
- adyacent/2: Qué nodos son adyacentes, sin importar la dirección de la arista.
- distance/3: La distancia que hay entre 2 nodos.
- leafNode/1: Qué nodos son hojas del árbol.
- key/1: Qué llaves hay.
- edgeKey/3: Qué llave tiene asociada una arista.
- nodeKey/2: Qué llave tiene asociado un nodo.
Para la codificación del programa se ha usado el método de Generate y Restriction: Primero se generan todas las posibles soluciones, y luego se eligen entre todas ellas a través de restriciones. Además, hay un pequeño codigo python agregado que permite visualizar de forma rápida el resultado (es necesario tener graphviz para ello). Para la generación de llaves lo que se hace es dividir el grafo en áreas, con las aristas con puertas separándolas. Se etiquetan esas áreas en función de la llave asociada a la puerta, para luego obligar que esa misma llave esté en la misma área que su puerta correspondiente. A parte del código, tambien se muestra una imagen del resultado que da.
|
|
Si se ejecuta el código tal cual con los mismos parámetros, Clingo siempre dará el mismo resultado, lo cual no es muy deseable para un sistema de PCG. Por eso, se puede llamar por ejemplo con los parámetros “–sign-def=rnd –rand-freq=0.2 –seed=732719” para que genere grafos de forma aleatoria en función de la seed proporcionada. Se podrían añadir más reglas para perfeccionar los resultados, pero tal como está ahora es demasiado lento. En mi máquina tarda entre 20 segundos y varios minutos en función de los parámetros, lo cual es inaceptable si se quiere poner este sistema en un videojuego rogue like. Por ello, en el siguiente artículo codificaré este mismo generador pero haciendo uso de clingo multi-shot. Con este método la solución se construye por partes en vez de toda de una vez, lo cual lo hace más eficiente.