DRAIN Devlog #1: Introducción y estado actual
¡Hola! 👋 Bienvenidos al primer post del Devlog (blog de desarrollo) de DRAIN y del equipo PENKINTON.
Nos gustaría introducirnos primero. Somos Esaú y Vidal, fundadores de PENKINTON y creadores de DRAIN. Podría decirse que ambos somos los directores, pero estamos divididos en tantos roles que resulta complicado definir uno solo. En términos de carga de trabajo, Esaú se encarga del arte y Vidal de la programación.
Con eso en mente, describiremos un poco más el Devlog.
Como indica el nombre, en el Devlog hablaremos de desarrollo. En este caso, del desarrollo de DRAIN.
En él abordaremos los avances del juego desde el último post, desde un punto de vista técnico, con la finalidad de ser transparentes con la comunidad sobre nuestro progreso, pero también para compartir conocimientos y escuchar sugerencias para mejorar la calidad. También nos servirá para desahogarnos.
Siendo completamente honestos, no podemos prometer un horario de publicación para este blog. Con la fecha de lanzamiento de la demo a la vuelta de la esquina (abril-mayo), nuestra prioridad es terminarla. Aun así, haremos lo posible por publicar, aunque sea, un post al mes.
Como este es el primer post, haremos un resumen de lo que llevamos hasta ahora, seguido de nuestros planes.
Cada porción estará escrita por el encargado de ese apartado.
Arte [Esaú]
Rework de mapas
(odié los efectos de partículas)
En estos cambios que hice durante el rework de estos mapas incluye:
Cambio de texturas
Cambio de modelos
Cambios de iluminación global
Creación de nuevos efectos de partículas
Creación de nuevos efectos de fuego con shaders six point light
Optimizaciones generales (aunque aún se puede mejorar un poco más este apartado)
¿Por qué decidí hacer estos cambios en mapas que ya estaban terminados?
Bueno, simplemente porque no terminaba de encajar con mi propio estándar de calidad, sentía que las texturas y la geometría no eran convincentes con el estilo artístico que quería darle al juego. Pensé que, para que el juego se viera increíble, hacía falta poner geometría por todos lados y la verdad es que aprendí que un buen texturizado es mucho más valioso que poner geometría a diestra y siniestra (sin mencionar más óptimo). En la escena más pesada y sin usar ningún tipo de reescalador, tenemos 150 fps como mínimo en una RX 6600 en Ultra a 1080. Aprendí a usar software de la Elemental Suite de Janga FX para poder hacer nuevos shaders de fuego y mejorar la calidad visual general del juego, además de que seguramente no solo mejorará el fuego, sino un sinfín de efectos volumétricos futuros.
¿Por qué odié los efectos de partículas?
Se tragan el rendimiento xD Tuve que experimentar durante muchísimo tiempo para lograr un equilibrio visual entre lo decente y lo de buen rendimiento.
¿Qué sigue?
Bueno, básicamente terminar el resto del mapeado para la demo en abril aprovechando los nuevos conocimientos que adquirí.
Programación [Vidal]
Hay muchas cosas de las que me gustaría hablar desde la perspectiva de la programación, pero como este post es para dar a conocer el estado actual del juego, voy a centrarme en lo más reciente.
Combate
Los conceptos para el gameplay del combate han estado desde hace 1 año, pero por una u otra razón, no habían pasado de ser prototipos a algo avanzado que nos convenciera. Aunque aún falta mucho por pulir, lo que conseguimos en estas últimas semanas es una muy buena base.
En realidad, empecé a reprogramar esta última iteración hace unos 2 meses. El código de la versión anterior era decente. Estaba diseñado para estar completamente aislado de otros sistemas, con la flexibilidad de cambiar prácticamente todo mediante parámetros. Por esa razón y porque aún no teníamos los detalles bien establecidos, el código terminó enredándose un poco demasiado. Al mismo tiempo, el gameplay seguía sin convencernos, por lo que era un buen momento para rehacerlo.
Empecé modificando el código existente, pero con una visión más clara, rápidamente decidí reorganizar todo. Las variables y funciones pasaron a estar en lugares con más sentido, dejando cierta flexibilidad, pero también estableciendo restricciones. Puede sonar negativo, pero en realidad pensé en estas restricciones como una forma de mantener el gameplay de combate por el mismo camino.
Como mencioné, el código anterior estaba completamente aislado de otros sistemas. Ahora, los personajes guardan su información de combate en sus variables. El sistema de combate simplemente toma esas variables y las usa como necesite, dependiendo de cada combate. Puede sonar extraño en comparación con otros juegos, pero tiene sentido si se entiende que el modo de exploración es completamente diferente al de combate.
Otra cosa interesante de la que me gustaría hablar es la ralentización del tiempo en función del contexto, como durante los impactos. Aunque Godot tiene una variable para esto (Engine.time_scale), se necesitó una planeación tanto de código como de nodos. Algunas cosas tenían que ralentizarse, pero no lo hacían, como los efectos de fuego, para los cuales fue necesario reemplazar la variable TIME en su shader por una variable global actualizada manualmente con base en el tiempo del "mundo", no del motor. Otra cosa fueron algunos temporizadores, los cuales fueron un poco molestos de detectar, ya que las ralentizaciones son muy cortas y solo algunos tenían que ser afectados, no todos. Puede que encontremos más detalles como estos, pero funciona bien por ahora.
Animaciones
Me refiero a cómo implementarlas, no a hacerlas. Esto fue lo que más tiempo me llevó decidir la mejor forma de hacerlo.
Hablo del AnimationTree. Es un sistema robusto, pero eso mismo me generó más dudas. ¿Cómo debía implementar diferentes capas de animación independientes en el mismo modelo? Al mismo tiempo, para algunas de ellas es necesario mezclarlas (blending). Por ejemplo, la protagonista cierra las manos para sujetar sus dagas, pero también camina o pelea; hay que tener en cuenta que pueden ser distintos estilos.
En resumen, son conjuntos de animaciones con capas sobre capas. Lo que terminé haciendo es algo así:
La raíz donde las diferentes partes del cuerpo se mezclan (BlendTree)
La capa donde se intercambian los estados entre el modo de exploración y el modo de combate (StateMachine)
La capa del modo de exploración (StateMachine)
La capa donde se intercambian los estilos de combate (StateMachine)
Una capa por cada estilo de combate (StateMachines)
Ni hablar del código necesario para acceder a todas estas capas según el contexto. Pero el post se está alargando mucho y me gustaría desahogarme sobre una cosa más relacionada con las animaciones.
He visto peores y debería ser posible limpiarlo un poco, pero muchas de estas conexiones se usan para lograr transiciones suaves pero reactivas. Aún faltan animaciones, por cierto.
Los observadores se habrán dado cuenta de que la gran mayoría de las conexiones no son automáticas. Al inicio, intenté usar el sistema de condiciones y expresiones del StateMachine lo más que pude, pero me di cuenta de que me estaba forzando a pensar más de lo que necesitaba. Fue más directo y simple para mí manejar esto desde el código, aunque tal vez para otros no lo sea.
Esto lleva a otro tema del que me gustaría hablar, pero, de nuevo, el post se está alargando demasiado, así que lo dejaré para futuros posts.
Tutorial
Para terminar con lo último en lo que estoy trabajando y un poco relacionado con el tema de las ralentizaciones, tuve que implementar un sistema de pausa, pero sin pausar todo, como la interfaz.
La gente un poco experimentada en Godot ya habrá pensado en la propiedad de los nodos para escoger el comportamiento al pausar el árbol de nodos. En su mayor parte, lograr estas pausas fue cuestión de planear qué nodos debían pausarse, cuáles no y cuáles debían heredar este comportamiento. No fue tan sencillo como podría pensarse, ya que hay muchos sistemas globales y carga de recursos, pero tampoco fue difícil.
Lo más complicado es que durante estas pausas se muestran instrucciones que reaccionan a las acciones del jugador. Eso significa que otros sistemas deben seguir funcionando completamente, como el gestor de entrada (Input) para detectar los botones, pero otros deben seguir funcionando a medias, como el del combate. Esto es porque la mayoría de los temporizadores, funciones y variables deben pausarse, pero el código necesario para reaccionar a las acciones del jugador debe seguir procesándose y detectar si la acción fue correcta. No hubo una solución mágica ni nada por el estilo. Tuve que organizar el código de forma que siguiera funcionando de forma independiente del tutorial, pero que permitiera al tutorial tomar un poco el control.
Una vez resuelto eso, fue cuestión de programar cada paso del tutorial. No fue divertido y rompí el combate de formas que no entendí, pero ya funciona.
¿Qué sigue?
Aún faltan funciones por programar en el combate y estamos en medio de discusiones sobre su interfaz. En mi caso, pensar en cómo implementarla. Después de eso, programaré la última parte de la secuencia inicial del juego.
Muchas gracias por leer hasta aquí y disculpas por alargar tanto el post. Nos apasiona el desarrollo de juegos y queríamos compartirlo con ustedes, así que, por favor, no duden en comentar sus dudas, sugerencias y observaciones.