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.
Checking the stable version of the documentation...
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.
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 é geradaTimer (nomeado
ScoreTimer) - para incrementar a pontuação a cada segundoTimer (nomeado
StartTimer) - para dar um atraso antes de começarMarker2D (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.5ScoreTimer:1StartTimer: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:
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.
Importante
Desenhe o caminho em sentido horário, ou sua turba vai surgir apontando para fora em vez de para dentro!
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:
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
using Godot;
public partial class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
[Export]
public PackedScene MobScene { get; set; }
private int _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.tscnda 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()
public void GameOver()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Timer>("ScoreTimer").Stop();
}
public void NewGame()
{
_score = 0;
var player = GetNode<Player>("Player");
var startPosition = GetNode<Marker2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("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()
// We also specified this function name in PascalCase in the editor's connection window.
private void OnScoreTimerTimeout()
{
_score++;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("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)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnMobTimerTimeout()
{
// Create a new instance of the Mob scene.
Mob mob = MobScene.Instantiate<Mob>();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.ProgressRatio = GD.Randf();
// Set the mob's direction perpendicular to the path direction.
float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
// Set the mob's position to a random location.
mob.Position = mobSpawnLocation.Position;
// Add some randomness to the direction.
direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
mob.Rotation = direction;
// Choose the velocity.
var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
mob.LinearVelocity = velocity.Rotated(direction);
// Spawn the mob by adding it to the Main scene.
AddChild(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()
public override void _Ready()
{
NewGame();
}
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.