Work in progress
The content of this page was not yet updated for Godot
4.2
and may be outdated. If you know how to improve this page or you can confirm
that it's up to date, feel free to open a pull request.
Puntaje y repetición¶
En esta parte, agregaremos la puntuación, la reproducción de música y la capacidad de reiniciar el juego.
Tenemos que llevar un registro de la puntuación actual en una variable y mostrarla en pantalla utilizando una interfaz mínima. Utilizaremos una etiqueta de texto para hacerlo.
En la escena principal, agrega un nuevo nodo Control como hijo de Main
y nómbralo UserInterface
. Serás llevado automáticamente a la pantalla 2D, donde podrás editar tu Interfaz de Usuario (UI).
Agrega un nodo Label y nómbralo ScoreLabel
En el Inspector, establece el Texto del Label como un marcador de posición como "Puntuación: 0".
Además, el texto es blanco por defecto, al igual que el fondo de nuestro juego. Necesitamos cambiar su color para poder verlo durante la ejecución.
Desplázate hacia abajo hasta Theme Overrides, expande Colors y activa Font Color para cambiar el tinte del texto a negro (el cual contrasta bien con la escena 3D blanca)
Finalmente, haz clic y arrastra el texto en el viewport para moverlo lejos de la esquina superior izquierda.
El nodo UserInterface
nos permite agrupar nuestra UI en una rama del árbol de escenas y utilizar un recurso de tema que se propagará a todos sus hijos. Lo utilizaremos para establecer la fuente de nuestro juego.
Creando un tema de interfaz de usuario¶
Una vez más, selecciona el nodo UserInterface
. En el Inspector, crea un nuevo recurso de tema en Theme -> Theme.
Haz clic en él para abrir el editor de temas en el panel inferior. Te brinda una vista previa de cómo se verán todos los widgets de la UI incorporados con tu recurso de tema.
Por defecto, un tema solo tiene una propiedad, la Default Font.
Ver también
Puedes agregar más propiedades al recurso de tema para diseñar interfaces de usuario más complejas, pero eso está fuera del alcance de esta serie. Para obtener más información sobre cómo crear y editar temas, consulta Introducción al skinning de la interfaz gráfica de usuario (GUI).
Este campo espera un archivo de fuente como los que tienes en tu computadora. Los archivos de fuente comunes son TrueType Font (TTF) y OpenType Font (OTF).
En el panel Sistema de Archivos, expande el directorio fonts
y arrastra el archivo Montserrat-Medium.ttf
que incluimos en el proyecto hacia Default Font. El texto volverá a aparecer en la vista previa del tema.
El texto es un poco pequeño. Cambia el Default Font Size a 22
pixeles para incrementar el tamaño del texto.
Manteniendo el rastro del puntaje¶
A continuación, trabajemos en la puntuación. Adjunta un nuevo script a ScoreLabel
y define la variable score
.
extends Label
var score = 0
using Godot;
public partial class ScoreLabel : Label
{
private int _score = 0;
}
La puntuación debería aumentar en 1
cada vez que aplastemos a un monstruo. Podemos utilizar su señal squashed
para saber cuándo ocurre eso. Sin embargo, como instanciamos los monstruos desde el código, no podemos hacer la conexión a ScoreLabel
en el editor.
En cambio, tenemos que hacer la conexión desde el código cada vez que generamos un monstruo.
Abre el script main.gd
. Si aún está abierto, puedes hacer clic en su nombre en la columna izquierda del editor de scripts.
Alternativamente, puedes hacer doble clic en el archivo main.gd
en el panel Sistema de Archivos.
En la parte inferior de la función _on_mob_timer_timeout()
, agrega la siguiente línea:
func _on_mob_timer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
private void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
}
Esta línea significa que cuando el enemigo emite la señal squashed
, el nodo ScoreLabel
la recibirá y llamará a la función _on_mob_squashed()
.
Regresa al script ScoreLabel.gd
para definir la función de callback _on_mob_squashed()
.
Allí, incrementamos la puntuación y actualizamos el texto mostrado.
func _on_mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = $"Score: {_score}";
}
La segunda línea utiliza el valor de la variable score
para reemplazar el símbolo %s
. Al utilizar esta función, Godot convierte automáticamente los valores a texto, lo cual es conveniente para mostrar texto en etiquetas o utilizando la función print()
.
Ver también
Puedes aprender más sobre formateo de cadenas aquí Cadenas de formato en GDScript. En C#, considera usar interpolación de cadenas con "$".
Ahora puedes jugar el juego y aplastar algunos enemigos para ver cómo aumenta la puntuación.
Nota
En un juego complejo, es posible que desees separar por completo la interfaz de usuario del mundo del juego. En ese caso, no llevarías un seguimiento de la puntuación en la etiqueta. En su lugar, podrías almacenarla en un objeto separado y dedicado. Sin embargo, al prototipar o cuando tu proyecto es simple, está bien mantener tu código simple. La programación siempre es un acto de equilibrio.
Reintentando el juego¶
Ahora agregaremos la capacidad de jugar nuevamente después de morir. Cuando el jugador muera, mostraremos un mensaje en la pantalla y esperaremos una entrada.
Regresa a la escena main.tscn
, selecciona el nodo UserInterface
, agrega un nodo ColorRect como hijo de este y nómbralo Retry
. Este nodo llenará un rectángulo con un color uniforme y servirá como una superposición para oscurecer la pantalla.
Para hacer que abarque toda la ventana de visualización, puedes utilizar el menú Anchor Preset en la barra de herramientas.
Ábrelo y aplica el comando Full Rect.
No sucede nada. Bueno, casi nada; solo los cuatro pines verdes se mueven a las esquinas del cuadro de selección.
Esto se debe a que los nodos de UI (todos los que tienen un ícono verde) funcionan con anclajes y márgenes relativos al cuadro delimitador de su padre. Aquí, el nodo UserInterface
tiene un tamaño pequeño y el nodo Retry
está limitado por él.
Selecciona el nodo UserInterface
y también aplica Anchor Presets -> Completo en él. Ahora, el nodo Retry
debería abarcar toda la ventana gráfica.
Cambiemos su color para que oscurezca el área del juego. Selecciona Retry
y en el Inspector, establece su Color en algo oscuro y transparente. Para hacerlo, en el selector de color, arrastra el deslizador A hacia la izquierda. Este controla el canal alfa del color, es decir, su opacidad/transparencia.
Ahora, agrega un Label como hijo de Retry
y en el Text escribe "Press Enter to retry." Para moverlo y anclarlo en el centro de la pantalla, aplica Anchor Preset -> Center a este.
Codificando la opcion retry¶
Ahora podemos dirigirnos al código para mostrar y ocultar el nodo Retry
cuando el jugador muere y vuelve a jugar.
Abre el script main.gd
. Primero, queremos ocultar la superposición al inicio del juego. Agrega esta línea a la función _ready()
.
func _ready():
$UserInterface/Retry.hide()
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
Luego, cuando el jugador recibe un golpe, mostramos la superposición.
func _on_player_hit():
#...
$UserInterface/Retry.show()
private void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Finalmente, cuando el nodo Retry
es visible, necesitamos escuchar la entrada del jugador y reiniciar el juego si presionan Enter. Para hacer esto, utilizamos el callback incorporado _unhandled_input()
que se dispara con cualquier entrada.
Si el jugador presiona la acción de entrada predefinida "ui_accept" y Retry
es visible, recargamos la escena actual.
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
La función get_tree()
nos da acceso al objeto global SceneTree, lo que nos permite recargar y reiniciar la escena actual.
Añadiendo musica¶
Para agregar música que se reproduzca continuamente en segundo plano, vamos a utilizar otra característica de Godot: los autoloads.
Para reproducir audio, todo lo que necesitas hacer es agregar un nodo AudioStreamPlayer a tu escena y adjuntarle un archivo de audio. Cuando inicias la escena, puede reproducirse automáticamente. Sin embargo, cuando vuelves a cargar la escena, como lo hacemos para jugar nuevamente, los nodos de audio también se reinician y la música vuelve a comenzar desde el principio.
Puedes utilizar la función de autoload para que Godot cargue automáticamente un nodo o una escena al inicio del juego, fuera de la escena actual. También puedes utilizarla para crear objetos de acceso global.
Crea una nueva escena yendo al menú Escena y haciendo clic en Nueva Escena o usando el icono + que está junto a la escena abierta.
Haz clic en el botón Otro Nodo para crear un AudioStreamPlayer y renómbralo como MusicPlayer
.
Incluimos una banda sonora en el directorio art/
, llamada House In a Forest Loop.ogg
. Haz clic y arrástrala hasta la propiedad Stream en el Inspector. Además, activa Autoplay para que la música se reproduzca automáticamente al inicio del juego.
Guarda la escena como MusicPlayer.tscn
.
Tenemos que registrarlo como una carga automática. Ve al menú Proyecto -> Configuración del proyecto... y haz clic en la pestaña Carga automática.
En el campo Path, debes ingresar la ruta de tu escena. Haz clic en el ícono de la carpeta para abrir el explorador de archivos y haz doble clic en MusicPlayer.tscn
. Luego, haz clic en el botón Add a la derecha para registrar el nodo.
MusicPlayer.tscn
ahora carga con cualquier escena que abras o ejecutes. Así que si ejecutas el juego ahora, la música se reproducirá automáticamente en cualquier escena.
Antes de finalizar esta lección, aquí tienes un vistazo rápido de cómo funciona internamente. Cuando ejecutas el juego, tu panel Escena cambia para mostrarte dos pestañas: Remote y Local.
La pestaña Remote te permite visualizar el árbol de nodos de tu juego en ejecución. Allí verás el nodo Main y todo lo que contiene la escena, así como los enemigos instanciados en la parte inferior.
En la parte superior se encuentran el nodo autocargado MusicPlayer
y un nodo root, que es el viewport del juego.
Y eso es todo para esta lección. En la próxima parte, agregaremos una animación para que el juego se vea y se sienta mucho mejor.
Aquí está el script completo main.gd
como referencia.
extends Node
@export var mob_scene: PackedScene
func _ready():
$UserInterface/Retry.hide()
func _on_mob_timer_timeout():
# Create a new instance of the Mob scene.
var mob = mob_scene.instantiate()
# Choose a random location on the SpawnPath.
# We store the reference to the SpawnLocation node.
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
# And give it a random offset.
mob_spawn_location.progress_ratio = randf()
var player_position = $Player.position
mob.initialize(mob_spawn_location.position, player_position)
# Spawn the mob by adding it to the Main scene.
add_child(mob)
# We connect the mob to the score label to update the score upon squashing one.
mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
func _on_player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
# This restarts the current scene.
get_tree().reload_current_scene()
using Godot;
public partial class Main : Node
{
[Export]
public PackedScene MobScene { get; set; }
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
// This restarts the current scene.
GetTree().ReloadCurrentScene();
}
}
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on the SpawnPath.
// We store the reference to the SpawnLocation node.
var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
// And give it a random offset.
mobSpawnLocation.ProgressRatio = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").position;
mob.Initialize(mobSpawnLocation.Position, playerPosition);
// Spawn the mob by adding it to the Main scene.
AddChild(mob);
// We connect the mob to the score label to update the score upon squashing one.
mob.Squashed += GetNode<ScoreLabel>("UserInterface/ScoreLabel").OnMobSquashed;
}
private void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}