A cena principal do jogo

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

Crie uma nova cena e adicione um :ref: Node <class_Node> chamado Main. (O motivo para usarmos um Node ao invés deum Node2D é que esse será um recipiente para lidar com a lógica do jogo, por si ele não necessita de funcionalidade 2D.)

Clique no botão Instância (representado por um ícone de elo de corrente) e selecione seu Player.tscn salvo.

../../_images/instance_scene.png

Agora adicione os seguintes nós como filhos de Principal, e os nomeie como mostrado (os valores estão em segundos):

  • 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

  • Position2D (nomeado StartPosition) - para indicar a posição inicial do jogador

Defina a propriedade Wait Time (tempo de espera) para cada um dos nós Timer da seguinte forma:

  • 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ó Principal ficará gerando novos inimigos, e nós queremos que eles apareçam em lugares aleatórios nos cantos da tela. Adicione um nó Path2D chamado CaminhoTurba como filho de Principal. Quando você selecionar Path2D, aparecerão alguns botões novos na parte superior do editor:

../../_images/path2d_buttons.png

Selecione o do meio ("Adicionar Ponto") e desenhe o caminho clicando para adicionar os pontos nos cantos mostrados. Para que os pontos fiquem alinhados à grade, verifique se a opção <b>Encaixar na Grade</b> está marcada. Essa opção pode ser encontrada à esquerda do botão "Travar", parecido como um ímã e ao lado de uma série de 3 pontos verticais.

../../_images/grid_snap_button.png

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.png

Script principal

Adicione um roteiro a Principal. No começo do script, nós usamos export (PackedScene) para permitir-nos escolher a cena Inimigo que queremos instanciar.

extends Node

export(PackedScene) var mob_scene
var score

Também adicionamos uma chamada para randomize() aqui para que o gerador de números aleatórios gere diferentes números aleatórios cada vez que o jogo é executado:

func _ready():
    randomize()

Clique no nó Principal``e você verá a propriedade ``Cena Mob no inspetor, abaixo das Variáveis do Script.

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

  • Arraste Mob.tscn do painel "Sistema de Arquivos" e solte-o na propriedade Cena Mob.

  • Clique na seta para baixo ao lado de "[vazio]" e escolha "Carregar". Selecione Mob.tscn.

Em seguida, selecione o nó do Player no painel Cena, e acesse o nó na barra lateral. Certifique-se de selecionar a aba Sinais no Painel de nós.

Você deve ver uma lista de sinais para o nó Player. Em seguida, encontre e dê dois cliques no sinal hit da lista (ou clique com o botão direito e selecione "Conectar..."). Isso abrirá a caixa de diálogo de conexão de sinal. Queremos criar uma função chamada game_over, que lidará com tudo o que precisa acontecer quando um jogo acaba. Digite "game_over" na caixa "Método no Nó" na parte inferior da janela de conexão de sinal e clique em "Conectar". Adicione o código a seguir à nova função, assim como uma função new_game (novo jogo) para definir tudo para um novo jogo:

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

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

Agora, conecte o sinal timeout() de cada um dos nós de Timer (StartTimer, ScoreTimer , e``MobTimer``) para o script principal. StartTimer irá iniciar os outros dois temporizadores. ScoreTimer irá incrementar a pontuação em 1.

func _on_ScoreTimer_timeout():
    score += 1

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

Em _on_MobTimer_timeout(), criaremos uma instância do mob, escolheremos um local inicial aleatório ao longo do Path2D e colocaremos o inimigo em movimento. O nó PathFollow2D irá girar automaticamente enquanto segue o caminho, então vamos usar isso para selecionar a direção do inimigo, bem como sua posição. Quando geramos um inimigo, vamos escolher um valor aleatório entre 150.0 e 250.0 para quão rápido cada inimigo irá se mover (seria chato se todos estivessem se movendo na mesma velocidade).

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

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

    # Choose a random location on Path2D.
    var mob_spawn_location = get_node("MobPath/MobSpawnLocation")
    mob_spawn_location.offset = randi()

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

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

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

    # Choose the velocity for the mob.
    var velocity = Vector2(rand_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 requerem ângulos, Godot usa radianos, não graus. Pi representa meia volta em radianos, cerca de 3.1415 (existe também TAU que é igual a 2 * PI). Se você estiver mais confortável trabalhando com graus, você precisará usar as funções deg2rad() e rad2deg() 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():
    randomize()
    new_game()

Vamos também atribuir Main como nossa "Cena Principal" - aquela que é executada automaticamente quando o jogo é iniciado. Pressione o botão "Reproduzir" e selecione `` Main.tscn`` quando solicitado.

Dica

Caso você já tenha configurado outra cena como a "Main Scene" (Cena Principal), você pode clicar com o botão direito em ``Main.tscn``no Gerenciador de Arquivos e selecionar "Atribuir 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 que tudo esta funcionando, remova a chamada de new_game() em _ready().

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.