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ó Control como filho de Main e nomeie-o como UserInterface. Você será levado automaticamente para a tela 2D, onde poderá editar sua Interface de Usuário (UI).
Adicione um nó Label e renomeie-o para 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.
Vá até Substituição de Tema, e expanda Cores e clique na caixa preta ao lado de Cor da fonte para pintar o texto.
Escolha um tom escuro para contrastar bem com a cena 3D.
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 IU 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 uma propriedade, a Fonte Padrão.
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.
Clique na propriedade Default Font e crie um novo DynamicFont.
Expanda a DynamicFont clicando nela e expanda sua seção Font. Lá, você verá um campo vazio Font Data.
Este espera um arquivo de fonte como os que você tem em seu computador. A DynamicFont suporta os seguintes formatos:
TrueType (
.ttf
)OpenType (
.otf
)Web Open Font Format 1 (
.woff
)Web Open Font Format 2 (
.woff2
, desde o Godot 3.5)
No painel Sistema de Arquivos, expanda o diretório fonts
e clique e arraste o arquivo Montserrat-Medium.ttf
incluído no projeto para o Font Data. O texto reaparecerá na visualização do tema.
O texto é um pouco pequeno. Configure o Configurações -> Tamanho 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
public 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, ao instanciarmos monstros a partir do código, não podemos fazer a conexão no 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 Principal.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_MobTimer_timeout()
, adicione a seguinte linha.
func _on_MobTimer_timeout():
#...
# We connect the mob to the score label to update the score upon squashing one.
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
public void OnMobTimerTimeout()
{
// ...
// We connect the mob to the score label to update the score upon squashing one.
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(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``ScoreLabel.gd`` para definir a função de chamada 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 = string.Format("Score: {0}", _score);
}
A segunda linha utiliza o valor da variável score
para substituir o substituto %s
. Ao utilizar este recurso, o Godot converte automaticamente os valores em texto, o que é conveniente para a saída de texto em etiquetas ou utilizando a função print()
.
Ver também
Você pode aprender mais sobre a formatação de strings aqui: Formatação de Strings em GDScript.
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 Principal, 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 Layout 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 Layout -> 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 filho de Retry e dê a ele o Text "Pressione Enter para tentar novamente."
Para movê-lo e ancorá-lo no centro da tela, aplique Layout -> 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 Principal.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()
public 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 <doc_singletons_autoload>`.
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 acessando o menu Cena e clicando em Nova Cena.
Clique no botão Outro nó para criar um AudioStreamPlayer e renomeá-lo 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 MusicPlayer.tscn
.
Temos que registrá-lo para carregar automaticamente. Vá para Projeto > Configurações do Projeto no menu e mude para a aba AutoLoad.
No campo Caminho, você deseja inserir o caminho para sua cena. Clique no ícone da pasta para abrir o navegador de arquivos e clique duas vezes em MusicPlayer.tscn
. Em seguida, clique no botão Adicionar à direita para registrar o nó.
Se você executar o jogo agora, a música tocará automaticamente. E mesmo quando você perde e tenta novamente, ela continua.
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 ''Principal.gd'' para referência.
extends Node
export (PackedScene) var mob_scene
func _ready():
randomize()
$UserInterface/Retry.hide()
func _unhandled_input(event):
if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible:
get_tree().reload_current_scene()
func _on_MobTimer_timeout():
var mob = mob_scene.instance()
var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
mob_spawn_location.unit_offset = randf()
var player_position = $Player.transform.origin
mob.initialize(mob_spawn_location.translation, player_position)
add_child(mob)
mob.connect("squashed", $UserInterface/ScoreLabel, "_on_Mob_squashed")
func _on_Player_hit():
$MobTimer.stop()
$UserInterface/Retry.show()
public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public override void _Ready()
{
GD.Randomize();
GetNode<Control>("UserInterface/Retry").Hide();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("ui_accept") && GetNode<Control>("UserInterface/Retry").Visible)
{
GetTree().ReloadCurrentScene();
}
}
public void OnMobTimerTimeout()
{
Mob mob = (Mob)MobScene.Instance();
var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
mobSpawnLocation.UnitOffset = GD.Randf();
Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
mob.Initialize(mobSpawnLocation.Translation, playerPosition);
AddChild(mob);
mob.Connect(nameof(Mob.Squashed), GetNode<ScoreLabel>("UserInterface/ScoreLabel"), nameof(ScoreLabel.OnMobSquashed));
}
public void OnPlayerHit()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Control>("UserInterface/Retry").Show();
}
}