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.

A cena principal do jogo

Agora é hora de juntar tudo que fizemos em uma cena jogável.

Crie uma nova cena e adicione um Node chamado Main. (O motivo de estarmos usando Node em vez de Node2D é que esse nó será um contêiner para gerenciar a lógica do jogo. Ele não requer funcionalidades 2D por si só.)

Click the Instantiate button (represented by a chain link icon) and select your saved player.tscn.

../../_images/instance_scene.webp

Agora, adicione os seguintes nós como filhos de Main e nomeie-os conforme mostrado:

  • Timer (nomeado MobTimer) - para controlar a frequência com que a turba é gerada

  • Timer (nomeado ScoreTimer) - para incrementar a pontuação a cada segundo

  • Timer (nomeado StartTimer) - para dar um atraso antes de começar

  • Marker2D (chamado StartPosition) — para indicar a posição inicial do jogador

Defina a propriedade Wait Time de cada um dos nós Timer da seguinte forma (os valores estão em segundos):

  • MobTimer: 0.5

  • ScoreTimer: 1

  • StartTimer: 2

Além disso, configure a propriedade One Shot ("uma só vez") de StartTimer para "Ativo" e Position do nó StartPosition para (240, 450).

Gerando monstros

O nó Main será responsável por gerar novos inimigos, e queremos que eles apareçam em uma posição aleatória na borda da tela. Clique no nó Main no painel Scene e adicione um nó filho Path2D chamado MobPath. Ao selecionar Path2D, você verá alguns novos botões na parte superior do editor:

../../_images/path2d_buttons.webp

Selecione o botão do meio ("Add Point") e desenhe o caminho clicando para adicionar os pontos nos cantos mostrados. Para fazer os pontos se ajustarem à grade, certifique-se de que "Use Grid Snap" e "Use Smart Snap" estejam ambos selecionados. Essas opções podem ser encontradas à esquerda do botão "Lock", aparecendo como um ímã ao lado de alguns pontos e de linhas que se cruzam, respectivamente.

../../_images/grid_snap_button.webp

Importante

Desenhe o caminho em sentido horário, ou sua turba vai surgir apontando para fora em vez de para dentro!

../../_images/draw_path2d.gif

Depois de colocar o ponto 4 na imagem, clique no botão "Fechar Curva", e sua curva estará completa.

Agora que o caminho está definido, adicione um nó PathFollow2D como filho de CaminhoTurba e dê o nome de LocalGeraçãoTurba. Esse nó vai rotacionar automaticamente e seguir o caminho conforme ele se move, para que possamos usá-lo para selecionar uma posição e uma direção aleatória ao longo do caminho.

Sua cena deve se parecer com isso:

../../_images/main_scene_nodes.webp

Script principal

Add a script to Main. At the top of the script, we use @export var mob_scene: PackedScene to allow us to choose the Mob scene we want to instantiate.

extends Node

@export var mob_scene: PackedScene
var score

Clique no nó Main e você verá a propriedade Mob Scene no Inspetor, sob "Main.gd".

Você pode atribuir o valor dessa propriedade de duas formas:

  • Arraste mob.tscn da aba "FileSystem" e solte-o na propriedade Mob Scene.

  • Clique na seta para baixo ao lado de "[empty]" e escolha "Load". Selecione mob.tscn.

Em seguida, selecione a instância da cena Player sob o nó Main na aba Scene, e acesse a aba Signals na barra lateral.

Você deverá ver uma lista dos sinais para o nó Player. Encontre e dê um duplo clique no sinal hit na lista (clique com o botão direito nele e selecione "Connect..."). Isso abrirá a janela de conexão de sinal. Queremos criar uma nova função chamada game_over, que cuidará do que precisa acontecer quando um jogo termina. Digite "game_over" na caixa "Receiver Method" na parte inferior da janela de conexão de sinal e clique em "Connect". O objetivo é fazer com que o sinal hit seja emitido pelo Player e tratado no script do Main. Adicione o seguinte código à nova função, bem como uma função new_game que configurará tudo para um novo jogo:

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()

Agora vamos conectar o sinal timeout() de cada nó Timer (StartTimer, ScoreTimer e MobTimer) ao script principal. Para cada um dos três timers, selecione o timer no painel Cena, abra a aba Sinais no painel Nó e clique duas vezes no sinal timeout() na lista. Isso abrirá uma nova janela de conexão de sinal. As configurações padrão dessa janela devem ser adequadas, então selecione Conectar para criar uma nova conexão de sinal.

Depois que os três timers estiverem configurados, você deverá ver cada timer com uma conexão de Sinal para seu respectivo sinal timeout(), exibida em verde, dentro de suas respectivas abas de Sinais:

  • (Para o MobTimer): _on_mob_timer_timeout()

  • (Para o ScoreTimer): _on_score_timer_timeout()

  • (Para o StartTimer): _on_start_timer_timeout()

Agora definimos como cada um desses timers funciona adicionando o código abaixo. Observe que o StartTimer iniciará os outros dois timers, e que o ScoreTimer incrementará a pontuação em 1.

func _on_score_timer_timeout():
    score += 1

func _on_start_timer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

Em _on_mob_timer_timeout(), criaremos uma instância de inimigo, escolheremos uma posição inicial aleatória ao longo do Path2D e colocaremos o inimigo em movimento. O nó PathFollow2D rotacionará automaticamente enquanto segue o caminho, então usaremos isso para selecionar tanto a direção quanto a posição do inimigo. Ao gerar um inimigo, escolheremos um valor aleatório entre 150.0 e 250.0 para a velocidade de movimento de cada um (seria entediante se todos se movessem na mesma velocidade).

Note que uma nova instância deve ser adicionada à cena usando add_child() (adicionar filho).

func _on_mob_timer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instantiate()

    # Choose a random location on Path2D.
    var mob_spawn_location = $MobPath/MobSpawnLocation
    mob_spawn_location.progress_ratio = randf()

    # Set the mob's position to the random location.
    mob.position = mob_spawn_location.position

    # Set the mob's direction perpendicular to the path direction.
    var direction = mob_spawn_location.rotation + PI / 2

    # Add some randomness to the direction.
    direction += randf_range(-PI / 4, PI / 4)
    mob.rotation = direction

    # Choose the velocity for the mob.
    var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
    mob.linear_velocity = velocity.rotated(direction)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)

Importante

Por que PI? Em funções que exigem ângulos, o Godot utiliza radianos, não graus. Pi representa meia volta em radianos, aproximadamente 3.1415 (também existe TAU, que é igual a 2 * PI). Se você se sente mais confortável trabalhando com graus, precisará usar as funções deg_to_rad() e rad_to_deg() para converter entre os dois.

Testando a cena

Vamos testar a cena para garantir que tudo esta funcionando. Adicione essa chamada new_game{ ao _ready():

func _ready():
    new_game()

Vamos também atribuir a cena Main como nossa "Main Scene" — aquela que roda automaticamente quando o jogo é lançado. Pressione o botão "Play" e selecione main.tscn quando solicitado.

Dica

Se você já definiu outra cena como "Cena Principal", você pode clicar com o botão direito em main.tscn na aba de Arquivos e selecionar "Definir como Cena Principal".

Você deve ser capaz de mover o jogador, ver os inimigos nascendo, e ver o jogador desaparecer quando atingido por um inimigo.

Quando tiver certeza de que tudo está funcionando, remova a chamada para new_game() de _ready() e substitua-a por pass.

O que nos falta? Uma interface de usuário! Na próxima lição, nós adicionaremos uma tela de título e mostraremos a pontuação do jogador.