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...
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.
Na cena principal, adicione um novo nó filho Control a Main e nomeie-o como UserInterface. Certifique-se de estar na tela 2D, onde é possível editar sua Interface de Usuário (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.

Por padrão, um tema tem apenas algumas propriedades: Default Base Scale, Default Font e 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.
A Default Font (Fonte Padrão) espera um arquivo de fonte como os que você tem no seu computador. Dois formatos comuns de arquivo de fonte são TrueType Font (TTF) e 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().
Volte para o script score_label.gd para definir a função de retorno _on_mob_squashed().
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}";
}
A segunda linha usa o valor da variável score para substituir o marcador %s. Ao usar esse recurso, o Godot converte automaticamente os valores para texto, o que é conveniente ao exibir textos em labels ou ao usar a função print().
Ver também
Você pode aprender mais sobre formatação de strings aqui: Formatação de Strings em GDScript. Em C#, considere usar interpolação de strings com "$".
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
Para adicionar música que toca continuamente ao fundo, vamos utilizar outra característica no 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.

Salve a cena como music_player.tscn.
Precisamos registrá-la como um autoload. Vá ao menu Project -> Project Settings… e clique na aba Globals -> Autoload.
No campo Path, informe o caminho para sua cena. Clique no ícone de pasta para abrir o navegador de arquivos e dê um duplo clique em music_player.tscn. Em seguida, clique no botão Add à direita para registrar o nó.

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();
}
}