Den Spieler coden¶
In dieser Lektion fügen wir Spielerbewegungen und Animationen hinzu und richten es so ein, dass es Kollisionen erkennt.
Jetzt müssen wir einige Funktionen hinzufügen, die wir von einen integrierten Node nicht enthalten sind, also fügen wir ein Skript hinzu. Klicken Sie auf den Node Player
und dann auf die Schaltfläche "Skript hinzufügen":
Im Fenster mit den Skripteinstellungen können Sie die Standardeinstellungen beibehalten. Klicken Sie einfach auf "Erstellen":
Bemerkung
Wenn Sie ein C#-Skript oder andere Sprachen verwenden wollen, wählen Sie die Sprache aus dem Auswahlmenü "Sprache", bevor Sie auf Erstellen klicken.
Bemerkung
Wenn du noch nie etwas mit GDScript zu tun hattest, lese bitte zuerst Skriptsprachen.
Beginnen Sie, indem Sie die Member-Variablen deklarieren, die dieses Objekt benötigt:
extends Area2D
export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
Wenn Sie das Schlüsselwort export
für die erste Variable speed
verwenden, ermöglicht es Ihnen dessen Wert im Inspektor einzustellen. Dies kann für Werte nützlich sein, die Sie wie die integrierten Eigenschaften eines Nodes anpassen möchten. Klicken Sie auf den Node Player
, und die Eigenschaft wird jetzt im Bereich "Script Variables" des Inspektors angezeigt. Denken Sie daran, wenn Sie den Wert hier ändern, wird der im Skript verwendete Wert überschrieben.
Warnung
Wenn Sie C# verwenden, müssen Sie die Projekt-Bausteine neu übersetzen, um neue Exportvariablen oder Signale sichtbar zu machen. Das kann manuell durch einen Klick auf das Wort "Mono" im unteren Fensterbereich geschehen, es wird das Mono-Panel eingeblendet und ein anschließender Klick auf "Build Project" führt die Aktion aus.
Die Funktion _ready()
wird aufgerufen, wenn ein Node in den Szenenbaum eintritt, was ein guter Zeitpunkt ist, um die Größe des Spielfensters zu ermitteln:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().size;
}
Jetzt können wir die Funktion _process()
verwenden, um festzulegen, was der Spieler tun soll. _process()
wird mit jedem Frame aufgerufen, deshalb werden wir es verwenden, um Elemente unseres Spiels zu aktualisieren, von denen wir erwarten, dass sie sich häufig ändern werden. Für den Spieler müssen wir Folgendes tun:
Auf Eingabe prüfen.
In die angegebene Richtung bewegen.
Die entsprechende Animation abspielen.
Zuerst müssen wir auf Eingaben prüfen - drückt der Spieler eine Taste? Für dieses Spiel haben wir 4 Richtungseingaben zu überprüfen. Eingabeaktionen werden in den Projekteinstellungen unter "Eingabe-Zuordnung" definiert. Sie können benutzerdefinierte Ereignisse definieren und ihnen verschiedene Tasten, Mausereignisse oder andere Eingaben zuweisen. Für diese Demo werden wir die Standardereignisse verwenden, die den Pfeiltasten auf der Tastatur zugeordnet sind.
Klicke auf Projekt -> Projekteinstellungen, um die Projekteinstellungen zu öffnen und wechsle dann zum Reiter Eingabe-Zuordnung. Schreibe "move_right" in die obere Leiste und betätige dann den Knopf "Hinzufügen", um die Aktion move_right
hinzuzufügen.
Wir müssen eine Taste zu der Aktion hinzufügen, indem wir auf rechts das "+" Symbol klicken und dann "Taste" aus dem Menü auswählen. In den Dialog wird dann die entsprechende Taste eingegeben. Drücken Sie den Pfeil nach rechts auf der Tastatur und bestätigen dann mit einem Klick auf "Ok".
Wiederholen Sie diese Schritte, um drei weitere Aktionen hinzuzufügen:
move_left
für den Pfeil nach links.move_up
für den Pfeil nach oben.Und
move_down
für den Pfeil nach unten.
Die Registerkarte "Eingabe-Zuordnung" sollte wie folgt aussehen:
Klicken Sie auf die Schaltfläche "Schließen", um die Projekteinstellungen zu schließen.
Bemerkung
Wir haben jeder Eingabeaktion nur eine Taste zugewiesen, aber Sie können mehrere Tasten, Joystick-Tasten oder Maustasten derselben Eingabeaktion zuweisen.
Sie können herausfinden, ob eine Taste gedrückt wird, indem Sie Input.is_action_pressed()
benutzen, welches true
zurückgibt, wenn eine Taste gedrückt wird und false
wenn nicht.
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed("move_right"):
velocity.x += 1
if Input.is_action_pressed("move_left"):
velocity.x -= 1
if Input.is_action_pressed("move_down"):
velocity.y += 1
if Input.is_action_pressed("move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
public override void _Process(float delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.x += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.x -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.y -= 1;
}
var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->stop();
}
}
Wir fangen damit an, die velocity
(Geschwindigkeit) auf (0,0)
zu setzen - standardmäßig sollte sich der Spieler nicht bewegen. Dann überprüfen wir jede Eingabe und addieren/subtrahieren von der velocity
, um eine Gesamtrichtung zu erhalten. Wenn Sie beispielsweise rechts
und runter
gleichzeitig halten, ist der resultierende velocity
-Vektor (1, 1)
. In diesem Fall, da wir eine horizontale und vertikale Bewegung addieren, würde sich der Spieler schneller bewegen, als wenn er sich nur horizontal bewegen würde.
Wir können das verhindern, indem wir die Geschwindigkeit normalisieren, was bedeutet, dass wir ihre Länge auf 1
festlegen, anschließend multiplizieren wir mit der gewünschten Geschwindigkeit. Das sorgt dafür, dass keine schnelle diagonale Bewegung mehr stattfindet.
Tipp
Wenn Sie noch nie zuvor Vektor-Mathematik verwendet haben oder eine Auffrischung benötigen, finden Sie eine Erklärung zur Vektorverwendung in Godot unter Vektor-Mathematik. Es ist gut zu wissen, wird aber für den Rest dieser Anleitung nicht notwendig sein.
Wir überprüfen auch, ob sich der Spieler bewegt, damit wir play()
oder stop()
auf dem Animierten Sprite aufrufen können.
Tipp
$
ist eine Abkürzung für get_node()
. Im obigen Code ist $AnimatedSprite.play()
dasselbe wie get_node("AnimatedSprite").play()
.
In GDScript gibt $
das Node am relativen Pfad vom aktuellen Node zurück oder gibt null
zurück, wenn das Node nicht gefunden wird. Da Animiertes Sprite ein Kind des aktuellen Nodes ist, können wir $AnimatedSprite
verwenden.
Jetzt, da wir eine Bewegungsrichtung haben, können wir die Position des Spielers aktualisieren und mit clamp()
verhindern, dass er den Bildschirm verlässt. clamp()
beschränkt dabei einen Wert auf einen angegebenen Bereich. Am Ende der _process
Funktion fügen wir also folgendes hinzufügen:
position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
Tipp
Der Parameter delta in der Funktion `__process ()`bezieht sich auf die Frame-Länge - die Zeit, die der vorherige Frame für die Fertigstellung benötigt hat. Durch die Verwendung dieses Werts wird sichergestellt, dass Ihre Bewegung auch dann konstant bleibt, wenn sich die Bildrate ändert.
Klicken Sie auf "Szene abspielen" (F6, Cmd + R unter macOS) und stellen Sie sicher, dass der Spieler sich auf dem Bildschirm in alle Richtungen bewegen kann.
Warnung
Wenn ein Fehler im "Debugger" Bereich auftaucht, der sagt
Attempt to call function 'play' in base 'null instance' on a null
instance
Dies bedeutet wahrscheinlich, dass Sie den Namen des Animierten Sprite-Nodes falsch geschrieben haben. Bei Nodenamen wird zwischen Groß- und Kleinschreibung unterschieden und $NodeName
muss mit dem Namen übereinstimmen, den Sie im Szenenbaum sehen.
Animationen¶
Da sich der Spieler nun bewegen kann, müssen wir die Animation, die das Animierte Sprite abspielt, entsprechend seiner Richtung ändern. Wir haben die "walk"-Animation, die den Spieler zeigt, wie er nach rechts geht. Diese Animation sollte horizontal gespiegelt werden, indem die Eigenschaft flip_h
für links verwendet wird. Wir haben auch die "up"-Animation, die vertikal mit flip_v
für die Abwärtsbewegung gespiegelt werden sollte. Wir fügen diesen Code am Ende der Funktion _process()
ein:
if velocity.x != 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_v = false
# See the note below about boolean assignment.
$AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite.animation = "up"
$AnimatedSprite.flip_v = velocity.y > 0
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
Bemerkung
Die booleschen Zuweisungen im obigen Code sind eine gebräuchliche Abkürzung für Programmierer. Da wir einen Vergleichstest (boolescher Wert) durchführen und auch einen booleschen Wert zuweisen, können wir beide gleichzeitig durchführen. Betrachten Sie diesen Code im Vergleich zur obigen einzeiligen booleschen Zuweisung:
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = false;
}
Spielen Sie die Szene erneut ab und überprüfen Sie, ob die Animationen in jeder Richtung korrekt sind.
Tipp
Ein allgemeiner Fehler ist hier, die Namen der Animationen falsch zu benennen. Die Animationsnamen in dem SpriteFrames Bereich müssen mit dem, was im Code geschrieben steht, übereinstimmen. Wenn die Animation "Walk"
heißt, dann muss auch ein großgeschriebenes "W" im Code stehen.
Spielen Sie die Szene noch einmal ab und überprüfen, ob die Animationen in jede Richtung korrekt sind. Wenn Sie sich sicher sind, dass die Bewegung korrekt funktioniert, fügen Sie diese Zeile zu _ready()
hinzu, damit der Spieler zu Beginn des Spiels ausgeblendet wird:
hide()
Hide();
hide();
Vorbereitung auf Kollisionen¶
Wir wollen, dass der Player
erkennt, wann er von einem Feind getroffen wird, aber wir haben uns noch keine Feinde erstellt! Das ist in Ordnung, denn wir werden Godots Signal-Funktionalität nutzen, damit es funktioniert.
Fügen Sie folgendes oben im Skript nach extends Area2D
hinzu:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
Dies definiert ein benutzerdefiniertes Signal namens "hit", das wir von unserem Spieler aussenden (send out) lassen, wenn es mit einem Gegner kollidiert. Wir werden Area2D
verwenden, um die Kollision zu erkennen. Wählen Sie den Node Player
und klicken auf die Registerkarte "Node" neben der Registerkarte Inspektor, um die Liste der Signale zu sehen, die der Spieler ausgeben kann:
Beachten Sie, dass unser benutzerdefiniertes "hit"-Signal auch da ist! Da unsere Feinde RigidBody2D
Nodes sein werden, brauchen wir das Signal body_entered(body: Node)
; dieses wird ausgesendet, wenn ein Körper den Spieler berührt. Klicken Sie auf "Verbinden..." und dann, im "Signal verbinden"-Fenster, wieder auf "Verbinden". Wir müssen keine dieser Einstellungen ändern - Godot erstellt automatisch eine Funktion im Skript des Spielers. Diese Funktion wird immer dann aufgerufen, wenn das Signal ausgelöst wird - sie handhabt das Signal.
Beachten Sie das grüne Symbol, was anzeigt, dass das Signal mit dieser Funktion verbunden ist. Fügen Sie diesen Code zu der Funktion hinzu:
func _on_Player_body_entered(body):
hide() # Player disappears after being hit.
emit_signal("hit")
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred("disabled", true)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("disabled", true);
}
Jedes Mal, wenn ein Feind den Spieler trifft, wird das Signal ausgesendet. Wir müssen die Kollision des Spielers deaktivieren, damit das Treffer
-Signal nicht mehr als einmal ausgelöst wird.
Bemerkung
Das Deaktivieren der Kollisionsform während der Kollisionsberechnung der Engine kann einen Fehler auslösen. Die Verwendung von set_deferred()
weist Godot an, mit dem Deaktivieren der Form bis zu einem sicheren Zeitpunkt zu warten.
Das letzte was wir tun müssen ist eine Funktion hinzuzufügen, die wir aufrufen können, um den Spieler beim erneuten Start des Spiels zurückzusetzen.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
Da der Spieler nun funktioniert, werden wir in der nächsten Lektion an dem Gegner arbeiten.