Tutorial de Godot – Parte 13: Puntos de experiencia y avance de nivel

En este tutorial agregaremos una de las características más importantes de un juego de rol: el avance de nivel.

Un nivel es un número que representa la habilidad y el poder general de un personaje. Los personajes generalmente comienzan en el nivel 1 y aumentan su nivel ganando puntos de experiencia.

Los puntos de experiencia, también conocidos como XP, se utilizan en los juegos de rol para cuantificar la progresión del jugador a lo largo del juego. Los puntos de experiencia generalmente se otorgan por derrotar enemigos o completar misiones.

Cuando se obtiene una cantidad suficiente de experiencia, los personajes «suben de nivel» y sus estadísticas (como la salud máxima, la magia y la fuerza) aumentan. Subir de nivel también puede dar a los personajes nuevas y poderosas habilidades. Para nuestro juego, en cada nivel superior dejaremos que el jugador elija si desea aumentar la salud o el maná en 50 puntos.

Por lo general, en los juegos de rol, cada nivel posterior requiere más XP para alcanzar, por lo que subir de nivel se vuelve cada vez más difícil. En nuestro caso, en cada nivel que subamos duplicaremos el número de XP necesarios para pasar al siguiente nivel .

Ahora que tenemos una idea de lo que tenemos que hacer, ¡comencemos!

GUI de nivel y XP

En el tutorial de GUI , agregamos un cuadro en la GUI que nos muestra cuántos puntos de experiencia tenemos y nuestro nivel. Necesitamos vincular este cuadro a las estadísticas del jugador, así que comience a agregar estas variables al script del jugador:

var xp = 0;
var xp_next_level = 100;
var level = 1;

Estas variables almacenan cuántos xp tenemos, cuántos necesitamos para alcanzar el siguiente nivel y cuál es nuestro nivel actual.

Para actualizar la GUI, adjunte un nuevo script al nodo XP . Llámelo XP.gd y guárdelo en la carpeta GUI . Luego, seleccione el nodo Player y en el panel Node haga doble clic en la señal player_stats_changed() para conectarlo a XP .

La función _on_Player_player_stats_changed() se agregará automáticamente al script XP.gd. Cámbialo de esta manera:

func _on_Player_player_stats_changed(player):
	$ValueXP.text = str(player.xp) + "/" + str(player.xp_next_level)
	$ValueLevel.text = str(player.level)

Esta función simplemente toma los valores del reproductor y los inserta en las etiquetas GUI.

Ganar XP cuando muere un esqueleto

Como vimos en la introducción, derrotar a un monstruo es la forma principal de obtener puntos de experiencia. Entonces, cuando un esqueleto muere, tendremos que actualizar la cantidad de XP del jugador.

Primero, en el script Player.gd agregue la función add_xp() :

func add_xp(value):
	xp += value
	emit_signal("player_stats_changed", self)

La función recibe como argumento el número de XP a otorgar al jugador. Este valor se agrega al XP actual y luego la función emite la señal para actualizar la GUI.

Más adelante volveremos a esta función para agregar el avance de nivel.

Ahora pasemos al script Skeleton.gd . En la función hit() , cuando manejamos la muerte del esqueleto, debemos agregar el código que asigna la XP al jugador:

func hit(damage):
	health -= damage
	if health > 0:
		$AnimationPlayer.play("Hit")
	else:
		... death code ...
		
		# Add XP to player
		player.add_xp(25)

Intenta ejecutar el juego ahora: cada esqueleto asesinado aumentará la XP del jugador en 25 puntos.

Ventana emergente de avance de nivel

Cuando alcancemos la cantidad de XP para subir de nivel, queremos detener el juego y mostrar una ventana emergente que le permita al jugador elegir si desea aumentar la salud o la puntuación de maná.

Por lo tanto, agregue un nodo Popup a CanvasLayer y cámbiele el nombre LevelPopup . El nodo emergente nos proporciona una manera fácil de mostrar ventanas y diálogos emergentes.

En el panel Escena , haz que LevelPopup sea visible haciendo clic en el ícono junto a él.

Junto a LevelPopup , aparecerá una advertencia que le indicará que el panel solo estará visible en el Editor, pero no en el juego. Para mostrarlo durante el juego tendremos que llamar a una de las funciones popup() de la clase Popup .

Con LevelPopup seleccionado, en el Inspector habilite Exclusive (puede encontrarlo en la sección Popup ). Cuando Exclusivo está habilitado, la ventana emergente no se ocultará cuando se produzca un evento de clic fuera de ella. Luego, en Nodo → Pausa , establezca Modo en Proceso . Esto nos permitirá procesar las entradas de este panel incluso cuando el juego está en pausa.

Todavía en el Inspector , en la sección Rect , establezca el tamaño del panel en (100,50). Luego, agregue un nodo ColorRect a LevelPopup y también establezca su tamaño en (100,50).

Agregue un nodo Label a ColorRect . En el Inspector , establezca Texto en ¡Nuevo nivel! Alinear al centro . Luego, en Fuente personalizada , cargue la fuente Font.tres que creamos en el tutorial de GUI y configure el Color de fuente en (90,90,90,255). Finalmente, establezca Rect → Posición en (0,1) y Rect → Tamaño en (100,10).

Para mostrarle al jugador las actualizaciones de estadísticas disponibles, usaremos dos imágenes, que puede descargar presionando el botón a continuación:

Descargar «Sprites de actualización de estadísticas de SimpleRPG»stats_upgrade_sprites.zip – 1 KB

Una vez descargados, impórtelos a la carpeta GUI (recuerde volver a importarlos deshabilitando Filtro en el panel Importar ).

Agregue un nodo Sprite a ColorRect y arrastre health_bonus.png a la propiedad Texture en el Inspector . luego, establezca Nodo2D → Transformar → Posición en (31,30). Agregue otro Sprite para mana_bonus.png y establezca su posición en (69,30).

Para mostrarle al jugador qué teclas presionar para elegir la actualización, agregue un nodo Etiqueta a ColorRect . En el Inspector , establezca Texto en A. Luego, en Fuente personalizada , cargue la fuente Font.tres y configure el Color de fuente en (90,90,90,255). Finalmente, establezca Rect → Posición en (7,25) y Rect → Tamaño en (6,10). Duplique esta etiqueta, cambie Texto a B y configure Rect → Posición a (88,25).

El resultado debería ser este:

Alguien que ya tiene experiencia con Godot puede preguntar: ¿por qué no usaste un nodo PopupPanel para crear la ventana emergente? Habría sido la solución más sencilla, porque PopupPanel ya integra un fondo configurable. Desafortunadamente, por ahora hay un error que lo hace inutilizable.

Subir de nivel

Ahora que tenemos la ventana emergente de selección de actualización, podemos manejar el avance de nivel del jugador.

Cuando el jugador suba de nivel, emitiremos una señal. Cuando la ventana emergente reciba esta señal, aparecerá y el juego se detendrá para darle tiempo al jugador para elegir la actualización.

Entonces, para empezar, declare esta nueva señal en Player.gd :

signal player_level_up

Luego, vaya a la función add_xp() y cámbiela así:

func add_xp(value):
	xp += value
	# Has the player reached the next level?
	if xp >= xp_next_level:
		level += 1
		xp_next_level *= 2
		emit_signal("player_level_up")
	emit_signal("player_stats_changed", self)

El código que agregamos verifica si el XP del jugador es igual o mayor que el XP necesario para alcanzar el siguiente nivel. Si es verdadero, el nivel aumenta en 1 y el siguiente objetivo de XP se calcula duplicando el objetivo de nivel actual. Finalmente, se emite la señal player_level_up() .

Para manejar esta señal, adjunte un nuevo script al nodo LevelPopu p. Llámelo LevelPopup.gd y guárdelo en la carpeta GUI . En este nuevo script, agregue una variable para almacenar una referencia al nodo Player :

var player

Luego, agregue la función _ready() , donde guardaremos la referencia a Player y deshabilitaremos la entrada con la función set_process_input() (no queremos manejar la entrada cuando la ventana emergente no está visible).

func _ready():
	player = get_node("/root/Root/Player")
	set_process_input(false)

Ahora conecte la señal player_level_up() de Player a LevelPopup y agregue este código para mostrar la ventana emergente:

func _on_Player_player_level_up():
	set_process_input(true)
	popup_centered()
	get_tree().paused = true

Lo primero que hace esta función es habilitar la entrada, para permitir que el jugador elija la actualización. Luego, llama a la función popup_centered() . Como sugiere el nombre, esta función muestra la ventana emergente en el centro de la pantalla. Finalmente, el juego se detiene configurando la propiedad de pausa del SceneTree actual en verdadero.

Lo último que debe hacer es manejar la entrada del jugador. Si se presiona la tecla A en el teclado, la salud aumenta; si en cambio se presiona la tecla B, el maná aumenta.

func _input(event):
	if event is InputEventKey:
		if event.scancode == KEY_A:
			player.health_max += 50
			player.health += 50
			player.emit_signal("player_stats_changed", player)
			hide()
			set_process_input(false)
			get_tree().paused = false
		elif event.scancode == KEY_B:
			player.mana_max += 50
			player.mana += 50
			player.emit_signal("player_stats_changed", player)
			hide()
			set_process_input(false)
			get_tree().paused = false

En primer lugar, la función comprueba si el evento es de tipo InputEventKey , es decir, si es un evento generado por el teclado. Si es así, verifica la propiedad scancode del evento para saber qué tecla se presionó. Si es una de las claves válidas, la estadística relativa se incrementa. Después de eso, la ventana emergente se oculta llamando a la función hide() . A continuación, la entrada se desactiva de nuevo y finalmente se elimina la pausa.

Una mejor alternativa que verificar directamente qué tecla se presionó sería definir acciones de entrada (como vimos en el movimiento del jugador y en los siguientes tutoriales), pero quería mostrarles otra forma de usar eventos de entrada.

Conclusiones

En este tutorial aprendimos:

  • cómo funcionan las ventanas emergentes
  • cómo pausar el juego, manteniendo solo unos pocos nodos en ejecución
  • otra forma de usar eventos de entrada