08 junio 2024

Cómo añadirle barras de vida a tus personajes en Godot

Es muy habitual que los personajes de los juegos cuenten con barras de progreso, a sus pies o sobre sus cabezas, para mostrar la cantidad de vida que les queda.

Godot con 2 nodos ideales para esto: ProgressBar y TextureProgressBar. Con el primero tenemos todo lo que podemos necesitar para una barra básica. El segundo nodo es una evolución el primero, que permite dotarle de una apariencia visual más atractiva, usando texturas en vez de colores plano. En este tutorial nos centraremos en ProgressBar, controlado este, usar el nodo TextureProgressBar es relativamente sencillo.

En Godot2D, ponerle una barra de progreso a tu personaje es muy simple. Te basta con añadir el nodo ProgressBar a la jerarquía de la escena del personaje y luego redimensionar y situar la barra usando sus puntos de agarre visuales.


A partir de ahí, sólo te quedaría configurar la apariencia visual de la barra e implementar su script, tal y como veremos más adelante.

Sin embargo, añadirle una barra de vida a un personaje de un juego 3D no es tan sencillo. El problema es que ProgressBar es un nodo 2D y no se puede añadir directamente a un juego 3D. De hecho, si intentas añadir el nodo a un personaje de un juego 3D, tal y como hemos hecho en el caso del 2D, verás que el editor de la escena conmuta a la pestaña 2D y no te deja colocar el nodo como parte de tu escena 3D.

El truco está en usar un nodo que ya empleamos en el artículo sobre los minimaps en Godot: el nodo SubViewport. Este nodo crea un área de visualización independiente en una región de la pantalla. En el caso de los minimaps lo empleamos para mostrar el punto de vista de la cámara cenital, al mismo tiempo que el resto de la pantalla continuaba mostrando lo que veía la cámara principal. En este caso, el papel del nodo será mostrar un elemento 2D en una región de la pantalla de un juego 3D.

En el caso de los minimaps, el truco funcionaba haciendo que el nodo Camera3D fuera hijo del SubViewport y colocando este en la región de la pantalla que nos interesase, usando un nodo SubViewportContainer.  

En el caso de las barras de vida se hace de una manera similar: colocas el nodo ProgressBar como hijo del SubViewport, pero en este caso ya no puedes usar el nodo SubViewportContainer porque este sitúa las cosas en una posición concreta de la pantalla y no de manera relativa a un personaje, para que se mueva con él por el escenario. Para eso, lo que podemos utilizar es un nodo Sprite3D. Este nodo, sí que puede colocarse en una posición relativa a un personaje, como parte de la jerarquía de su escena. Así que haremos que los nodos SubViewport y ProgressBar sean hijos del Sprite3D. 



Al terminar, la barra de vida seguirá sin verse. Esto se debe a que tenemos que configurar el Sprite3D para que sea este el que muestre la barra de vida. Dicho de otra manera, tenemos que configurar el Sprite3D para que este muestre la imagen renderizada por el SubViewport. Para eso, tenemos que buscar en el inspector la propiedad Texture del Sprite3D. Cuando la encuentres estará vacía, por lo que deberás crear en el ella un New ViewportTexture y seleccionar nuestro SubViewport en la ventana emergente que salga. Será a partir de ese momento cuando la barra se hará visible dentro de la escena de nuestro personaje.



Lo normal es que, más allá del cacharreo que puedas hacer para probar, concentres todos los nodos de la barra en su propia escena, de manera que puedas reutilizarla en personajes diferentes. Yo es lo que he hecho y es lo que se ve en la captura anterior.

Hasta ahí lo más difícil de la configuración de las barras de vida. Lo siguiente sería configurar la apariencia visual de la barra. Fijaremos su tamaño con la propiedad Size del SubViewport. Yo suelo desactivar la propiedad Show Percentage de ProgressBar para que no se muestre el tanto por ciento. En cuanto a los colores de la barra, tendremos que buscar la sección Themes Overrides, en el inspector del ProgressBar. Allí, tendremos que ampliar otra sección denominada Styles. Tiene dos apartados: Background y Fill. El primero sirve para definir la apariencia visual del fondo de la barra y el segundo la de la barra principal. Lo más sencillo es dotar a esas propiedades de sendos recursos StyleBoxFlat y editar su propiedad BG Color con los colores que deseemos. Por ejemplo, podríamos fijar el BG Color del fondo a un color con Alpha 0, para que el fondo fuese completamente transparente, y el color de la barra a azul.




Lo que quedaría sería la lógica para actualizar los valores de la barra conforme lo hiciesen los del personaje. 

Las tres propiedades básicas de un ProgressBar son: MaxValue, MinValue y Value. Las dos primeras se suelen fijar al principio, por ejemplo en el método _Ready(), y definen los valores máximo y mínimo que abarcará la barra. Por su parte, la propiedad Value es la que iremos actualizando a lo largo del juego para que el ProgressBar actualice la longitud de la barra en función del valor de Value en relación con el mínimo y el máximo.

Una aproximación que suelo seguir es crear un script  C# para el Sprite3D, con una referencia al ProgressBar:



A partir de esa referencia, creo propiedades para los valores máximo, mínimo y actual, de manera que cuando se modifiquen estos valores desde fuera del script, se actualice también su equivalente dentro de ProgressBar. Por ejemplo, para el valor máximo:


Las propiedades para los valores mínimo y actual son prácticamente idénticas.

He exportado estas propiedades para poder ponerle valores iniciales desde el inspector. Para que los valores que hayamos configurado en el inspector se apliquen a la barra de progreso, al comenzar el juego, tendremos que tirar del método _Ready():


Una vez arrancado el juego, serán las propiedades las que actualicen el ProgressBar ya que para entonces la referencia a esta no será null.

Lo que queda es ofrecer al exterior un medio para actualizar el valor de la propiedad CurrentValue y, con eso, el de la barra. Puedes hacerlo de muchas maneras, por ejemplo, haciendo que los que scripts que vayan a actualizar la barrar tengan un referencia al script de la barra y, a través de ella, manipulen la propiedad CurrentValue. Sería una aproximación válida, pero incrementaría el acoplamiento al obligar a establecer una referencia directa entre objetos. 

Otra opción, que reduzca el acoplamiento, es hacer que los scripts que modifican el nivel de vida emitan señales (la versión de Godot de los eventos) cada vez que se produzca un cambio y que la barra se suscriba a esas señales. En mi ejemplo he seguido ese enfoque y he incluido un handler, en el script de la barra, para responder a ese tipo de señales:


Luego, he suscrito ese handler a la señal emitida por el personaje cada vez que recibe daño:


En el caso de mi ejemplo, la señal del daño la emite el script CharacterLifeManager.cs, el cual define la señal de la siguiente manera:


La emisión de la señal anterior se realiza desde la línea 46 del método ApplyDamage() del script anterior, al cual se llama cada vez que el personaje recibe un golpe de sus contrincantes:


El hecho de usar deltas, en vez de valores absolutos, en el handler OnCurrentValueChanged() permite suscribirlo no sólo a señales de daño (que transmiten deltas negativas) como a señales de curación (cuyas deltas son positivas). En este caso, el script que gestiona las curaciones, cuando el jugador recoge una poción, emite una señal con delta positiva a la que podemos suscribirnos al igual que hicimos con la señal de daño:


La definición de la señal y su lanzamiento es muy similar al caso de la señal de daño, así que no la voy a repasar aquí.

Al basarnos en señales, hemos reducido el acoplamiento entre componentes y hemos conseguido una barra que esa altamente reutilizable ya que la podremos aplicar a cualquier elemento del juego siempre que este emita señales con el valor del incremento (ya sea positivo o negativo) cada vez que se produzca un cambio en el valor monitorizado. De esta manera, podremos reutilizar esta barra de vida, que hemos implementado aquí, con otros componentes para mostrar valores que no tienen por qué ser de vida, como por ejemplo munición, karma o nivel de blindaje.

Aquí acabamos el artículo, espero que te haya gustado. Si las explicaciones y las capturas no te hubiesen sido suficientes, puedes bajar el proyecto que he utilizado de ejemplo desde mi repositorio DungeonRPG en GitHub. He utilizado como base el mini juego que hice al seguir el curso de GameDevTV "Godot 4 C# Action Adventure: Build your own 2.5D RPG", el cual recomiendo muchísimo. El curso no abarca las barras de vida, pero el minijuego que implementa es una base excelente para aprender a crearlas.

01 junio 2024

Curso "Godot 4 Shaders: Craft Stunning Visuals" de GameDev.TV

Sigo haciendo los cursos que me compré en el pack de Humble Bundle, y esta vez le ha tocado a "Godot 4 Shaders: Craft Stunning Visuals" de GameDevTV.

A diferencia de otros cursos de GameDevTV, este no está en Udemy, por lo que no te quedará otra que verlo en la plataforma de GameDevTV. Esto tiene un par de pegas: no tienes subtítulos en inglés y no tienes la posibilidad de acelerar la velocidad del video. Afortunadamente, el autor del curso tiene buena pronunciación, por lo que se le entiende todo, y habla con buena cadencia, así que no hay necesidad de acelerar.

El curso dura unas 5 horas y se enfoca a darte un primer baño en shaders, que te quite el miedo a profundizar por tu lado. Para eso, se estructura en tres partes:

  • Fundamentos de los shaders.
  • Shaders 2D, enfocada a crear efectos habituales en este tipo de juegos: flashes, paso a monocromo, disoluciones, enmascaramientos, scrolls y distorsiones.
  • Shaders3D. Enfocada a fijar las propiedades básicas de albedo, metallic, roughness y normal. Aunque no profundiza mucho, lo remata con un tutorial para hacer el efecto de agua.
Ya había hecho cursos de shaders en Unity, pero este era el primero enfocado a Godot, y debo decir que me ha gustado mucho. En los cursos que había hecho de shaders en Unity, iba repitiendo las acciones de los tutoriales, pero me quedaba la sensación de que en realidad no estaba entendiendo los conceptos generales. Básicamente, repetía las cosas, pero no sabía muy bien por qué las hacía. Afortunadamente, este ha sido el primer curso en el que no me ha pasado eso. El autor hace un esfuerzo por explicar conceptos más básicos, como las fases fragment o vertex, o qué son las coordenadas UV, y el esfuerzo se agradece muchísimo. Por primera vez he entendido por qué hacía las cosas.

Otra agradable sorpresa ha sido que no me ha importunado que el autor prescindiese de los visual shaders de Godot y se centrase en los shaders de código. A priori, me parecía que eso podría hacer más complicado el curso, pero ahora creo que lo ha simplificado bastante. Si has programado en C#, el lenguaje que usa Godot para el código de sus shaders resulta muy parecido. No te cuesta ni 10 minutos cogerle el tranquillo. A partir de ahí, me da la sensación de que los shaders en código quedan más concisos que sus equivalentes visuales, lo que me ha facilitado seguir el curso.

En lo referente a contenidos, el apartado de shaders 2D me ha parecido muy completo. Sin embargo, el de shaders 3D me ha parecido que se queda algo corto. En la parte de 3D se queda en lo más básico.

En todo caso, para un curso de 5 horas, creo que está muy bien y se hace con gusto. La inmediatez de Godot hace comodísimo seguir el curso. ¡Qué diferencia con Unity y sus recargas de dominio cada vez que rozas el editor!.

Así que nada: os recomiendo el curso.