Tutorial de Godot – Parte 09: Diseñando la GUI del juego

En este tutorial, aprenderemos cómo crear una interfaz gráfica de usuario para nuestro juego de rol , utilizando algunos nodos simples. También veremos cómo actualizarlo usando el sistema de eventos y señales de Godot.

Estadísticas del jugador

Por ahora, nos ocuparemos principalmente de las estadísticas de dos jugadores:

  • Salud : representa el estado de salud del jugador. Tiene un valor máximo que no se puede exceder y se regenera lentamente con el tiempo. Los puntos de salud se pierden cuando el jugador es golpeado por un ataque enemigo.
  • Mana : es el poder mágico del jugador. Tiene un valor máximo que no se puede sobrepasar. Se consume al lanzar una bola de fuego y se regenera con el tiempo.

Para comenzar, agreguemos estas estadísticas al script del jugador:

# Player stats
var health = 100
var health_max = 100
var health_regeneration = 1
var mana = 100
var mana_max = 100
var mana_regeneration = 2

Las variables salud y maná son los valores actuales, mientras que salud_máximo y maná_máximo son los valores máximos que no se pueden superar. Las variables health_regeneration y mana_regeneration son las tasas de regeneración de las dos estadísticas, expresadas en puntos recuperados por segundo.

Barras de salud y maná

La GUI de un juego se utiliza para mostrar información al jugador de forma rápida y sencilla . Por ejemplo, puede mostrarle al jugador cuál es su salud, el nivel del personaje y otras estadísticas utilizando barras y otros elementos gráficos. La forma generalmente utilizada para mostrar la salud y el maná en una GUI de RPG es usar barras cuya longitud puede variar cuando se pierden o recuperan puntos.

Crearemos estas barras usando nodos ColorRect . Como su nombre lo indica, este nodo tiene una sola función: mostrar un rectángulo de color en la pantalla.

Agregue un nodo ColorRect como elemento secundario de Root y cámbiele el nombre Health .

En el Inspector , haga clic en Color y establezca los valores de Rojo , Verde , Azul y Alfa en 0, 0, 0 y 160 respectivamente.

Luego, abre la sección Rect y establece Position en (3, 158) y Size en (74, 9).

Ahora, agregue un nodo secundario a Health , también del tipo ColorRect , y cámbiele el nombre Bar . En el Inspector , establezca su color en (255, 50, 50, 255) y, en la sección Rect , establezca Posición en (1, 1) y Tamaño en (72, 7).

En el espacio de trabajo, ahora verá una barra roja rodeada por un borde negro semitransparente.

Ejecuta el juego y muévete a la derecha. ¡Verás que la barra quedará anclada al suelo y saldrás de la pantalla!

Para resolver este problema, debe crear un segundo lienzo para dibujar la GUI. Agregue un nodo CanvasLayer a Root .

Ahora arrastre el nodo Salud a CanvasLayer .

Vuelve a ejecutar el juego: esta vez la barra permanecerá anclada en la esquina inferior izquierda de la ventana del juego.

Para crear la barra de maná, haz clic con el botón derecho en el nodo Salud y elige Duplicar .

Se creará un duplicado del nodo Salud , incluidos todos sus elementos secundarios. Cambie el nombre del nodo duplicado a Mana .

Seleccione el nodo Mana y, en el Inspector , cambie el valor de Rect → Position → y a 169. Luego, seleccione su nodo secundario Bar y cambie su color a (33, 128, 255, 255). Ahora tendrás una segunda barra azul para ver el nivel de maná del jugador.

Actualización de los valores de salud y maná

Ahora que hemos creado las barras de salud y maná, necesitamos actualizar su longitud cada vez que se cambien las variables de salud y maná del jugador .

Haremos esto usando una señal personalizada, emitida desde el script del nodo Player cuando cambiamos una de las estadísticas del jugador. Adjuntaremos a cada nodo un script, que manejará la señal y se encargará de cambiar la longitud de las barras.

Abra el script Player y declare la señal personalizada player_stats_changed() usando la palabra clave signal :

signal player_stats_changed

La primera vez que queremos emitir esta señal es cuando se inicia el juego. De esta manera, aunque hayamos cambiado la longitud de las barras en el editor para realizar algunas pruebas, al comienzo del juego se establecerán en el estado inicial correcto.

Emitiremos la señal dentro de una de las funciones que usaremos más a menudo en Godot: la función _ready() . Como su nombre indica, la función _ready() se llama cuando el nodo está «listo», es decir, cuando tanto el nodo como sus hijos han entrado en el árbol de escena. Esta función generalmente se usa para la inicialización del nodo, por lo que es el lugar correcto para emitir player_stats_changed() para establecer las barras en su longitud inicial.

Para emitir una señal, usaremos la función emit_signal() . Agregue este código al script del jugador :

func _ready():
	emit_signal("player_stats_changed", self)

Se llama a la función emit_signal() pasándole como primer argumento el nombre de la señal que queremos emitir. Además del nombre de la señal, acepta un número variable de parámetros que posteriormente se pasan como parámetros a la función que maneja la señal.

En nuestro caso, establecemos solo un parámetro: una referencia al nodo Player , obtenido usando la palabra clave self . Los scripts de barra usarán esta referencia para leer las variables de salud y maná del jugador .

Como dijimos antes, la salud y el maná se regeneran con el tiempo. Luego, debemos agregar el código que incrementa estos valores muy marco. Cada vez que se incrementan, se debe emitir la señal player_stats_changed() para actualizar las barras.

Para ejecutar código de forma continua durante el juego, utilizaremos otra función fundamental de Godot: la función _process() . Esta función se llama en cada cuadro, lo más rápido posible. La función tiene un parámetro delta , que representa el tiempo transcurrido desde el cuadro anterior.

Para la regeneración de salud y maná, agregue este código al script:

func _process(delta):
	# Regenerates mana
	var new_mana = min(mana + mana_regeneration * delta, mana_max)
	if new_mana != mana:
		mana = new_mana
		emit_signal("player_stats_changed", self)

	# Regenerates health
	var new_health = min(health + health_regeneration * delta, health_max)
	if new_health != health:
		health = new_health
		emit_signal("player_stats_changed", self)

Al comienzo de la función, calculamos el valor de maná actualizado, agregando al valor actual la variación de maná para el cuadro actual ( mana_regeneration * delta ). La función min() garantiza que nunca se exceda el valor máximo de salud. Si el nuevo valor es diferente del anterior, el valor se actualiza y se emite la señal para actualizar la GUI. Para la variable de salud , funciona de la misma manera.

La última circunstancia en la que queremos emitir la señal player_stats_changed() es cuando lanzamos una bola de fuego. Lanzar una bola de fuego cuesta una cierta cantidad de maná, que restaremos cuando se detecte el evento de entrada de la bola de fuego.

En el método _input() , cambie el código de manejo de eventos de bola de fuego de esta manera:

elif event.is_action_pressed("fireball"):
	if mana >= 25:
		mana = mana - 25
		emit_signal("player_stats_changed", self)
		attack_playing = true
		var animation = get_animation_direction(last_direction) + "_fireball"
		$Sprite.play(animation)

El código verifica si hay suficiente maná para lanzar una bola de fuego (el costo es de 25 puntos). Si es así, se restan los puntos y se emite la señal para actualizar la GUI.

Conexión de la señal a los scripts

En este punto, solo necesitamos crear los scripts para las dos barras y conectarlos a la señal emitida por Player .

Para mantener el proyecto organizado, en el panel FileSystem , cree una carpeta llamada GUI , en la que colocaremos todos los archivos relacionados con la interfaz de usuario. Adjunte un nuevo script al nodo Health y llámelo HealthBar.gd . Guárdelo en la carpeta GUI que acaba de crear.

Si lo desea, puede eliminar todas las líneas del script excepto la primera ( extiende ColorRect ).

Ahora debemos conectar la señal player_stats_changed() a este script. Seleccione el nodo Reproductor y vaya al panel Nodo → Señales . Seleccione la señal player_stats_changed() y presione Conectar .

Cuando aparezca la ventana Connect Signal , seleccione el nodo Health y presione Connect nuevamente.

Godot abrirá el script del nodo Salud y verás la nueva función que maneja la señal. Reemplace su código con esto:

func _on_Player_player_stats_changed(var player):
	$Bar.rect_size.x = 72 * player.health / player.health_max

Como puede ver, agregamos un argumento a la función. Este argumento contendrá la referencia a Player que le hemos pasado como parámetro al emitir la señal. La función no hace más que establecer el ancho del nodo de la barra calculándolo como el ancho máximo (72 píxeles) multiplicado por el porcentaje de salud del jugador ( player.health / player.health_max ).

Repita el mismo procedimiento para el nodo Mana , creando el script ManaBar.gd y conectándolo a la señal. El código que maneja la señal también es muy similar:

func _on_Player_player_stats_changed(var player):
	$Bar.rect_size.x = 72 * player.mana / player.mana_max

Ejecuta el juego y presiona el botón de bola de fuego. Verás que el valor del maná cae instantáneamente y luego vuelve a subir lentamente.

Si desea probar la barra de salud, puede cambiar temporalmente la variable de salud en el script del jugador a un valor inferior a health_max . Verás que la barra crece a medida que se regenera la salud (la barra de salud se mueve más lentamente que la barra de maná porque la salud se regenera 1 punto por segundo en lugar de 2).

Puntos de experiencia y nivel del jugador

El segundo bloque que debemos agregar a la GUI nos mostrará los puntos de experiencia (XP) ganados por el jugador y su nivel actual. En este tutorial solo dibujaremos la interfaz, sin entrar en los detalles de su funcionamiento. Veremos la progresión de XP y niveles en un tutorial futuro.

Lo que queremos crear es un cuadro simple, donde se muestren los valores de XP y nivel. Para mostrar el texto, necesitamos una fuente que refleje el estilo pixel art del juego, así que creé una que puedes descargar haciendo clic en el botón de abajo.

Descargar «Fuente Schrödinger»Schrödinger.zip – 6 KB

En el panel Sistema de archivos , cree una carpeta llamada Fuentes y arrastre la fuente que acaba de descargar. No podemos usar directamente la fuente en un nodo GUI, sino que primero debemos crear un recurso de tipo DynamicFont . Para hacerlo, haga clic con el botón derecho en la carpeta Fuentes y elija Nuevo recurso .

Elija DynamicFont y presione Crear .

Finalmente, guarde el nuevo recurso como Font.tres .

Las propiedades del recurso recién creado aparecerán en el Inspector (cámbielo si aún tiene seleccionado el panel Nodo ). En la sección Fuente , arrastre el archivo Schrödinger.ttf a la propiedad Datos de fuente . En la sección Configuración , establezca Tamaño en 10 (en este tamaño, la fuente es perfecta en píxeles). Finalmente, guarde el recurso presionando el ícono del disco .

Cree un rectángulo agregando un nodo de tipo ColorRect como elemento secundario de CanvasLayer y llámelo XP . En el Inspector , establezca Color en (0, 0, 0, 160) y luego, en la sección Rect , establezca Posición en (124, 158) y Tamaño en (72, 20).

Ahora, agregue cuatro nodos Label como elementos secundarios de XP y configure sus fuentes personalizadas → Propiedades de fuente para usar el recurso Font.tres que creamos anteriormente. Luego, configure sus parámetros como se muestra en la tabla:

NombreTextoAlinearRecto → PosiciónRecto → Tamaño
EtiquetaXPEXP:Izquierda(2, 1)(20, 10)
nivel de etiquetaNVI:Izquierda(2, 10)(20, 10)
ValorXP0/100Correcto(21, 1)(50, 10)
Nivel de valor1Correcto(21, 10)(50, 10)

Este es el resultado final:

Inventario

El juego que estamos creando es muy simple y, por lo tanto, solo habrá dos tipos de objetos que el jugador puede poseer: pociones de salud y pociones de maná .

Así, nuestro inventario estará compuesto simplemente por dos cajas, que nos mostrarán cuántas pociones tenemos . Junto a la cantidad, colocaremos dos sprites para identificar el tipo de poción. Incluso para el inventario y las pociones, por ahora solo dibujaremos la interfaz de usuario. Habrá un tutorial específico explicando cómo funciona.

En primer lugar, descargue el sprite de pociones haciendo clic en el botón de abajo.

Descargar “SimpleRPG Pociones Sprite”pociones.png – 262 B

Importe la imagen que acaba de descargar al proyecto, colocándola en la carpeta GUI . Recuerde volver a importar la imagen deshabilitando la propiedad Filtro .

Para crear el cuadro para pociones de salud, agregue un nodo secundario ColorRect a CanvasLayer y llámelo HealthPotions . En el Inspector , establezca su propiedad Color en (0, 0, 0, 160), luego, en la sección Rect , establezca Posición en (243, 158) y Tamaño en (36, 20).

Ahora, agregue un nodo Sprite a HealthPotions . En el Inspector , arrastre el archivo potions.png a la propiedad Textura , luego, en la sección Región , active Habilitado y establezca la región Rect ‘s w y h en 16. Finalmente, en la sección Transformar , establezca Posición en (10, 10).

Para mostrar la cantidad de pociones que posee el jugador, agregue un nodo Etiqueta a HealthPotions . En el Inspector , en la sección Fuentes personalizadas , establezca Fuente en Font.tres . Luego, establezca Texto en 0, Alinear a la derecha y, en la sección Rect , establezca Posición en (19, 6) y Tamaño en (14, 10).

Para el cuadro de pociones de maná, haga clic derecho en HealthPotions y elija Duplicar . Cambie el nombre de la copia recién creada en ManaPotions . Con ManaPotions seleccionado, en el Inspector , cambie Rect → Position a (281, 158). Luego, seleccione su nodo Sprite secundario y en Region → Rect establezca x en 16 para cambiar la imagen de la poción.

Este es el resultado final:

Conclusiones

En este tutorial aprendimos cómo crear una interfaz gráfica de usuario simple y actualizarla usando el sistema de eventos de Godot.