Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Punteggio e rigioco
In questa parte aggiungeremo il punteggio, la riproduzione della musica e l'abilità di riavviare il gioco.
Dobbiamo tenere traccia del punteggio attuale in una variabile e visualizzarlo sullo schermo utilizzando un'interfaccia minimale. Useremo un'etichetta di testo per farlo.
Nella scena principale, aggiungi un nuovo nodo figlio Control a Main e rinominalo UserInterface. Assicurati di essere nella schermata 2D, dove puoi modificare l'interfaccia utente (UI).
Aggiungi un nodo Label e denominalo ScoreLabel

Nell'Ispettore, imposta il Text di Label su un segnaposto, ad esempio "Score: 0".

Inoltre, il testo è bianco come predefinito, ugale allo sfondo del nostro gioco. Dobbiamo cambiarne il colore per vederlo durante l'esecuzione.
Scorri verso il basso fino a Sostituzioni del tema, espandi Colori e abilita Colore del font per cambiare la tinta del testo in nero (il quale contrasta bene con il bianco della scena 3D)

Infine, clicca e trascina il testo nella viewport per spostarlo via dall'angolo in alto a sinistra.

Il nodo UserInterface ci permette di raggruppare la nostra interfaccia utente in un ramo dell'albero di scene e di utilizzare una risorsa tema che verrà propagata a tutti i suoi nodi figlio. La utilizzeremo per impostare il font del nostro gioco.
Creazione di un tema per l'UI
Ancora una volta, seleziona il nodo UserInterface. Nell'Ispettore, crea una nuova risorsa tema in Tema -> Tema.

Cliccaci sopra per aprire l'editor di temi nel pannello inferiore. Offre un'anteprima di come appariranno tutti i widget integrati dell'interfaccia utente con la tua risorsa tema.

Normalmente, un tema ha solo qualche proprietà: Scala di base predefinita, Font predefinito e Dimensione del font predefinita.
Vedi anche
È possibile aggiungere ulteriori proprietà alla risorsa tema per progettare interfacce utente complesse, ma questo va oltre lo scopo di questa serie. Per ulteriori informazioni sulla creazione e la modifica dei temi, consultare Introduzione a GUI skinning.
La proprietà Default Font si aspetta un file di font come quelli presenti sul tuo computer. Due formati di file di font comuni sono TrueType Font (TTF) e OpenType Font (OTF).
Nel pannello FileSystem, espandi la cartella fonts e trascina il file Montserrat-Medium.ttf incluso nel progetto su Default Font. Il testo riapparirà nell'anteprima del tema.
Il testo è un po' piccolo. Imposta la proprietà Default Font Size a 22 pixel per aumentare le dimensioni del testo.

Tenere traccia del punteggio
Lavoriamo ora sul punteggio. Alleghiamo un nuovo script a ScoreLabel e definiamo la variabile score.
extends Label
var score = 0
using Godot;
public partial class ScoreLabel : Label
{
private int _score = 0;
}
Il punteggio dovrebbe aumentare di 1 ogni volta che schiacciamo un mostro. Possiamo usare il segnale squashed per sapere quando ciò accade. Tuttavia, poiché istanziamo i mostri da codice, non possiamo collegare il segnale di un mob a ScoreLabel tramite l'editor.
Invece, dobbiamo effettuare la connessione dal codice ogni volta che generiamo un mostro.
Apri lo script main.gd. Se è ancora aperto, puoi cliccare sul suo nome nella colonna di sinistra dell'editor di script.

In alternativa, puoi fare doppio clic sul file main.gd nel pannello FileSystem.
In fondo alla funzione _on_mob_timer_timeout(), aggiungi la seguente riga:
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;
}
Questa riga significa che quando il mob emette il segnale squashed, il nodo ScoreLabel lo riceverà e chiamerà la funzione _on_mob_squashed().
Ritorna allo script score_label.gd per definire la funzione di callback _on_mob_squashed().
Da qui, incrementiamo il punteggio e aggiorniamo il testo visualizzato.
func _on_mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = $"Score: {_score}";
}
La seconda riga utilizza il valore della variabile score per sostituire il segnaposto %s. Quando si utilizza questa funzione, Godot converte automaticamente i valori in testo, il che è utile per mostrare testo nelle etichette o per utilizzare la funzione print().
Vedi anche
Puoi saperne di più sulla formattazione delle stringhe qui: Stringe di formato in GDScript. In C#, considera di utilizzare l'interpolazione di stringhe con "$".
Ora puoi giocare e schiacciare un po' di nemici per vedere il punteggio aumentare.

Nota
In un gioco complesso, potresti voler separare completamente l'interfaccia utente dal mondo di gioco. In tal caso, non terresti traccia del punteggio sull'etichetta. Invece, potresti volerlo memorizzare in un oggetto separato e dedicato. Ma durante la prototipazione o quando il progetto è semplice, è bene mantenere il codice semplice. La programmazione è sempre un atto di bilanciamento.
Riprovare il gioco
Adesso aggiungeremo l'abilita di giocare nuovamente dopo essere morti. Quando il giocatore muore, mostreremo un messaggio sullo schermo e aspetteremo un input.
Ritorna alla scena main.tscn, seleziona il nodo UserInterface, aggiungi un nodo figlio ColorRect e chiamalo Retry. Questo nodo riempie un rettangolo con un colore uniforme e fungerà da rivestimento per scurire lo schermo.
Per estenderlo sull'intera finestra, puoi utilizzare il menu Preimpostazioni di ancoraggio nella barra degli strumenti.

Aprilo e applica il comando Rettangolo completo.

Nulla è successo. Beh, quasi nulla; solo i quattro spilli verdi si spostano agli angoli del riquadro di selezione.

Questo perché i nodi dell'interfaccia utente (tutti quelli con un'icona verde) funzionano con ancore e margini relativi al riquadro di delimitazione del nodo genitore. In questo caso, il nodo UserInterface ha dimensioni ridotte e il nodo Retry è limitato da queste.
Seleziona UserInterface e applica anche Preimpostazione di ancoraggio -> Rettangolo completo. Il nodo Retry dovrebbe ora estendersi sull'intera viewport.
Cambiamo il suo colore in modo che scurisca l'area di gioco. Seleziona Retry e nell'Ispettore, imposta il suo Color su qualcosa di scuro e trasparente. Per farlo, nel selettore colore, trascina lo slider A verso sinistra. Questo controlla il canale alfa del colore, ovvero la sua opacità/trasparenza.

Successivamente, aggiungi un nodo Label come figlio di Retry e assegnagli su Text il testo "Press Enter to retry". Per spostarla e ancorarla al centro dello schermo, applicagli Preimpostazioni di ancoraggio -> Al centro.

Programmare l'opzione per riprovare
Ora possiamo passare al codice per mostrare e nascondere il nodo Retry quando il giocatore muore e ricomincia a giocare.
Apri lo script main.gd. Per prima cosa, vogliamo nascondere il rivestimento all'inizio del gioco. Aggiungi questa riga alla funzione _ready().
func _ready():
$UserInterface/Retry.hide()
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
Poi, quando il giocatore viene colpito, mostriamo il rivestimento.
func _on_player_hit():
#...
$UserInterface/Retry.show()
private void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Infine, quando il nodo Retry è visibile, dobbiamo ascoltare l'input del giocatore e riavviare il gioco se preme Invio. Per fare ciò, utilizziamo il callback integrato _unhandled_input(), che viene attivato a ogni input.
Se il giocatore ha premuto l'azione di input predefinita ui_accept e Retry è visibile, ricarichiamo la scena attuale.
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 funzione get_tree() ci dà accesso all'oggetto globale SceneTree, che ci consente di ricaricare e riavviare la scena attuale.
Aggiungere la musica
Per aggiungere musica che viene riprodotta continuamente in sottofondo, utilizzeremo un'altra funzionalità di Godot: gli autoload.
Per riprodurre l'audio, basta aggiungere un nodo AudioStreamPlayer alla scena e allegarvi un file audio. Quando si avvia la scena, l'audio può essere riprodotto automaticamente. Tuttavia, ricaricando la scena, come facciamo per giocare nuovamente, anche i nodi audio vengono ripristinati e la musica riparte da capo.
Puoi usare la funzionalità degli autoload per affinché Godot carichi automaticamente un nodo o una scena all'inizio del gioco, fuori dalla scena attuale. Puoi anche utilizzarla per creare oggetti accessibili globalmente.
Crea una nuova scena andando al menu Scena e cliccando su Nuova scena oppure usa l'icona + accanto alla scena attualmente aperta.

Fare clic sul pulsante Altro nodo per creare un AudioStreamPlayer e rinominarlo in MusicPlayer.

Abbiamo incluso una colonna sonora nella cartella art/, House In a Forest Loop.ogg. Cliccala e trascinala sulla proprietà Stream nell'Ispettore. Inoltre, attiva la proprietà Autoplay in modo che la musica venga riprodotta automaticamente all'inizio del gioco.

Salva la scena sotto il nome di music_player.tscn.
Dobbiamo registrarlo come un autoload. Andiamo al menu Progetto -> Impostazioni del progetto… e clicchiamo sulla scheda Globali -> Autoload.
Nel campo Percorso, inserisci il percorso della scena. Fai clic sull'icona della cartella per aprire l'esploratore file e fai doppio clic su music_player.tscn. Quindi, fai clic sul pulsante Aggiungi a destra per registrare il nodo.

music_player.tscn ora viene caricato in qualsiasi scena che apri o avvii. Perciò, se ora avvii il gioco, la musica verrà riprodotta automaticamente in qualsiasi scena.
Prima di concludere questa lezione, ecco una rapida occhiata a come funziona. Quando avvii il gioco, il pannello Scena cambia e ti mostra due schede: Remoto e Locale.

La scheda Remoto ti consente di visualizzare l'albero dei nodi del tuo gioco in esecuzione. Lì vedrai il nodo Main e tutto il contenuto della scena, con i mob istanziati in basso.

In cima si trovano il MusicPlayer caricato automaticamente, e un nodo root, che è la viewport del gioco.
E questo è tutto per questa lezione. Nella prossima parte aggiungeremo delle animazioni per rendere il gioco più piacevole da guardare e da provare.
Ecco lo script main.gd completo come riferimento.
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();
}
}