Tutorial Space Shooter Godot: Power-Ups - Parte 5

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 - Parte 5
Photo by N I F T Y A R T ✍🏻 / Unsplash

Fala desenvolvedor, tudo bom? Bom ver você por aqui novamente!

No post de hoje vamos criar Power-Up's, afinal o que seria de um jogo shmup sem power up's para recuperar a vida do jogador, melhorar os tiros ou dar um escudo temporário, né?

Para fazer os Power-Up's funcionarem precisamos criar uma cena e script base para os poderes, criar um spawner para estes poderes e ajustar uma série de pontos nos códigos que já criamos. Bem pareceido com o que fizemos para os inimigos, né?

Perdeu algum dos posts anteriores? Você pode buscar no site através da Tag Tutorial, buscar pelo nome "Tutorial Space Shooter Godot" ou buscar pelos posts que eu criei para ver a lista completa. Dá uma olhada nos anteriores antes de começarmos a trabalhar neste aqui, beleza?

Passo 7 - Power-Up's

Incremente sua aventura cósmica com poderes nunca antes imaginados!!!

Temos muita coisa pra fazer aqui hoje, se prepara. Começaremos com as novas cenas para os Power-Up's, assim como fizemos pra nossos inimigos, vamos criar uma cena inicial e depois fazemos as cenas herdadas. Nossa cena precisa ter os seguintes nós:

  • Area2D - Nó Raíz da cena;
  • Sprite2D - para armazenar a textura do Power-Up;
  • CollisionShape2D - para controlar a colisão com outros objetos;
  • VisibleOnScreenNotifier2D - para remover o poder da cena caso ele tenha saído da cena.

Vou chamar a cena de BasePowerUp e incluir uma textura qualquer do tamanho das texturas dos poderes, no final não vamos utilizar esta cena mas sim as cenas que herdaram as características dessa. Sua cena deve ficar assim:

Cena BasePowerUp com os nós filhos criados

Salve a sua cena e vamos começar à criar o script base para todos os power-up's.

Nosso script precisa ter os seguintes componentes:

  • Um nome de classe para identificar os Power-Up's;
  • Uma variável para a velocidade com que o Power-Up vai descer pela tela;
  • A função para fazer o Power-Up se mover;
  • Uma função para detectar colisão com o Player à partir do sinal on_body_entered da Area2D;
  • Uma função para remover o Power-Up caso não esteja mais visível em tela à partir do sinal screen_exited do Visibility Notifier;
  • A base para uma função que vai dar o poder ao Player (esta função será implementada para cada um dos power-ups e não na cena base).

Seu script ficará assim:

class_name Powerup
extends Area2D

@export var move_speed: float = 50.0


func _physics_process(delta):
	position.y += move_speed * delta

# essa função será implementada nas cenas herdadas
func apply_power_up(player):
    pass

func _on_visible_on_screen_notifier_2d_screen_exited():
	queue_free()
    
func _on_body_entered(body):
	if body is Player:
		apply_power_up(body)
		queue_free()

Dentro do editor o script vai ficar desse jeito:

Script com as funções base.

Desta forma você já tera seu script base e poderá trabalhar na criação dos poderes específicos. Vamos começar com o escudo e vamos devagar pois vamos precisar ajustar também a cena do Player.

Crie uma nova cena herdada, como vimos no post sobre os inimigos, no menu suspenso selecione Scene > New Inherited Scene... Na tela que se abre, selecione a cena que vc acabou de criar e clique em Open. Pronto, sua nova cena foi criada, altere o nome do nó principal e a textura do Sprite2D para o escudo (eu optei por assets do Kenney aqui também) e salve sua nova cena:

Nova cena herdada com alteração no nome e textura.

Atenção no próximo passo, pois apesar de simples pode gerar confusão do que precisa ser feito. Vamos desconectar o script base dessa nova cena e criar um novo script para ela.

Para isso, com o nó raiz selecionado, clique no ícone do pergaminho que agora deve apresentar um X vermelho e algo como "desvincular o script do nó selecionado" e clique uma segunda vez no mesmo ícone que na sequencia deve apresentar um + verde para "vincular um novo script ao nó selecionado".

Neste novo script vamos implementar a função apply_power_up que definimos no script base. Essa função vai simplesmente chamar uma função (que ainda vamos criar) no script do Player para ativar o escudo por um determinado período.

Para que funcione corretamente, nosso script precisa extender a classe Power-Up que nomeamos no script anterior, ter uma variável para definir o tempo do escudo e a função para ativar o power-up. Desse jeito aqui:

extends Powerup

@export var shieldTime : float = 5.0

func apply_power_up(player):
	player.apply_shield(shieldTime)

No editor você verá o script dessa forma:

Script do Power-Up do escudo.

Vamos fazer o próximo Power-up? Faremos agora o Power-Up de tiro duplo.

O processo é exatamente o mesmo que fizemos para o escudo. Crie uma nova cena herdada, salve com o nome que quiser, altere a textura do sprite, desassocie o script base e associe um novo, extendendo a classe Power-Up criando também uma variável do tempo desse tiro duplo e sua chamada na função de aplicar os poderes.

Seu script ficará desta forma:

extends Powerup

@export var doubleShootTime : float = 5.0

func apply_power_up(player):
	player.apply_double_shoot(doubleShootTime)

O último poder que vamos criar será a recuperação de vida, mas não preciso explicar mais uma vez como fazer, né? A diferença é que neste último, a quantidade de vida não vai mudar só por um tempo, então não vamos precisar passar um tempo, mas sim a quantidade de vida que será recuperada.

O script de recuperação de vida ficará desta forma:

extends Powerup

@export var recoveryPoints : int = 3

func apply_power_up(player):
	player.recover_health(recoveryPoints)

E nossos Power-Ups estão prontos... Quer dizer... Mais ou menos, né? Ainda temos que ajustar o script do Player para disparar as funções que chamamos nos scripts de Power-Up. São três, conforme abaixo:

  • apply_shield: Ativa o escudo por um determinado tempo;
  • apply_double_shoot: Ativa o tiro duplo por um determinado tempo;
  • recover_health: Recupera uma quantidade de saúde do jogador.

Mas fora estas funções no script, também temos que fazer ajustes na cena. Precisamos incluir:

  • O sprite para o escudo,
  • Um segundo ponto de disparo (na verdade vamos incluir dois pontos novos),
  • Timers para controlar o tempo do escudo e do tiro duplo,
  • Uma variável que define a vida do jogador,

E fora isso:

  • Ajustar nosso código para não receber dano quando o escudo estiver ativo,
  • Fazer ajustes nos scripts dos inimigos fazendo com que a colisão com inimigos não mate o jogador diretamente e sim diminua sua vida.

É tanta coisa que daria pra fazer um post só para estes ajustes, hein?

Começando com o escudo, vamos incluir uma área 2D que terá como filhos um Sprite2D, um CollisionShape2D e um Timer. Vou renomear a Area2D para Shield e o Timer para ShieldTimer. Selecione um shape para seu CollisionShape que cubra o escudo como um todo, no meu caso usei um shape redondo.

Escudo criado e incluído na cena do Player.

No script do player, fizemos alguns dos ajustes indicados lá em cima e ficamos com os seguintes ajustes no código:

...
@export var health = 10

...
@onready var shield = $Shield
@onready var shield_timer = $Shield/ShieldTimer

...
var is_shield_on = false

func _ready():
	shield.process_mode = Node.PROCESS_MODE_DISABLED
	shield.visible = false

...
func apply_shield(shieldTime):
	is_shield_on = true
	shield.process_mode = Node.PROCESS_MODE_ALWAYS
	shield.visible = true
	shield_timer.wait_time = shieldTime
	shield_timer.start()
	
func apply_damage(damage):
	if !is_shield_on:
		health -= damage
		if health <= 0:
			die()

...
func _on_shield_timer_timeout():
	shield.process_mode = Node.PROCESS_MODE_DISABLED
	shield.visible = false
	is_shield_on = false

Note que criamos uma variável de health controlável pelo editor, duas variáveis referenciais aos nodes de Shield e ShieldTimer e uma variável boleana para indicar se o escudo está ou não ativo, incluímos a função ready na qual desabilitamos e ocultamos o escudo logo no início do jogo, criamos uma função apply_damage que será chamada à partir do script do inimigo ao invés da função die, criamos a função apply_shield que ficará responsável por ativar e tornar visível o escudo e também ativar o timer e vinculamos o sinal de timeout do timer para controlar o desligamento do escudo.

No editor estes ajustes todos vão ficar assim:

Ajustes no script do Player para o funcionamento do escudo
💡
Interessante:
Para que a tela de script seja apresentada separadamente do restante do editor, como apresentado na imagem acima, basta ao usuário clicar no ícone de "duas janelas" que fica no canto superior direito da área de script. Isso é bastante interessante para quem tem 2 monitores.
Adicionalmente, para apresentar somente os ítens relevantes de alteração, ocultei o conteúdo das funções que não foram alteradas, clicando na "seta de agrupamento" ao lado da função à ocultar.

Agora um ajuste pontual no script do Inimigo para que ao invés de matar diretamente o jogador ele chame a função de aplicar dano:

...
@export var collision_damage = 2

...
func _on_body_entered(body):
	if body is Player:
		body.apply_damage(collision_damage)
		die()

Basicamente criamos uma variável controlável pelo editor para indicar qual o dano de colisão (assim podemos definir valores diferentes para diferentes inimigos) e ao invés de chamar a função die do player, vamos chamar a função apply_damage passando qual o valor de dano que queremos gerar ao jogador.

Script do inimigo com os ajustes para chamada da função de aplicar dano ao Player
💡
Atenção:
Lembre-se que ao alterar o script do inimigo, é necessário revisar as cenas herdadas para garantir que os valores que você quer aplicar permaneçam corretos.

Só com o Power-Up de escudo já tomamos um tempão, né? Vamos fazer o seguinte: vou deixar você pensar como deveríamos fazer para os outros Power-Up's e na próxima semana a gente continua esse papo, que acha?

Se tá gostando desse tutorial, não se esqueça de deixar um comentário aí embaixo, isso vai fazer com que a gente traga sempre coisas novas e intressantes para você.