La scène principale du jeu¶
Il est maintenant temps de transformer tout ce que nous avons fait ensemble en une scène de jeu jouable.
Créez une nouvelle scène et ajoutez un noeud Node nommé Main
. (Nous utilisons Node au lieu Node2D est que nous l'utiliserons comme contenant pour gérer la logique du jeu qui ne nécessite pas de gestion 2D en soit.)
Cliquez sur le bouton Instance (représenté par une icône en chaine) et sélectionne votre scène Player.tscn
.

Ajoutez maintenant les nœuds suivants en tant qu'enfants de Main
, et nommez-les comme indiqué (les valeurs sont en secondes) :
Timer (nommé
MobTimer
) - pour contrôler à quelle fréquence les ennemis apparaissentTimer (nommé
ScoreTimer
) - pour incrémenter le score à chaque secondeTimer (nommé
StartTimer
) - pour ajouter un délai avant le débutPosition2D (named
StartPosition
) - pour indiquer la position de départ du joueur
Réglez la propriété Wait Time
de chacun des nœuds Timer
comme suit :
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
En outre, mettez la propriété One Shot
de StartTimer
sur "On" et réglez la Position
du nœud StartPosition
sur (240, 450)
.
Générer des monstres¶
Le nœud principal va générer de nouveaux monstres, et nous voulons qu'ils apparaissent à un endroit aléatoire sur le bord de l'écran. Ajouter un nœud Path2D nommé MobPath
comme un enfant de Main
. Lorsque vous sélectionnez Path2D
, vous verrez de nouveaux boutons en haut de l'éditeur :

Sélectionnez celui du milieu ("Ajouter un point") et tracez le chemin en cliquant pour ajouter les points aux coins montrés. Pour que les points s'accrochent à la grille, assurez-vous que "Utiliser l’aimantation à la grille" et "Utiliser l’aimantation intelligente" sont sélectionnés. Cette option se trouve à gauche du bouton "Verrouiller", apparaissant comme un aimant à côté de lignes qui se croisent.

Important
Tracez le chemin dans le sens des aiguilles d'une montre, ou vos monstres pointeront vers l'extérieur au lieu de vers l'intérieur !

Après avoir placé le point 4
dans l'image, cliquez sur le bouton "Fermer la courbe" et votre courbe sera terminée.
Maintenant que le chemin est défini, ajoutez un nœud PathFollow2D en tant qu'enfant de MobPath
et nommez-le MobSpawnLocation
. Ce nœud tournera automatiquement et suivra le chemin au fur et à mesure qu'il se déplace, de sorte que nous pouvons l'utiliser pour sélectionner une position et une direction aléatoires le long du chemin.
Votre scène devrait ressembler à ceci :

Script principal¶
Ajoutez un script à Main
. Au début du script nous utilisons export (PackedScene)
pour nous permettre de choisir la scène du monstre que nous voulons instancier.
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
Nous ajoutons également un appel à randomize()
ici afin que le générateur de nombres aléatoires génère des nombres aléatoires différents à chaque exécution du jeu :
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();
}
Cliquez sur le nœud Main
et vous verrez la propriété Mob Scene
dans l'inspecteur sous "Script Variables".
Vous pouvez affecter la valeur de cette propriété de deux façons :
Drag
Mob.tscn
from the "FileSystem" dock and drop it in the Mob Scene property.Cliquez sur la flèche vers le bas à côté de "[empty]" et choisissez "Load". Sélectionnez
Mob.tscn
.
Ensuite, sélectionnez le nœud Player
dans le dock Scène, et accédez au dock Nœud dans la barre latérale. Assurez-vous que l'onglet Signaux est sélectionné dans le dock Nœud.
Vous devriez voir une liste des signaux pour le nœud Player
. Trouvez et double-cliquez sur le signal hit
dans la liste (ou faites un clic droit et sélectionnez "Connect..."). Cela ouvrira le dialogue de connexion des signaux. Nous voulons créer une nouvelle fonction appelée game_over
, qui gérera ce qui doit se passer quand une partie se termine. Tapez "game_over" dans la case "Receiver Method" en bas du dialogue de connexion des signaux et cliquez sur "Connect". Ajoutez le code suivant à la nouvelle fonction, ainsi qu'une fonction new_game
qui configurera tout pour une nouvelle partie :
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();
}
Maintenant, connectez le signal timeout()
de chacun des nœuds Timer (StartTimer
, ScoreTimer
et MobTimer
) au script principal. StartTimer
démarrera les deux autres timers. ScoreTimer
incrémentera le score de 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);
}
Dans _on_MobTimer_timeout()
, nous allons créer une instance de monstre, choisir un emplacement de départ aléatoire le long du Path2D
, et mettre le monstre en mouvement. Le nœud PathFollow2D
tournera automatiquement puisqu'il suit le chemin, donc nous l'utiliserons pour sélectionner la direction du monstre ainsi que sa position. Lorsque nous créons un mob, nous choisirons une valeur aléatoire entre 150.0
et 250.0
pour la vitesse de déplacement de chaque mob (ce serait ennuyeux s'ils se déplaçaient tous à la même vitesse).
Notez qu'une nouvelle instance doit être ajoutée à la scène en utilisant add_child()
.
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);
}
Important
Pourquoi PI
? Dans les fonctions nécessitant des angles, Godot utilise des radians et non des degrés. Pi représente un demi-tour en radians, environ 3.1415
(il existe aussi TAU
qui est égal à 2 * PI
). Si vous êtes plus à l'aise avec les degrés, vous devrez utiliser les fonctions deg2rad()
et rad2deg()
pour convertir les angles entre les deux.
Tester la scène¶
Testons la scène pour nous assurer que tout fonctionne. Ajoutez cet appel new_game
à _ready()
:
func _ready():
randomize()
new_game()
public override void _Ready()
{
NewGame();
}
// This code goes in `main.cpp`.
void Main::_ready() {
new_game();
}
Assignons également Main
comme "Main Scene" - celle qui s'exécute automatiquement au lancement du jeu. Appuyez sur le bouton "Play" et sélectionnez Main.tscn
lorsque vous y êtes invité.
Astuce
Si vous avez déjà défini une autre scène comme "Scène Principale", vous pouvez faire du clic-droit sur Main.tscn
dans le dock du système de fichier et sélectionner "Définir comme Scène Principale".
Vous devriez être capable de bouger le joueur, voir les monstres apparaître, et voir le joueur disparaître quand il est touché par un monstre.
Quand vous êtes sûr que tout fonctionne, supprimez l'appel à new_game()
depuis _ready()
.
Que manque-t-il à notre jeu ? Une interface utilisateur. Dans la prochaine leçon, nous ajouterons un écran titre et afficherons le score du joueur.