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.