Die Hauptszene des Spiels¶
Jetzt ist es Zeit, alles, was wir zusammen gemacht haben, in eine spielbare Szene zusammenzuführen.
Erstellen Sie eine neue Szene und fügen einen Node namens Main
hinzu. (Der Grund, warum wir Node anstelle von Node2D verwenden, ist, dass dieser Knoten ein Container für die Handhabung der Spiellogik sein wird. Er benötigt selbst keine 2D-Funktionalität.)
Klicke auf die Instanz Schaltfläche (dargestellt durch ein Kettengliedsymbol) und wähle deinen gespeicherten Player.tscn
aus.

Fügen Sie nun die folgenden Nodes als untergeordnete Elemente von Main
hinzu und benenne sie wie abgebildet (Werte sind in Sekunden):
Timer (genannt
MobTimer
) - um zu steuern, wie oft Gegner erscheinenTimer (genannt
ScoreTimer
) - um die Punktzahl jede Sekunde zu erhöhenTimer (genannt
StartTimer
) - um eine Verzögerung vor dem Start zu gebenPosition2D (genannt
StartPosition
) - um die Startposition des Spielers anzuzeigen
Stellen Sie die Eigenschaft Wait Time
von jedem der Timer
Nodes wie folgt ein:
MobTimer
:0.5
ScoreTimer
:1
StartTimer
:2
Stellen Sie zusätzlich die Eigenschaft One Shot
von StartTimer
auf "An" und die Position
des StartPosition
-Nodes auf (240, 450)
.
Gegner (Mobs) erzeugen¶
Der Main-Node wird neue Gegner hervorbringen, und wir möchten, dass sie an einer beliebigen Stelle am Rande des Bildschirms erscheinen. Fügen Sie einen Path2D <class_Path2D>`Node namens ``MobPath` als Unterobjekt von Main
hinzu. Wenn Sie Path2D
auswählen, sehen Sie oben im Editor einige neue Schaltflächen:

Wählen Sie die Mittlere ("Punkt hinzufügen (in leerem Raum)"). Zeichnen Sie den Pfad, indem Sie durch klicken auf die dargestellten Ecken die Punkte hinzuzufügen. Damit die Punkte am Gitter einrasten, stellen Sie sicher, dass "Gitter-Einrasten benutzen(Shift+G)" aktiviert ist. Diese Option findet sich links vom "Schloss"-Symbol und wird als ein "Magnet neben sich schneidender Linien" dargestellt.

Wichtig
Zeichnen Sie den Pfad im Uhrzeigersinn, sonst erscheinen Ihre Gegner nach außen statt nach innen!

Nachdem Sie den Punkt 4
im Bild platziert haben, klicken Sie auf die Schaltfläche "Kurve schließen" und Ihre Kurve ist vollständig.
Jetzt, nachdem der Pfad definiert ist, fügen Sie einen PathFollow2D Node als Unterobjekt von MobPath
hinzu und nenne ihn MobSpawnLocation
. Dieser Node dreht sich automatisch und folgt dem Pfad, während er sich bewegt, so dass wir damit eine beliebige Position und Richtung entlang des Pfades auswählen können.
Die Szene sollte so aussehen:

Main-Skript¶
Füge ein Skript zu Main
hinzu. Am Anfang des Skripts verwenden wir export (PackedScene)
, damit wir die Mob-Szene auswählen können, die wir als Instanz verwenden wollen.
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
Wir fügen hier außerdem einen Aufruf zu randomize()
ein, damit der Zufallszahlengenerator bei jedem Durchlauf des Spiels andere Zufallszahlen erzeugt:
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();
}
Klicken Sie auf das Main
-Node und Sie werden die Eigenschaft Mob Scene
im Inspektor unter "Script Variables" sehen.
Man kann einen Wert für eine Eigenschaft auf zwei Wege hinzufügen:
Ziehen Sie
Mob.tscn
aus dem "Dateisystem"-Dock und legen Sie es in der Mob Scene Eigenschaft ab.Klicken Sie auf den Pfeil nach unten neben "[leer]" und wählen "Lade". Klicken Sie anschließend auf
Mob.tscn
.
Wählen Sie den „Player“-Node im Szenenbaum aus und klicken dann auf die "Node"-Registerkarte. Als nächstes stellen Sie sicher, dass "Signale" ausgewählt ist.
Sie sollten eine Liste der Signale für den Player
-Node sehen. Suchen und doppelklicken Sie das hit
Signal (oder Rechtsklicke darauf und drücken "Verbinden..."). Dies wird das Fenster für die Signal-Verbindung öffnen. Wir wollen eine neue Funktion namens game_over
erstellen, welche angeben wird, was passieren muss, wenn das Spiel beendet wurde. Schreiben Sie "game_over" in das "Empfängermethode"-Eingabefeld unten im Fenster und klicken auf "Verbinden". Fügen Sie dann sowohl den folgenden Code zur neuen Funktion, als auch eine new_game
-Funktion, die alles für ein neues Spiel einstellen wird:
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();
}
Verbinden Sie nun das Signal Timeout()
von jedem der Timer-Nodes (StartTimer
, ScoreTimer
` und MobTimer
) mit dem Main-Skript. StartTimer
startet die anderen beiden Timer. ScoreTimer
erhöht die Punktzahl um 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()
erstellen wir eine Gegner-Instanz, wählen eine zufällige Startposition entlang des Path2D
und setzen den Gegner in Bewegung. Das PathFollow2D
-Node wird sich automatisch drehen, wenn es dem Pfad folgt, also werden wir es benutzen, um die Richtung des Gegners und seine Position zu bestimmen. Wenn wir einen Gegner erzeugen, wählen wir einen Zufallswert zwischen 150.0
und 250.0
für die Geschwindigkeit, mit der sich jeder Gegner bewegt (es wäre langweilig, wenn sie sich alle mit der gleichen Geschwindigkeit bewegen würden).
Beachten Sie, dass der Szene mit add_child()
eine neue Instanz hinzugefügt werden muss.
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);
}
Wichtig
Warum PI
? Bei Funktionen, die Winkel erfordern, verwendet Godot Bogenmaß, nicht Grad. Pi steht für eine halbe Drehung im Bogenmaß, etwa 3,1415
(es gibt auch TAU
, das gleich 2 * PI
ist). Wenn Sie lieber mit Grad arbeiten, müssen Sie die Funktionen deg2rad()
und rad2deg()
verwenden, um zwischen den beiden zu konvertieren.
Die Szene testen¶
Testen wir die Szene, um sicherzustellen, dass alles funktioniert. Füge diesen new_game
Aufruf zu _ready()
hinzu:
func _ready():
randomize()
new_game()
public override void _Ready()
{
NewGame();
}
// This code goes in `main.cpp`.
void Main::_ready() {
new_game();
}
Lassen Sie uns auch Main
zu unserer "Main-Szene" hinzufügen - die automatisch läuft, wenn das Spiel startet. Drücken Sie dazu den "Start"-Knopf und wählen Main.tscn
im Dialog.
Tipp
Wenn du bereits eine andere Szene als die "Hauptszene" festgelegt hast, kannst du Main.tscn
im Dateisystem Panel rechtsklicken und "Lege als Hauptszene fest" auswählen.
Es sollte jetzt möglich sein den Spieler zu bewegen, erscheinende Gegner zu sehen, und den Spieler verschwinden zu sehen, wenn er von einem Gegner getroffen wird.
Wenn Sie sicher sind, dass alles läuft, löschen Sie den Funktionsaufruf new_game()
von _ready()
.
Was fehlt noch in unserem Spiel? Eine Benutzeroberfläche. In der nächsten Lektion werden ein Titelbild und die Punktzahl anzeigen lassen.