30 agosto 2024

Cómo implementar una niebla de guerra de dos niveles (FOW de dos niveles) en Godot

Mapa con FOW de dos niveles
En mi anterior artículo expliqué cómo se puede implementar un mapa de nivel en Godot con niebla de guerra. Aquel mapa aparecía al principio en negro, pero se iba destapando conforme el personaje se iba paseando por él. Se trataba de un mapa de un único nivel, porque una vez que el personaje transitaba por una zona, esta quedaba completamente destapada y eran visibles todos los enemigos que pasasen por ella, aunque el personaje principal estuviese lejos.

Otros juegos implementan FOW de dos niveles, en los que el área inmediatamente circundante al personaje, dentro de su rango de visión, queda completamente destapada, y los enemigos que pasan por ella plenamente visibles, mientras que las zonas que para las zonas que se hayan quedado atrás se ve sólo la parte estática del mapa, pero no los enemigos que pueda haber por allí. Generalmente, los mapas con FOW de dos niveles muestran con un tono más apagado las zonas alejadas, donde sólo se ve la parte estática, para distinguirla de la parte donde sí puede aparecer enemigos. 

Resulta que, cuando se tiene un mapa con un nivel de FOW, implementar un segundo nivel es tremendamente fácil. Así que voy a partir del punto en el que lo dejamos al final del artículo anterior. Si no lo has leído aún, es imprescindible que lo hagas, utilizando si quieres el enlace al comienzo de este. Para no repetirme, aquí voy a dar por entendido todo lo tratado en aquel artículo.

Tienes todo el código de este artículo en su repositorio de GitHub.

Partiendo de aquel mapa, debemos plantearnos que el shader que utilizábamos para el mapa (Assets/Shaders/Map.gdshader) lo que hacía era comprobar para cada pixel de la imagen del mapa si ese mismo pixel estaba coloreado en el mapa de la máscara renderizado por SubViewportMask. Si no estaba coloreado en el mapa de la máscara, asumía que el pixel no era visible y lo pintaba de negro, en vez de pintarlo con el color que le llegaba desde el SubViewport del mapa (recuerda que el TextureRect del mapa estaba conectado a la imagen renderizada por SubviewportMap). Conceptualmente, el mapa de la máscara se corresponde con las zonas ya recorridas.

En este nuevo mapa queremos darle una vuelta más de tuerca al tema distinguiendo las zonas ya recorridas (el mapa de la máscara) de las zonas directamente visibles. A las primeras, las dejaremos en un tono más apagado y sólo mostrando la parte estática, y a las segundas las seguiremos renderizando como antes.

¿Cuáles son las zonas directamente visibles? las del mapa de shapes. Las que se renderizan en SubviewportShapes con una cámara que sólo capta el círculo blanco colocado sobre el personaje. Hasta ahora habíamos usado el mapa de shapes para el shader de la máscara (Assets/Shaders/MapMask.gdshader), pero ahora lo usaremos también en el del mapa para saber qué píxeles del mapa se encuentran en la zona visible del jugador.

Una vez que ya sabemos distinguir, dentro de las zonas ya descubiertas, cuáles están en las zonas visibles y cuales no, tenemos que renderizar una imagen que sólo muestre la parte visible del mapa. Al igual que en el resto de los casos, esto se puede lograr con un SubViewport. En mi caso, me ha bastado con copiar SubviewportMap y a su cámara hija. A la copia la he llamado SubViewportStatic.

Jerarquía del mapa

Para que ese SubViewport sólo muestre la parte estática, hay que configurar la Cull Mask de su cámara para que sólo capte la capa 1, en la que he situado todos los elementos estáticos del escenario.

Captura del inspector de la cámara

Fíjate que la misma cámara, en el SubViewportMap, está configurada para captar las capas 1 y 4 (la 1 para los objetos estáticos del escenario y la 4 para los iconos identificativos de los personajes, colocados sobre ellos).

Para que el tono de la imagen captada por la cámara sea más apagado, tiene que dotarla de un recurso Environment (en el campo del mismo nombre). Cuando hayas creado dicho recurso, puedes pinchar en él para configurarlo. En el apartado Adjustments, he pinchado en Enabled y he bajado el brillo que venía por defecto a la mitad.

Configuración del Environment de la cámara

 Fíjate que el recurso Environment tiene muchísimos más apartados, así que imagina la cantidad de cosas que podrías hacerle a la imagen captada por la cámara.

Con eso ya tenemos una imagen más apagada del escenario, pero sin personajes. Justo la parte estática que necesitábamos. El shader del mapa recibirá esa imagen a través de un Uniform (Static Map Texture), que configuraremos a través del inspector para que apunte a SubViewportStatic.

Inspector del shader del mapa

Por debajo, el código del shader es muy parecido al del artículo anterior.

Nuevo código del shader

La principal novedad es el nuevo uniform que mencionábamos antes (línea 12) para recibir la imagen del mapa de elementos estáticos, y las líneas 16 a 18. Si el código llega hasta ese punto es porque en la línea 14 se ha llegado a la conclusión de que, al estar marcado con color en el mapa de la máscara, ese pixel se corresponde con una zona ya explorada. En la línea 16 se comprueba si ese pixel, además de ser de una zona explorada, se corresponde con un pixel coloreado en el mapa de shapes (es decir de las zonas directamente visibles). De no ser así, el pixel recibe el color del pixel equivalente en la imagen del mapa estático (línea 17). En caso de que el pixel sí estuviese coloreado en el mapa de shapes (y su canal rojo fuera distinto de 0), ese pixel recibiría el color por defecto que le llega desde SubViewportMap (el que muestra los iconos de los enemigos).

El resultado es el de la imagen que abre este artículo, con el área circundante al personaje mostrando a los enemigos más cercanos, las zonas exploradas más lejanas mostrando sólo los elementos estáticos del escenario, y las no exploradas coloreadas en negro.