Tutorial de Godot – Parte 12: Selección y uso de elementos

En este tutorial, aprenderemos cómo crear objetos que el jugador puede recoger y usar. Para mantener las cosas simples, en nuestro pequeño juego solo habrá dos tipos de objetos, es decir, dos pociones que permitirán al jugador recuperar salud y maná.

escena de pociones

Para comenzar, descargue la imagen que usaremos para las pociones haciendo clic en el botón a continuación.

Descargar “SimpleRPG Sprite de pociones pequeñas”pociones_pequeñas.png – 202 B

Para mantener el proyecto organizado, cree una nueva carpeta de pociones en Entidades . Dentro de esta carpeta, importa la imagen que acabas de descargar (recuerda volver a importar la imagen con el filtro desactivado).

Ahora, crea una nueva escena. Usaremos esta escena para ambos tipos de pociones. Use un nodo Area2D como nodo raíz y cámbiele el nombre Potion .

Agregue un nodo Sprite a Potion . En el Inspector , arrastra small_potions.png a la propiedad Texture . Dado que la imagen contiene ambas pociones, debemos habilitar la sección Región y establecer las propiedades w y h en 8. De esta manera, solo se usa una poción.

Luego, agregue un nodo CollisionShape2D a Potion . En el Inspector , establezca Forma en el nuevo RectangleShape2D y cambie el tamaño del rectángulo para que tenga el mismo tamaño que la poción.

Guión de pociones

Adjunte un nuevo script a Potion y guárdelo como Potion.gd en la carpeta Entities/Potion .

En el script, antes de extender Area2D , agregue esta línea:

tool

Cuando agrega en la parte superior de un script la palabra clave de la herramienta , se ejecutará no solo durante el juego, sino también en el editor. Ejecutar un script en el editor puede ser útil para hacer muchas cosas, pero se usa principalmente en el diseño de niveles para mostrar cosas que, de otro modo, solo serían visibles durante el juego.

En nuestro caso, nos permitirá ver en el editor qué tipo de poción instanciamos. Sin usar la herramienta de palabras clave, ambos tipos de pociones se mostrarían en el editor con el mismo sprite.

Veremos más adelante que también puedes decidir qué partes del script se ejecutan en el editor, cuáles en el juego y cuáles en ambos.

Primero, necesitamos una variable para almacenar el tipo de poción:

enum Potion { HEALTH, MANA }
export(Potion) var type = Potion.HEALTH

La primera línea declara una enumeración . Las enumeraciones son una forma de declarar un grupo de constantes relacionadas. Hemos declarado una enumeración llamada Poción , que contiene dos constantes SALUD y MANÁ , a las que se asignan automáticamente los valores 0 y 1.

En la segunda línea declaramos el tipo de variable , que tiene la constante Potion.HEALTH como valor predeterminado. Como ya hemos visto en los tutoriales anteriores, la exportación de palabras clave hace que una variable sea visible en el Inspector . Si después de exportar , entre paréntesis, ponemos el nombre de una enumeración, esta variable se mostrará en el Inspector como una lista desplegable.

Guarde la escena como Potion.tscn en Entities/Potion .

Si crea una instancia de una poción arrastrando la escena Potion.tscn a la escena principal y luego intenta cambiar el tipo de poción cambiando la variable de tipo, verá que el sprite de la poción no cambia.

Para cambiar el sprite en el editor según la variable de tipo , podemos verificar su valor dentro de la función _process() . Dado que usamos la herramienta de palabras clave al comienzo del script, la función _process() se ejecutará en el editor, por lo que el sprite cambiará tan pronto como cambiemos el valor de Type en el Inspector .

Entonces, agreguemos la función _process() al script:

func _process(delta):
	if Engine.editor_hint:
		if type == Potion.MANA:
			$Sprite.region_rect.position.x = 8
		else:
			$Sprite.region_rect.position.x = 0

La primera línea de la función comprueba si estamos en el editor, utilizando la propiedad editor_hint de la clase Engine . Queremos ejecutar este código solo en el editor, porque durante el juego el tipo de poción no cambia. Por lo tanto, sería un desperdicio de poder de procesamiento repetir la verificación de tipo en cada cuadro. Si estamos en el editor, verificamos el tipo de poción y, en base a eso, elegimos la región de la imagen para usar como sprite de la poción.

Ahora, coloca una poción en la escena principal y muévela cerca del jugador. Luego, coloca una segunda poción y en Inspector cambia el tipo a Mana . Verás el cambio de sprite.

Al ejecutar el juego, debemos configurar el sprite de la poción al principio. Entonces, debemos poner la verificación de tipo en la función _ready() :

func _ready():
	if type == Potion.MANA:
		$Sprite.region_rect.position.x = 8
	else:
		$Sprite.region_rect.position.x = 0

Si el jugador camina sobre una poción, queremos que la recoja. Por tanto, debemos detectar si el jugador ha entrado en la zona de la poción. Para hacerlo, conectamos la señal body_entered() de Potion consigo misma. La función _on_Potion_body_entered() se agregará automáticamente al script. Agregue este código a la función:

func _on_Potion_body_entered(body):
	if body.name == "Player":
		body.add_potion(type)
		get_tree().queue_delete(self)

Primero, la función verifica si el cuerpo que ingresó al área es Player. Si es así, hace dos cosas:

  • llama a la función add_potion () del jugador, que actualizará el inventario del jugador y emitirá la señal para actualizar la GUI (escribiremos esta función en breve)
  • elimina la poción del árbol de escenas.

Como no hay nada más que hacer en la escena de Pociones , guárdalo y vuelve a la escena principal.

Guión del jugador

Abra el script del jugador ( player.gd ) y al principio agregue este código para almacenar el inventario del jugador:

# Player inventory
enum Potion { HEALTH, MANA }
var health_potions = 0
var mana_potions = 0

Simplemente declaramos dos variables para almacenar cuántas pociones tenemos. También copiamos aquí la enumeración utilizada en Potions.gd , por lo que podemos usar las mismas constantes.

Ahora, debemos agregar la función add_potion() al script. Llamamos a esta función en el script de Poción cuando el jugador toma la poción.

func add_potion(type):
	if type == Potion.HEALTH:
		health_potions = health_potions + 1
	else:
		mana_potions = mana_potions + 1
	emit_signal("player_stats_changed", self)

Esta función simplemente actualiza el inventario según el tipo de poción recogida y luego emite la señal para actualizar la GUI.

Actualización del conteo de pociones en GUI

En el tutorial de la GUI , habíamos agregado los nodos de la interfaz para mostrar el inventario, pero no habíamos escrito el código para actualizar los valores, así que hagámoslo ahora.

Adjunte un nuevo script al nodo HealthPotions (se encuentra en CanvasLayer ), llámelo HealthPotions.gd y guárdelo en la carpeta GUI .

Selecciona Player y ve al panel Node para conectar la señal player_stats_changed() a HealthPotions . La función _on_Player_player_stats_changed() se agrega automáticamente al script HealthPotions.gd . Reemplace la función con este código:

func _on_Player_player_stats_changed(var player):
	$Label.text = str(player.health_potions)

La función simplemente asigna la cantidad de pociones actualmente presentes en el inventario a la propiedad de texto del nodo Etiqueta . Para asignar el valor, primero debe convertirse en una cadena, usando la función str() .

Repita el procedimiento para el nodo ManaPotions . La función en este caso será:

func _on_Player_player_stats_changed(var player):
	$Label.text = str(player.mana_potions)

Ejecute el juego e intente mover al jugador sobre las pociones para ver si se eliminan de la escena y si la GUI se actualiza correctamente.

Dejar caer una poción después de la muerte del enemigo.

En los juegos de rol, cuando un enemigo muere, generalmente los jugadores obtienen un botín destinado a recompensarlos por progresar en el juego. En nuestro juego, cuando el jugador derrota a un esqueleto, habrá un 80 % de posibilidades de obtener una poción al azar.

Para implementar el lanzamiento de botín, abra el script Skeleton.gd . En primer lugar, necesitamos una referencia a la escena Potion.tscn para poder instanciarla cuando muera el esqueleto:

# Reference to potion scene
var potion_scene = preload("res://Entities/Potion/Potion.tscn")

En la función _ready() , debemos agregar una llamada a la función randomize() para aleatorizar la semilla del generador de números aleatorios:

func _ready():
	player = get_tree().root.get_node("Root/Player")
	rng.randomize()

En la función hit() , cuando el esqueleto muere, debemos decidir aleatoriamente si instanciar una poción. Edite la función de la siguiente manera:

func hit(damage):
	health -= damage
	if health > 0:
		$AnimationPlayer.play("Hit")
	else:
		... death code ...
		
		# 80% probability to drop a potion on death
		if rng.randf() <= 0.8:
			var potion = potion_scene.instance()
			potion.type = rng.randi() % 2
			get_tree().root.get_node("Root").call_deferred("add_child", potion)
			potion.position = position

Inmediatamente después de ejecutar el código de muerte existente, se genera un número aleatorio entre 0 y 1 utilizando la función randf() . Si este número es menor o igual a 0,8 (80%), se instancia una nueva poción.

Luego, se genera un número aleatorio entero usando la función randi() . Con una operación de módulo (%) 2, obtienes el recordatorio de la división por 2 de este número. Como el resto solo puede ser 0 o 1, se elige aleatoriamente uno de los dos tipos de poción (0 para salud, 1 para maná).

Por último, la poción se agrega al árbol de escenas y se coloca donde murió el esqueleto.

Nota para lectores más avanzados : no podemos llamar directamente a la función add_child() para agregar la poción, porque no es seguro agregar un nuevo Area2D al árbol de nodos dentro de una función que maneja la señal on_area_entered() de otro nodo Area2D . Por lo tanto, debemos usar call_deferred() para diferir la llamada a add_child() durante el tiempo de inactividad.

bebiendo pociones

Lo último que queda por hacer es manejar la entrada para beber pociones.

Primero, haga clic en el menú Proyecto → Configuración del proyecto para abrir la ventana Configuración del proyecto y vaya a la pestaña Mapa de entrada .

Necesitamos crear dos acciones:

  • drink_health , al que mapeamos la tecla Coma (,) y el botón 0 del joypad (equivale al botón Cross de PlayStation y al botón A de XBox)
  • drink_mana , al que mapeamos la tecla Period (.) y el botón 1 del joypad (equivalente al botón PlayStation Circle y al botón XBox B)

Si no recuerda cómo crear acciones de entrada, eche un vistazo al tutorial de movimiento del jugador .

Cierra la ventana y ve a la función _input() en el script de Player . Agregue a la función el código para manejar estas dos nuevas acciones:

elif event.is_action_pressed("drink_health"):
	if health_potions > 0:
		health_potions = health_potions - 1
		health = min(health + 50, health_max)
		emit_signal("player_stats_changed", self)
elif event.is_action_pressed("drink_mana"):
	if mana_potions > 0:
		mana_potions = mana_potions - 1
		mana = min(mana + 50, mana_max)
		emit_signal("player_stats_changed", self)

Cuando se detecta la acción de entrada, la función verifica si el jugador tiene alguna poción para usar. Si es así, la cantidad de la poción se reduce en 1 y se agregan 50 puntos a la estadística correspondiente ( se usa la función min() para no exceder el valor máximo). Finalmente, emite la señal player_stats_changed() para actualizar la GUI.

Ejecuta el juego y, cuando hayas perdido puntos de salud o consumido tu maná, intenta beber una poción para recuperar algunos puntos.

Conclusiones

En este tutorial hemos visto cómo crear objetos que el jugador puede recolectar y usar. Además, aprendimos a usar la palabra clave tool para agregar interactividad al editor y ampliamos un poco nuestro conocimiento de GDScript.