Tutorial Space Shooter Godot: Power-Ups (continuação) - Parte 6

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: Power-Ups (continuação) - Parte 6
Photo by Geeky Shots / Unsplash

No último post fizemos a criação das nossas cenas de Power-Up's e iniciamos os ajustes nas cenas do jogador e inimigo para a criação do escudo. Mas a forma como criamos nossa cena do escudo não foi a ideal, pois nós tivemos que ocultar e desabilitar 0 nó Área2D que criamos para evitar que o escudo apareça na cena e seja atingido pelos inimigos.

No post de hoje, vamos ajustar isso, criando uma cena separada para o escudo e instanciando esta cena de forma similar ao que foi feito lá atrás com os tiros laser.

Mas antes de começar, vou te pedir para você interagir com a gente:

É muito importante entender o quanto vocês estão aproveitando nossos posts para aprender e evoluir na sua jornada de desenvolvedor de jogos.

Sem mais delongas, vamos ao trabalho. Comecemos selecionando o nó Área2D do escudo e clicando com o botão direito do mouse selecionando para salvar como uma nova cena, desse jeito:

Salvando o escudo como nova cena.

Após criar esta cena separada, remova o escudo da cena do jogador para não ter o escudo disponível desde o início, o funcionamento que definimos anteriormente no script do jogador precisa ser alterado.

Ao invés de habilitar a visibilidade do nó no script do jogador, nós vamos instanciar o escudo e controlar o seu tempo de vida dentro de um novo script associado diretamente ao escudo.

Muita informação? Vamos devagar que você vai entender.
Vamos alterar os seguintes pontos no script do jogador:

  • Criar a variável shield_scene para que ela referencie a cena pré-carregada que acabamos de criar;
  • Remover a referência ao nó do escudo e ao timer relacionado;
  • Remover as alterações feitas na função ready, pois o escudo não fará parte automática da cena do jogador;
  • Instanciar a cena do escudo dentro da função apply_shield;
  • Remover a função de timeout do escudo do script do jogador, será ativado pela cena do escudo posteriormente.

Estes ajustes tornam o script do jogador mais limpo e separam as funções de uma maneira mais isoladas, isso torna seu código mais conciso e fácil de dar manutenção.

...
var shield_scene = preload("res://scenes/shield.tscn")
...
func apply_shield(shieldTime):
	is_shield_on = true
	var shield = shield_scene.instantiate()
	shield.shield_time = shieldTime
	add_child(shield)
...

No editor seu script ficará assim:

Ajustes no script do Player com a cena do escudo separada
💡
Interessante:
Sempre que possível, desde que não torne seu código algo muito complexo, isole funções para que sejam executadas dentro do contexto correto.

Para que funcione corretamente, precisamos também criar o script na cena do escudo. Abra a cena do escudo criada anteriormente, selecione o nó raíz e vincule um script à ele como já fizemos anteriormente para diversas cenas.

Neste script vamos criar uma variável exportável que vai receber o tempo de vida do escudo. Só para lembrar, no power-up nós definimos este tempo e recebemos este tempo como um parâmetro na função apply_shield do player. Passaremos este mesmo parâmetro para a cena do escudo através da variável shield_time.

Também precisamos de uma variável referenciando o timer para termos um acesso mais simplificado dele no nosso script e além disso vamos deixá-lo como Autostart e definir seu tempo na função ready ele como sendo o shield_time. Vamos também conectar o sinal de timeout para excluir o escudo da cena e avisar ao Player que o escudo foi desativado.

extends Area2D

@onready var shield_timer = $ShieldTimer

@export var shield_time: float

func _ready():
	shield_timer.wait_time = shield_time

func _on_shield_timer_timeout():
	get_parent().is_shield_on = false
	queue_free()

Mais à frente, quando formos falar de colisões e explosões, talvez precisemos fazer alguns ajustes neste script mas por enquanto vamos deixar desta forma, já está nos atendendo como precisamos.

Acho que sobre escudo já tá bom por hoje, afinal tomamos um post inteiro na semana passada e mais metade do post de hoje para ajustes, né? Erros acontecem e peço desculpa se a forma como apresentei na semana passada não foi a ideal, temos que ter a mente aberta para aceitar que erramos e fazer os ajustes com o coração aberto. Assim não temos muito como nos decepcionar.

Vamos para o próximo power-up então? Vamos com um mais simples dessa vez... Vamos com o de atualizar a saúde do jogador. Nesse power-up não vamos precisar alterar nada na cena, somente no script, pois a vida do jogador é apenas uma variável que armazena uma certa quantidade de pontos.

Faremos os seguintes ajustes:

  • Criar uma variável de valor máximo de saúde;
  • Criar a função recover_health que será chamada quando o power-up de vida for obtido;
  • Validar se o valor da vida é maior que o valor máximo e limitar à tal valor.

Basicamente:

...
var max_health = 10
...
func recover_health(recoveryPoints):
	health += recoveryPoints
	if health >= max_health:
		health = max_health

Simples, fácil e indolor, né? Talvez precisemos fazer algum ajuste posteriormente quando formos falar de apresentar a vida, tempo de escudo e de tiro duplo na interface do jogador, mas uma coisa de cada vez... Já nos atende dessa forma.

E por falar em tiro duplo, vamos para o nosso último power-up, ele terá uma complexidade intermediária entre a recuperação de vida e o escudo do jogador.

Vamos ao que interessa, vamos alterar a cena do jogador incluíndo o seguinte:

  • Um novo Node2D como container dos novos pontos de disparo, somente para não deixar os nós bagunçados;
  • Dois novos Marker2D à esquerda e direita da Turreta original, como filhos do container;
  • Um nó Timer para controlar o tempo de vida do tiro duplo, deixar ele como Oneshot ou seja de execução única .
Ajustes na cena do jogador incluíndo os pontos de disparo do tiro duplo.

E para o script precisamos criar:

  • A função apply_double_shoot que irá coordenar quais pontos de disparo ficarão ativos;
  • Uma variável booleana para identificar quando o tiro duplo estiver ativo, será ligada na função apply_double_shoot e desligada no final do timer.
  • Conectar o timeout do timer para controlar por quanto tempo ficará habilitado o tiro duplo;
  • Uma variável para referenciar cada umas das turretas de forma fácil no nosso código;
  • Uma variável para referenciar o timer das turretas de forma simples no código;

E o nosso código vai ficar da seguinte forma:

...
@onready var right_turret = $DoubleTurret/RightTurret
@onready var left_turret = $DoubleTurret/LeftTurret
@onready var double_turret_timer = $DoubleTurret/DoubleTurretTimer

...
var is_double_shoot_on = false
...
func shoot():
	if !is_double_shoot_on:
		laser_shot.emit(laser_scene, turret.global_position)
	else:
		laser_shot.emit(laser_scene, right_turret.global_position)
		laser_shot.emit(laser_scene, left_turret.global_position)
...
func apply_double_shoot(doubleShootTime):
	double_turret_timer.wait_time = doubleShootTime
	double_turret_timer.start()
	is_double_shoot_on = true
...
func _on_double_turret_timer_timeout():
	is_double_shoot_on = false

No editor o código será apresentado dessa forma:

Script do Player com os ajustes de tiro duplo

E por último, de nada adianta ter o power-ups criados, os scripts ajustados para identificá-los e realizar as ações correspondentes se não temos como gerar tais power-up's de forma automática para o jogador, então vamos para a próxima parte.


Passo 8 - Spawner de Poderes

Nosso jogador precisará de super-poderes para poder encarar este desafio, vamos ajudá-lo.

Esta parte será muito parecida com a criação do Spawner de Inimigos que a gente fez lá no quarto post desta série, se não se lembra dá uma olhadinha aqui e volta pra cá para continuarmos, tá legal?

Primeiro passo é criar um container para os Power-up's, com um nó do tipo Node2D. Este nó vai armazenar os power-up's gerados para o jogador.

Também vamos incluir um novo Timer, para executar à cada 15 segundos e gerar um Power-Up aleatório. Este nó deve estar definido como Autostart no inspetor.

Novos nós Container e Timer dos Power-Up's

E no script o código será muito semelhante ao que foi feito para os inimigos. Teremos uma variável do tipo Array para definir as cenas dos Power-Up's, uma variável referenciando o novo container e a conexão do sinal de timeout do timer gerando um dos power-up's de forma aleatória.

O código vai ficar desse jeitinho aí:

...
@export var power_up_scenes : Array[PackedScene] = []
...
@onready var power_up_container = $PowerUpContainer

...
func _on_power_up_spawn_timer_timeout():
	var p = power_up_scenes.pick_random().instantiate()
	p.global_position = Vector2(randf_range(30, 510), -50)
	power_up_container.add_child(p)

No editor da Godot vai ser apresentado assim:

Ajustes do script para inclusão do Spawner de Power-Up's

E por último, só precisamos associar as cenas dos poderes no novo array criado, faremos isso selecionando a cena principal do game e incluíndo as cenas através do inspetor:

Incluíndo as cenas dos Power-Up's na lista.

Vamos recapitular o que fizemos hoje?

  • Refizemos a ativação do escudo, criando ele como uma cena separada da cena do Player e criando um script próprio para ele;
  • Criamos uma função de recuperação da vida do jogador quando ele obter o Power-Up correspondente;
  • Criamos uma função para disparo de tiros duplos quando o jogador obter o Power-Up correspondente;
  • Criamos um Spawner de Power-Up's para gerar eles de forma aleatória à cada período de tempo.

É importante ressaltar que as soluções que tomamos para a habilitação dos poderes no jogo não é a única. O escudo foi um exemplo de mais de uma forma de fazer isso. O tiro duplo também poderia ser feito de outra forma, que tal você pensar em uma sugestão e comentar aí embaixo?

Também é possível deixar o tempo de geração mais randômico ou definir uma determinada frenquencia maior para determinado tipo de power-up, mas vou deixar estes pontos para você pensar e tentar propor algo, que tal?

Por hoje é isso desenvolvedor, espero que esteja curtindo a nossa jornada, aproveite e compartilhe esse tutorial com os seus conhecidos, tá legal?

Abraços e nos vemos na próxima semana.