Tutorial Space Shooter Godot: Heads-up Display - Parte 8

Aprenda a criar um jogo Space Shooter de forma simples e prática com a Godot. Aprenda as funcionalidades da Engine e faça seu jogo um sucesso.

Tutorial Space Shooter Godot: Heads-up Display - Parte 8
Photo by Ben Griffiths / Unsplash

Bem vindo de volta desenvolvedor!!! Hoje vamos falar sobre o HUD ou Heads-Up Display, também conhecido como tela de alerta ou mesmo como interface de usuário (se bem que esse último tem outros significados). O HUD é responsável por manter o jogador atualizado de sua pontuação, quantidade de vidas, munição e outros elementos de forma instantânea.

Um bom HUD apresenta ao jogador as informações que ele precisa, sem interferir na jogabilidade, possui elementos de fácil visualização e entendimento. Vou dar um exemplo simples do que é uma HUD excelente:

Exemplo de HUD: Super Mário Bros Wii U

Suponhamos que você nunca tenha jogado nenhum jogo do Mário, só de olhar para uma tela como essa você conseguiria identificar os elementos da HUD?

Na imagem acima, você consegue ver do lado esquerdo a quantidade de vidas do jogador, número de moedas especiais coletadas e número de moedas simples coletadas, do lado direito vemos a pontuação total do jogador e tempo restante da fase.

E o mais legal é que praticamente todos elementos tem alguma imagem/ícone que representa estes elementos de forma lúdica, então ao ver uma moeda na fase, você consegue saber que ao coletá-la, o número de moedas vai ser incrementado.

Todas estas informações foram colocadas nestas posições de forma estratégica, pois não impactam na jogabilidade e permitem ao jogador identificar rapidamente informações importantes para o seu progresso. Legal né?

Um HUD tem uma característica bem diferente de outros elementos do jogo, ele faz parte de uma "camada de apresentação", isto significa que os elementos desta camada são elementos visuais que não fazem parte dos elementos jogáveis. Esta camada também é conhecida como Canvas e é como se fosse uma máscara que cobre a tela do jogo com os elementos de atenção do jogador.

E bora começar o post de hoje, né? Ah, e se você perdeu algum post anterior, clica neste link aqui para apresentar todos os posts marcados com a tag tutorial, dá uma olhada vê se encontra o que está procurando. Segue o fio:

Passo 10 - HUD (HeadsUp Display)

Implementaremos um HUD intergaláctico para exibir informações cruciais aos pilotos do Space Shooter.

No nosso jogo vou optar por um HUD simples, vamos apresentar somente a quantidade de vidas do jogador e a pontuação atual, mas se você achar interessante pode incluir algum elemento representando o tempo de duração do escudo e/ou tiro duplo (quem sabe não incluímos isto em um post futuro).

Vamos criar 2 novas cenas, uma delas só com os ícones representando as vidas do jogador e a outra que vai apresentar de forma dinâmica a quantidade de vidas do jogador e a pontuação atual.

Fist things first... Clique no + ao lado da última tab aberta e crie uma nova cena, selecione como nó raíz dessa cena um TextureRect. Este tipo de nó é similar ao nó Sprite que usamos para a criação do Jogador, com a diferença que ele faz parte dos nós da camada de apresentação. Este nó será responsável por armazenar a imagem do ícone de vidas, no meu caso selecionei uma miniatura da nave, mas você pode usar um coração, bolinhas, estrelinhas, o que achar interessante.

Cena do Icone de vidas criada.

Nesta cena ajustei apenas algumas propriedades para não distorcer o tamanho da imagem à ser apresentada na tela do jogador. Estas propriedades foram:

  • Expand Mode: Keep
  • Stretch Mode: Scale
  • Transform:
    • Size: altura e largura da imagem que utilizei

Agora vamos para a próxima cena, a cena que além das vidas, também vai apresentar a pontuação do jogador.

Nesta segunda cena, vamos usar como nó raíz um nó do tipo Control, quando for criar a cena, basta selecionar a opção User Interface que o nó será criado como um nó Control. Como filho deste control, inclua um Nó do tipo HBoxContainer e outro do tipo Label.

O nó HBoxContainer tem uma propriedade interessante de colocar cada um dos elementos visuais filhos dele lado à lado, isso nos permitirá incluir as vidas de forma dinâmica via código e fazer com que elas apareçam corretamente na tela, sem códigos muito complexos.

Para que as vidas fiquem em uma posição legal na tela, ajustei a posição do HBoxContainer na propriedade Position e para que os ícones não fiquem muito próximos entre si, ajustei o espaçamento entre eles na propriedade separation.

💡
Dica:
Para facilitar a visualização inclua algumas cópias da cena de ícone de vida como filhas do HBoxContainer e enquanto estiver alterando os valores você consegue ver os ajustes sendo apresentados na tela.
Não esqueça de remover tais cenas antes de incluir o HUD na sua cena do jogo.

Para o Label, os ajustes que fiz foram de alterar o Alinhamento Horizontal, para que fique alinhado à direita (no lado oposto às vidas), Alinhamento Vertical centralizado, para ficar no centro do retângulo, o Layout Mode para Anchors, a propriedade de Anchor Preset para Top Wide e a posição um pouco deslocada do topo da tela, alinhada com os ícones de vida.

Além disso apliquei também uma fonte diferente na propriedade Font e o resultado ficou assim:

HUD com os ícones de vida e o Label para o Score

O próximo passo é incluir o HUD na cena do Game, como um filho desta cena. Ainda vamos fazer o script para incluir as vidas de forma dinâmica, mas antes quero que você veja um pequeno problema quando incluímos a cena como filha da cena do jogo:

Cena do Game com o HUD apresenta problemas com o Label

As informações do Label ficaram sobrepostas aos ícones, bem deslocadas em relação à HUD que estávamos querendo montar, né? isso acotnece porque a cena do Game é um Node2D e isso faz com que o Control da cena do HUD não funcione corretamente.

Para passar à funcionar corretamente, vamos precisar mudar o nó referência da cena do Game. Clique com o botão direito no nó raíz dessa cena e selecione a opção Change Node... e selecione a opção Node ao invés de Node2D.

Este tipo de nó é o tipo de nó mais básico do Godot e dessa forma também é base para um nó do tipo Control funcionar corretamente, com isso o problema da posição do HUD vai ficar adequado. Não esqueça de ajustar o script associado para passar à extender do nó Node e não mais de Node2D.

Ateração do tipo de cena do Game para Node.
💡
Atenção:
Este tipo de alteração de nó só pode ser feito se você não estiver utilizando nenhuma funcionalidade do tipo de nó antes da alteração ou então será necessário que você adapte seu código para que as funcionalidades continuem funcionando corretamente.

Se você executar o seu jogo agora, vai conseguir vizualizar a HUD sobre a tela do jogo, mas ainda sem refletir nada de pontuação ou das vidas perdidas e recuperadas pelo jogador. Vamos ajustar nossos scripts para refletir estes pontos em tela à partir de agora, tá legal?

Comecemos pela cena do HUD, vamos associar um script ao nó raíz e incluir os seguintes pontos:

  • Uma variável referenciando ao container HBox;
  • Uma variável pré-carregando a cena de ícone das vidas;
  • Uma função para limpar as vidas quando a cena for carregada;
  • Uma função para definir a quantidade de vidas à serem apresentadas.

O seu código vai ficar dessa forma:

extends Control

@onready var life_container = $LifeContainer

var life_icon_scene = preload("res://scenes/life_icon.tscn")

func _ready():
	clear_lives()

func clear_lives():
	for child in life_container.get_children():
		child.queue_free()

func set_lives(lives: int):
	clear_lives()
	for i in range(lives):
		life_container.add_child(life_icon_scene.instantiate())

Legal, já temos uma função para atualizar a quantidade de vidas do jogador, mas quem controla a quantidade de vidas do jogador é o script do jogador e o HUD não tem uma relação direta com esse nó, ficando meio complicado passar o dado de um lado pro outro, né?

Vamos inserir então um conceito interessante aqui: scripts globais... e não são scripts que trabalham na Rede Globo não, nem scripts que os Terraplanistas não acreditam... são scripts que podem ser acessados à partir de qualquer nó presente no seu jogo. Este tipo de script também é conhecido como Autoload ou Singleton.

Crie uma nova pasta no seu projeto com o nome de autoload, clique com o botão direito sobre esta nova pasta e crie um novo script chamado signals. Este script vai ter todos os sinais que podem ser acessados à partir de qualquer cena. É desse jeito aqui:

Criação do script autoload de sinais.

Abra o seu script global e inclua um sinal customizado conforme abaixo:

extends Node

signal on_player_life_change(life)

Inclua seu script como um autoload à partir do menu de configurações do projeto, conforme fiz nessa imagem aqui:

Definindo o script como autoload.

Este sinal, por estar em um script global, pode ser acessado à partir de qualquer nó, mas precisamos fazer alguns ajustes para que tal sinal seja interpretado. No script do Player, faremos os seguintes ajustes:

  • Incluir a chamada desse sinal quando a cena do jogador for criada inicialmente, com a quantidade padrão de vidas;
  • Incluir a chamada desse sinal sempre que ocorrer uma mudança da quantidade de vida do jogador;

Isso será feito incluíndo o seguinte comando nas funções ready, apply_damage e recover_health:

Signals.on_player_life_change.emit(health)

No script da HUD, faremos um pequeno ajuste também:

  • Incluir a conexão do sinal à função que atualiza a quantidade de ícones de vida na tela do jogador;

Basta incluir na função ready o seguinte comando:

Signals.on_player_life_change.connect(set_lives)

E por último, na cena do Game, precisamos colocar o HUD acima do nó do Player ou então as vidas não serão apresentadas até que o jogador receba algum dano ou colete o power-up de recuperação de vida:

Cena do HUD posicionada antes da cena do jogador para que o sinal inicial do jgador seja capturado corretamente e apresente a quantidade de vidas.

Para a pontuação, faremos de forma similar:

  • Criar um sinal customizado no script global, para identificar o aumento dos pontos;
  • Conectar o sinal no script da HUD com uma função para atualizar os pontos apresentados;
  • Disparar o sinal no script do Inimigo para gerar pontuação à cada inimigo abatido.

Nosso autoload vai ficar assim:

Novo sinal para incremento do score

No script da HUD, vamos criar uma variável referência ao label, uma variável para armazenar o score, uma função para atualizar esta variável e o texto do label e também incluir a conexão do sinal à esta função. Vai ficar desta forma:

Script do HUD com os ajustes para atualizar o score

No script do inimigo, vamos criar uma variável para definir quantos pontos cada tipo de inimigo vai proporcionar e disparar o sinal sempre que o inimigo for abatido. Nosso script vai ficar desta forma:

Ajustes no script do inimigo para gerar pontos ao morrer

Não se esqueça de atualizar a variável criada em cada um dos inimigos para refletir a pontuação que você quer que cada tipo gere.

Seu joguinho já está ficando com cara de um jogo profissional, hein? Agora é com você, que tal começar fazendo testes para ver se tudo está funcionando como você queria?

Espero que tenha nos acompanhado e gostado do conteúdo até aqui, nos vemos na próxima semana com a criação do menu inicial e tela de game over, ok?