Pontuação e repetição
Nesta parte, adicionaremos a pontuação, a reprodução de música e a capacidade de reiniciar o jogo.
Temos que acompanhar a pontuação atual em uma variável e exibi-la na tela usando uma interface mínima. Para isso, usaremos uma text label.
In the main scene, add a new child node Control to Main and name it
UserInterface. Ensure you are on the 2D screen, where you can edit your User Interface (UI).
Adicione um Label nó e nomei-o ScoreLabel

No Inspetor, defina o Texto do Label como um substituto como "Pontuação: 0".

Além disso, o texto é branco por padrão, como o fundo do nosso jogo. Precisamos mudar sua cor para vê-lo durante a execução.
Role para baixo para Theme Overrides, e expanda Colors e habilite Font Color para colorir o texto em preto (o que contrasta bem com a cena 3D branca)

Finalmente, clique e arraste o texto na janela de exibição para afastá-lo do canto superior esquerdo.

O nó UserInterface nos permite agrupar nossa UI em um ramo da árvore da cena e utilizar um recurso temático que se propagará a todos os seus filhos. Vamos usá-lo para definir a fonte do nosso jogo.
Criando um tema de interface do usuário
Mais uma vez, selecione o nó UserInterface. No Inspetor, crie um novo recurso temático em Tema -> Tema.

Clique sobre ele para abrir o editor de temas No painel inferior. Ele lhe dá uma prévia de como todos os widgets UI embutidos irão ficar com seu recurso temático.
By default, a theme only has a few properties: Default Base Scale, Default Font and Default Font Size.
Ver também
Você pode adicionar mais propriedades ao recurso temático para projetar interfaces de usuário complexas, mas isso está além do escopo desta série. Para saber mais sobre a criação e edição de temas, veja Introdução ao skinning GUI.
The Default Font expects a font file like the ones you have on your computer. Two common font file formats are TrueType Font (TTF) and OpenType Font (OTF).
No painel Sistema de Arquivos, expanda o diretório fonts e clique e arraste o arquivo Montserrat-Medium.ttf incluído no projeto para Default Font. O texto reaparecerá na visualização do tema.
O texto é um pouco pequeno. Configure o Default Font Size para 22 pixels para aumentar o tamanho do texto.

Acompanhando a pontuação
Vamos trabalhar a seguir na pontuação. Anexe um novo script ao ScoreLabel e defina a variável score.
extends Label
var score = 0
using Godot;
public partial class ScoreLabel : Label
{
private int _score = 0;
}
A pontuação deve aumentar em 1 cada vez que esmagarmos um monstro. Podemos utilizar o sinal squashed para saber quando isso acontece. Entretanto, porque instanciamos monstros pelo código, não podemos conectar o sinal do inimigo ScoreLabel através do editor.
Ao invés disso, temos que fazer a conexão a partir do código toda vez que geramos um monstro.
Abra o script main.gd. Se ainda estiver aberto, você pode clicar em seu nome na coluna da esquerda do editor de scripts.

Alternativamente, você pode clicar duas vezes no arquivo main.gd no painel Sistema de Arquivos.
Na parte inferior da função _on_mob_timer_timeout(), adicione a seguinte linha:
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 linha significa que quando o inimigo emitir o sinal squashed, o nó ScoreLabel o receberá e chamará a função _on_mob_squashed().
Head back to the score_label.gd script to define the _on_mob_squashed()
callback function.
Aí, incrementamos a pontuação e atualizamos o texto exibido.
func _on_mob_squashed():
score += 1
text = "Score: %s" % score
public void OnMobSquashed()
{
_score += 1;
Text = $"Score: {_score}";
}
The second line uses the value of the score variable to replace the
placeholder %s. When using this feature, Godot automatically converts values
to string text, which is convenient when outputting text in labels or
when using the print() function.
Ver também
You can learn more about string formatting here: Formatação de Strings em GDScript. In C#, consider using string interpolation with "$".
Agora você pode jogar e esmagar alguns inimigos para ver a pontuação aumentar.

Nota
Em um jogo complexo, você pode querer separar completamente sua interface de usuário do mundo do jogo. Nesse caso, você não manteria o registro da pontuação no rótulo. Em vez disso, você pode querer armazená-la em um objeto separado e dedicado. Mas quando estiver fazendo um protótipo ou quando seu projeto é simples, é bom manter seu código simples. A programação é sempre um ato de equilíbrio.
Tentando novamente o jogo
Agora vamos acrescentar a capacidade de jogar novamente após a morte. Quando o jogador morrer, exibiremos uma mensagem na tela e aguardaremos a entrada.
Volte para a cena main.tscn, selecione o nó UserInterface, adicione um nó ColorRect como filho dele e nomeie-o como Retry. Esse nó preenche um retângulo com uma cor uniforme e servirá como uma sobreposição para escurecer a tela.
Para que se estenda por toda janela de exibição, você pode usar o menu Anchor Preset na barra de ferramentas.

Abra e aplique o comando Full Rect.

Nada acontece. Bem, quase nada; apenas os quatro pinos verdes se movem para os cantos da caixa de seleção.

Isto porque os nós da IU (todos os que têm um ícone verde) trabalham com âncoras e margens relativas à caixa de delimitação de seus pais. Aqui, o nó UserInterface tem um tamanho pequeno e o Retry é limitado por ele.
Selecione o UserInterface e aplique Anchor Preset -> Full Rect a ele também. O nó Retry deve agora abranger toda janela de exibição.
Vamos mudar sua cor para que escureça a área de jogo. Selecione Retry e no Inspetor, defina sua Cor para algo mais escuro e transparente. Para isso, no seletor de cores, arraste a barra deslizante A para a esquerda. Ela controla o canal alfa da cor, ou seja, sua opacidade.

Em seguida, adicione um Label como um filho de Retry e coloque o Texto "Press Enter to retry." Para movê-lo e ancorar no centro da tela, aplique Anchor Preset -> Center a ele.

Programando a opção de nova tentativa
Agora podemos ir para o código para mostrar e esconder o nó Retry quando o jogador morre e joga novamente.
Abra o script main.gd. Primeiro, queremos ocultar a sobreposição no início do jogo. Adicione esta linha à função _ready().
func _ready():
$UserInterface/Retry.hide()
public override void _Ready()
{
GetNode<Control>("UserInterface/Retry").Hide();
}
Então, quando o jogador é atingido, mostramos a sobreposição.
func _on_player_hit():
#...
$UserInterface/Retry.show()
private void OnPlayerHit()
{
//...
GetNode<Control>("UserInterface/Retry").Show();
}
Finalmente, quando o nó Retry estiver visível, precisamos ouvir a entrada do jogador e reiniciar o jogo se ele pressionar Enter. Para fazer isso, usamos a chamada de retorno interna _unhandled_input().
Se o jogador pressionar a ação de entrada predefinida ui_accept e Retry estiver visível, nós recarregamos a cena atual.
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();
}
}
A função get_tree() nos dá acesso ao objeto global SceneTree, que nos permite recarregar e reiniciar a cena atual.
Adicionando música
To add music that plays continuously in the background, we're going to use another feature in Godot: autoloads.
Para reproduzir áudio, tudo o que você precisa fazer é adicionar um nó AudioStreamPlayer à sua cena e anexar um arquivo de áudio a ele. Quando você inicia a cena, ela pode ser reproduzida automaticamente. Entretanto, quando você recarrega a cena, como fazemos para tocar novamente, os nós de áudio também são reinicializados, e a música começa de novo desde o início.
Você pode usar o recurso de carregamento automático para que Godot carregue um nó ou uma cena automaticamente no início do jogo, fora da cena atual. Você também pode usá-lo para criar objetos globalmente acessíveis.
Crie uma nova cena indo para o menu Scene e clicando Nova Cena ou usando o ícone + ao lado de sua cena aberta atualmente.

Clique no botão Other Node para criar um AudioStreamPlayer e renomei-o para MusicPlayer.

Incluímos uma trilha sonora House In a Forest Loop.ogg, no diretório art/. Clique e arraste-a para a propriedade Stream no diretório Inspetor. Além disso, ative Autoplay para que a música toque automaticamente no início do jogo.

Save the scene as music_player.tscn.
We have to register it as an autoload. Head to the Project -> Project Settings… menu and click on the Globals -> Autoload tab.
In the Path field, you want to enter the path to your scene. Click the folder
icon to open the file browser and double-click on music_player.tscn. Then,
click the Add button on the right to register the node.

A cena music_player.tscn agora é carregada em qualquer outra cena que você abrir ou executar. Portanto, se você rodar o jogo agora, a música será reproduzida automaticamente em qualquer cena.
Antes de encerrarmos esta lição, aqui vai uma rápida olhada em como funciona por debaixo dos panos. Quando você executa o jogo, seu painel Cena muda para lhe dar duas abas: Remota e Local.

A aba Remoto permite que você visualize a árvore de nós do seu jogo em execução. Lá, você verá o nó Principal e tudo o que a cena contém e os inimigos instanciados na parte inferior.

No topo estão o MusicPlayer carregado automaticamente e um nó raiz, que é a janela de visualização do seu jogo.
E isso é tudo para esta lição. Na próxima parte, adicionaremos uma animação para tornar o jogo muito mais bonito.
Aqui está o script completo main.gd para referência.
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();
}
}