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.)
Click the Instance button (represented by a chain link icon) and select your saved
Player.tscn
.

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 é geradaTimer (nomeado
ScoreTimer
) - para incrementar a pontuação a cada segundoTimer (nomeado
StartTimer
) - para dar um atraso antes de começarPosition2D (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:

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.

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¶
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
public class Main : Node
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
#pragma warning disable 649
// We assign this in the editor, so we don't need the warning about not being assigned.
[Export]
public PackedScene MobScene;
#pragma warning restore 649
public int Score;
}
// Copy `player.gdns` to `main.gdns` and replace `Player` with `Main`.
// Attach the `main.gdns` file to the Main node.
// Create two files `main.cpp` and `main.hpp` next to `entry.cpp` in `src`.
// This code goes in `main.hpp`. We also define the methods we'll be using here.
#ifndef MAIN_H
#define MAIN_H
#include <AudioStreamPlayer.hpp>
#include <CanvasLayer.hpp>
#include <Godot.hpp>
#include <Node.hpp>
#include <PackedScene.hpp>
#include <PathFollow2D.hpp>
#include <RandomNumberGenerator.hpp>
#include <Timer.hpp>
#include "hud.hpp"
#include "player.hpp"
class Main : public godot::Node {
GODOT_CLASS(Main, godot::Node)
int score;
HUD *_hud;
Player *_player;
godot::Node2D *_start_position;
godot::PathFollow2D *_mob_spawn_location;
godot::Timer *_mob_timer;
godot::Timer *_score_timer;
godot::Timer *_start_timer;
godot::AudioStreamPlayer *_music;
godot::AudioStreamPlayer *_death_sound;
godot::Ref<godot::RandomNumberGenerator> _random;
public:
godot::Ref<godot::PackedScene> mob_scene;
void _init() {}
void _ready();
void game_over();
void new_game();
void _on_MobTimer_timeout();
void _on_ScoreTimer_timeout();
void _on_StartTimer_timeout();
static void _register_methods();
};
#endif // MAIN_H
We also add a call to randomize()
here so that the random number
generator generates different random numbers each time the game is run:
func _ready():
randomize()
public override void _Ready()
{
GD.Randomize();
}
// This code goes in `main.cpp`.
#include "main.hpp"
#include <SceneTree.hpp>
#include "mob.hpp"
void Main::_ready() {
_hud = get_node<HUD>("HUD");
_player = get_node<Player>("Player");
_start_position = get_node<godot::Node2D>("StartPosition");
_mob_spawn_location = get_node<godot::PathFollow2D>("MobPath/MobSpawnLocation");
_mob_timer = get_node<godot::Timer>("MobTimer");
_score_timer = get_node<godot::Timer>("ScoreTimer");
_start_timer = get_node<godot::Timer>("StartTimer");
// Uncomment these after adding the nodes in the "Sound effects" section of "Finishing up".
//_music = get_node<godot::AudioStreamPlayer>("Music");
//_death_sound = get_node<godot::AudioStreamPlayer>("DeathSound");
_random = (godot::Ref<godot::RandomNumberGenerator>)godot::RandomNumberGenerator::_new();
_random->randomize();
}
Click the Main
node and you will see the Mob Scene
property in the Inspector
under "Script Variables".
Você pode atribuir o valor dessa propriedade de duas formas:
Drag
Mob.tscn
from the "FileSystem" dock and drop it in the Mob Scene property.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()
public void GameOver()
{
GetNode<Timer>("MobTimer").Stop();
GetNode<Timer>("ScoreTimer").Stop();
}
public void NewGame()
{
Score = 0;
var player = GetNode<Player>("Player");
var startPosition = GetNode<Position2D>("StartPosition");
player.Start(startPosition.Position);
GetNode<Timer>("StartTimer").Start();
}
// This code goes in `main.cpp`.
void Main::game_over() {
_score_timer->stop();
_mob_timer->stop();
}
void Main::new_game() {
score = 0;
_player->start(_start_position->get_position());
_start_timer->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()
public void OnScoreTimerTimeout()
{
Score++;
}
public void OnStartTimerTimeout()
{
GetNode<Timer>("MobTimer").Start();
GetNode<Timer>("ScoreTimer").Start();
}
// This code goes in `main.cpp`.
void Main::_on_ScoreTimer_timeout() {
score += 1;
}
void Main::_on_StartTimer_timeout() {
_mob_timer->start();
_score_timer->start();
}
// Also add this to register all methods and the mob scene property.
void Main::_register_methods() {
godot::register_method("_ready", &Main::_ready);
godot::register_method("game_over", &Main::game_over);
godot::register_method("new_game", &Main::new_game);
godot::register_method("_on_MobTimer_timeout", &Main::_on_MobTimer_timeout);
godot::register_method("_on_ScoreTimer_timeout", &Main::_on_ScoreTimer_timeout);
godot::register_method("_on_StartTimer_timeout", &Main::_on_StartTimer_timeout);
godot::register_property("mob_scene", &Main::mob_scene, (godot::Ref<godot::PackedScene>)nullptr);
}
In _on_MobTimer_timeout()
, we will create a mob instance, pick a random
starting location along the Path2D
, and set the mob in motion. The
PathFollow2D
node will automatically rotate as it follows the path, so we
will use that to select the mob's direction as well as its position.
When we spawn a mob, we'll pick a random value between 150.0
and
250.0
for how fast each mob will move (it would be boring if they were
all moving at the same speed).
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)
public void OnMobTimerTimeout()
{
// Note: Normally it is best to use explicit types rather than the `var`
// keyword. However, var is acceptable to use here because the types are
// obviously Mob and PathFollow2D, since they appear later on the line.
// Create a new instance of the Mob scene.
var mob = (Mob)MobScene.Instance();
// Choose a random location on Path2D.
var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
mobSpawnLocation.Offset = GD.Randi();
// 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);
}
// This code goes in `main.cpp`.
void Main::_on_MobTimer_timeout() {
// Create a new instance of the Mob scene.
godot::Node *mob = mob_scene->instance();
// Choose a random location on Path2D.
_mob_spawn_location->set_offset((real_t)_random->randi());
// Set the mob's direction perpendicular to the path direction.
real_t direction = _mob_spawn_location->get_rotation() + (real_t)Math_PI / 2;
// Set the mob's position to a random location.
mob->set("position", _mob_spawn_location->get_position());
// Add some randomness to the direction.
direction += _random->randf_range((real_t)-Math_PI / 4, (real_t)Math_PI / 4);
mob->set("rotation", direction);
// Choose the velocity for the mob.
godot::Vector2 velocity = godot::Vector2(_random->randf_range(150.0, 250.0), 0.0);
mob->set("linear_velocity", velocity.rotated(direction));
// Spawn the mob by adding it to the Main scene.
add_child(mob);
}
Importante
Why PI
? In functions requiring angles, Godot uses radians,
not degrees. Pi represents a half turn in radians, about
3.1415
(there is also TAU
which is equal to 2 * PI
).
If you're more comfortable working with degrees, you'll need to
use the deg2rad()
and rad2deg()
functions to convert
between the two.
Testando a cena¶
Let's test the scene to make sure everything is working. Add this new_game
call to _ready()
:
func _ready():
randomize()
new_game()
public override void _Ready()
{
NewGame();
}
// This code goes in `main.cpp`.
void Main::_ready() {
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
If you had already set another scene as the "Main Scene", you can right
click Main.tscn
in the FileSystem dock and select "Set As Main Scene".
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()
.
What's our game lacking? Some user interface. In the next lesson, we'll add a title screen and display the player's score.