Verwenden von KinematicBody2D

Einführung

Godot bietet mehrere Kollisionsobjekte an, um sowohl Kollisionserkennung als auch Reaktion zu ermöglichen. Der Versuch, zu entscheiden, welches für Ihr Projekt verwendet werden soll, kann verwirrend sein. Sie können Probleme vermeiden und die Entwicklung vereinfachen, wenn Sie verstehen, wie sie funktionieren und welche Vor- und Nachteile sie haben. In dieser Anleitung sehen wir uns den KinematicBody2D Node an und zeigen einige Beispiele für dessen Verwendung.

Bemerkung

In diesem Dokument wird davon ausgegangen, dass Sie mit den verschiedenen physikalischen Körpern von Godot vertraut sind. Bitte lesen Sie zuerst Physik Einleitung.

Was ist ein beweglicher Körper?

KinematicBody2D dient zur Implementierung von Körpern, die über Code gesteuert werden. Kinematische Körper erkennen Kollisionen mit anderen Körpern, wenn sie sich bewegen, werden jedoch nicht von physikalischen Eigenschaften der Engine wie Schwerkraft oder Reibung beeinflusst. Dies bedeutet, dass Sie Code schreiben müssen, um ihr Verhalten zu erstellen, aber Sie haben auch eine genauere Kontrolle darüber, wie sie sich bewegen und reagieren.

Tipp

Ein KinematicBody2D kann durch die Schwerkraft und andere Kräfte beeinflusst werden, aber Sie müssen die Bewegung im Code berechnen. Die Physik-Engine bewegt keinen KinematicBody2D.

Bewegung und Kollisionen

Wenn Sie einen KinematicBody2D verschieben, sollten Sie seine position-Eigenschaft nicht direkt festlegen. Stattdessen verwenden Sie die Methoden move_and_collide() or move_and_slide(). Diese Methoden bewegen den Körper entlang eines bestimmten Vektors und stoppen sofort, wenn eine Kollision mit einem anderen Körper erkannt wird. Nachdem ein KinematicBody2D kollidiert ist, muss jede Kollisionsantwort manuell codiert werden.

Warnung

Sie sollten kinematische Körperbewegungen nur in der _physics_process() Funktion ausführen.

Die beiden Bewegungsmethoden dienen unterschiedlichen Zwecken. Später in dieser Anleitung sehen Sie Beispiele für deren Funktionsweise.

move_and_collide

Diese Methode verwendet einen Parameter: a Vector2, der die relative Bewegung des Körpers angibt. In der Regel ist dies Ihr Geschwindigkeitsvektor multipliziert mit dem Frame-Zeitschritt (Delta). Wenn die Engine irgendwo entlang dieses Vektors eine Kollision feststellt, hört der Körper sofort auf sich zu bewegen. In diesem Fall gibt die Methode ein KinematicCollision2D Objekt zurück.

KinematicCollision2D ist ein Objekt, das Daten über die Kollision und das kollidierende Objekt enthält. Mit diesen Daten können Sie Ihre Kollisionsantwort berechnen.

move_and_slide

Die Methode move_and_slide() soll die Kollisionantwort in dem allgemeinen Fall vereinfachen, in dem ein Körper entlang des anderen gleiten soll. Es ist zum Beispiel besonders nützlich bei Plattformspielen oder Top-Down-Spielen.

Tipp

move_and_slide() berechnet automatisch die Frame-basierte Bewegung mit delta. Multiplizieren Sie Ihren Geschwindigkeitsvektor nicht mit delta, bevor Sie ihn an move_and_slide() übergeben.

Zusätzlich zum Geschwindigkeitsvektor verwendet move_and_slide() eine Reihe weiterer Parameter, mit denen Sie das Gleitverhalten anpassen können:

  • up_direction - Standardwert: Vector2( 0, 0 )

    Mit diesem Parameter können Sie festlegen, welche Oberflächen die Engine als Boden betrachten soll. Wenn Sie dies einstellen, können Sie die Methoden is_on_floor(), is_on_wall() und is_on_ceiling() verwenden um festzustellen, mit welcher Art von Oberfläche der Körper in Kontakt steht. Der Standardwert bedeutet, dass alle Oberflächen als Wände betrachtet werden.

  • stop_on_slope - Standardwert: false

    Dieser Parameter verhindert, dass ein Körper im Stillstand die Hänge hinunterrutscht.

  • max_slides - Standardwert: 4

    Dieser Parameter gibt die maximale Anzahl von Kollisionen an, bevor der Körper aufhört sich zu bewegen. Wenn Sie es zu niedrig einstellen, kann dies die Bewegung vollständig verhindern.

  • floor_max_angle - Standardwert: 0.785398 (in Radiant, entspricht 45 Grad)

    Dieser Parameter ist der maximale Winkel, bevor eine Oberfläche nicht mehr als "Boden" betrachtet wird.

  • infinite_inertia - Standardwert: true

Wenn dieser Parameter true ist, kann der Körper RigidBody2D-Nodes verschieben (wobei deren Masse ignoriert wird), jedoch keine Kollisionen mit ihnen erkennen. Wenn es false ist, kollidiert der Körper mit starren Körpern und stoppt.

move_and_slide_with_snap

Diese Methode fügt move_and_slide() einige zusätzliche Funktionen hinzu, indem der Parameter snap hinzugefügt wird. Solange dieser Vektor mit dem Boden in Kontakt steht, bleibt der Körper an der Oberfläche haften. Beachten Sie, dass Sie daher das Einrasten deaktivieren müssen, beispielsweise beim Springen. Sie können hierfür entweder snap auf Vector2.ZERO setzen oder stattdessen move_and_slide() verwenden.

Kollisionen erkennen

Bei Verwendung von move_and_collide() gibt die Funktion direkt eine KinematicCollision2D zurück, die Sie in Ihrem Code verwenden können.

Bei Verwendung von move_and_slide() können mehrere Kollisionen auftreten, wenn die Antwort berechnet wird. Verwenden Sie zum Verarbeiten dieser Kollisionen get_slide_count() und get_slide_collision():

# Using move_and_collide.
var collision = move_and_collide(velocity * delta)
if collision:
    print("I collided with ", collision.collider.name)

# Using move_and_slide.
velocity = move_and_slide(velocity)
for i in get_slide_count():
    var collision = get_slide_collision(i)
    print("I collided with ", collision.collider.name)

Bemerkung

get_slide_count() only counts times the body has collided and changed direction.

Siehe KinematicCollision2D für Details darüber, welche Kollisionsdaten zurückgegeben werden.

Welche Bewegungsmethode soll verwendet werden?

Eine häufige Frage von neuen Godot-Benutzern lautet: "Wie entscheiden Sie, welche Bewegungsfunktion verwendet werden soll?" Oft lautet die Antwort move_and_slide() weil es "einfacher" ist, aber dies ist nicht unbedingt der Fall. Eine Möglichkeit sich das vorzustellen ist, dass move_and_slide() ein Sonderfall und move_and_collide() allgemeiner ist. Die folgenden zwei Codefragmente führen beispielsweise zu derselben Kollisionsantwort:

../../_images/k2d_compare.gif
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
    velocity = velocity.slide(collision.normal)

# using move_and_slide
velocity = move_and_slide(velocity)
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
    velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);

Alles was Sie mit move_and_slide() tun, kann auch mit move_and_collide() ausgeführt werden, es kann jedoch etwas mehr Code erforderlich sein. Wie wir in den folgenden Beispielen sehen werden gibt es Fälle, in denen move_and_slide() nicht die gewünschte Antwort liefert.

Im obigen Beispiel weisen wir die Geschwindigkeit zu, die move_and_slide() in die Variable velocity übergibt. Dies liegt daran, dass die Funktion die Geschwindigkeit intern neu berechnet, wenn das Zeichen mit der Umgebung kollidiert, um die Verlangsamung widerzuspiegeln.

Wenn Ihr Charakter beispielsweise auf den Boden gefallen ist, möchten Sie nicht, dass er aufgrund der Schwerkraft eine vertikale Geschwindigkeit erreicht. Stattdessen soll die vertikale Geschwindigkeit auf Null zurückgesetzt werden.

move_and_slide() kann die Geschwindigkeit des kinematischen Körpers auch mehrmals in einer Schleife neu berechnen, da er den Charakter bewegt und standardmäßig bis zu fünf Mal kollidiert, um eine gleichmäßige Bewegung zu erzeugen. Am Ende des Prozesses gibt die Funktion die neue Geschwindigkeit des Charakters zurück, die wir in unserer Variablen velocity speichern und im nächsten Frame verwenden können.

Beispiele

Um diese Beispiele in Aktion zu sehen, laden Sie das Beispielprojekt herunter: using_kinematic2d.zip.

Bewegung und Wände

Wenn Sie das Beispielprojekt heruntergeladen haben, befindet sich dieses Beispiel in "BasicMovement.tscn".

Fügen Sie in diesem Beispiel einen KinematicBody2D mit zwei untergeordneten Elementen hinzu: ein Sprite und ein CollisionShape2D. Verwenden Sie das Godot "icon.png" als Sprite-Textur (ziehen Sie es aus dem Dateisystem-Dock in die Textur-Eigenschaft des Sprite). Wählen Sie in der Eigenschaft Shape von CollisionShape2D die Option "NewRectangleShape2D" aus und passen Sie das Rechteck so an, dass es über das Sprite-Bild passt.

Bemerkung

Beispiele für die Implementierung von 2D-Bewegungsmuster finden Sie unter 2D Bewegungsübersicht.

Hängen Sie ein Skript an KinematicBody2D an und fügen Sie den folgenden Code hinzu:

extends KinematicBody2D

var speed = 250
var velocity = Vector2()

func get_input():
    # Detect up/down/left/right keystate and only move when pressed.
    velocity = Vector2()
    if Input.is_action_pressed('ui_right'):
        velocity.x += 1
    if Input.is_action_pressed('ui_left'):
        velocity.x -= 1
    if Input.is_action_pressed('ui_down'):
        velocity.y += 1
    if Input.is_action_pressed('ui_up'):
        velocity.y -= 1
    velocity = velocity.normalized() * speed

func _physics_process(delta):
    get_input()
    move_and_collide(velocity * delta)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    public int Speed = 250;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // Detect up/down/left/right keystate and only move when pressed
        _velocity = new Vector2();

        if (Input.IsActionPressed("ui_right"))
            _velocity.x += 1;

        if (Input.IsActionPressed("ui_left"))
            _velocity.x -= 1;

        if (Input.IsActionPressed("ui_down"))
            _velocity.y += 1;

        if (Input.IsActionPressed("ui_up"))
            _velocity.y -= 1;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        MoveAndCollide(_velocity * delta);
    }
}

Wenn Sie diese Szene ausführen, werden Sie sehen, dass move_and_collide() wie erwartet funktioniert und den Körper entlang des Geschwindigkeitsvektors bewegt. Nun wollen wir sehen was passiert, wenn Sie einige Hindernisse hinzufügen. Fügen Sie ein StaticBody2D mit einer rechteckigen Kollisionsform hinzu. Zur besseren Sichtbarkeit können Sie ein Sprite oder ein Polygon2D verwenden oder "Sichtbare Kollisionsformen" im Menü "Debuggen" aktivieren.

Führen Sie die Szene erneut aus und versuchen Sie, sich in das Hindernis zu bewegen. Sie werden sehen, dass der KinematicBody2D das Hindernis nicht durchdringen kann. Wenn Sie jedoch versuchen sich schräg in das Hindernis zu bewegen, werden Sie feststellen, dass das Hindernis wie Klebstoff wirkt - es fühlt sich an, als würde der Körper stecken bleiben.

Dies geschieht, weil es keine Kollisionsantwort gibt. move_and_collide() stoppt die Bewegung des Körpers, wenn eine Kollision auftritt. Wir müssen jede gewünschte Antwort auf die Kollision codieren.

Versuchen Sie die Funktion in "move_and_slide (velocity)" zu ändern und erneut auszuführen. Beachten Sie, dass wir delta aus der Geschwindigkeitsberechnung entfernt haben.

move_and_slide() bietet eine Standardkollisionsantwort, bei der der Körper entlang des Kollisionsobjekts verschoben wird. Dies ist nützlich für sehr viele Spieltypen und kann schon alles sein was Sie benötigen, um das gewünschte Verhalten zu erreichen.

Abprallen/Reflektieren

Was ist, wenn Sie keine gleitende Kollisionsreaktion wünschen? In diesem Beispiel ("BounceandCollide.tscn" im Beispielprojekt) haben wir einen Charakter, der Kugeln abschießt und wir möchten, dass die Kugeln von den Wänden abprallen.

In diesem Beispiel werden drei Szenen verwendet. Die Hauptszene enthält den Spieler und die Wände. Die Kugel und die Wand sind separate Szenen, damit sie instanziiert werden können.

Der Spieler wird über die Tasten w und s für Vorwärts und Rückwärts gesteuert. Zum Zielen wird der Mauszeiger verwendet. Hier ist der Code für den Spieler mit move_and_slide():

extends KinematicBody2D

var Bullet = preload("res://Bullet.tscn")
var speed = 200
var velocity = Vector2()

func get_input():
    # Add these actions in Project Settings -> Input Map.
    velocity = Vector2()
    if Input.is_action_pressed('backward'):
        velocity = Vector2(-speed/3, 0).rotated(rotation)
    if Input.is_action_pressed('forward'):
        velocity = Vector2(speed, 0).rotated(rotation)
    if Input.is_action_just_pressed('mouse_click'):
        shoot()

func shoot():
    # "Muzzle" is a Position2D placed at the barrel of the gun.
    var b = Bullet.instance()
    b.start($Muzzle.global_position, rotation)
    get_parent().add_child(b)

func _physics_process(delta):
    get_input()
    var dir = get_global_mouse_position() - global_position
    # Don't move if too close to the mouse pointer.
    if dir.length() > 5:
        rotation = dir.angle()
        velocity = move_and_slide(velocity)
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
    public int Speed = 200;
    private Vector2 _velocity = new Vector2();

    public void GetInput()
    {
        // add these actions in Project Settings -> Input Map
        _velocity = new Vector2();
        if (Input.IsActionPressed("backward"))
        {
            _velocity = new Vector2(-Speed/3, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("forward"))
        {
            _velocity = new Vector2(Speed, 0).Rotated(Rotation);
        }
        if (Input.IsActionPressed("mouse_click"))
        {
            Shoot();
        }
    }

    public void Shoot()
    {
        // "Muzzle" is a Position2D placed at the barrel of the gun
        var b = (Bullet)_bullet.Instance();
        b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
        GetParent().AddChild(b);
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        var dir = GetGlobalMousePosition() - GlobalPosition;
        // Don't move if too close to the mouse pointer
        if (dir.Length() > 5)
        {
            Rotation = dir.Angle();
            _velocity = MoveAndSlide(_velocity);
        }
    }
}

Und der Code für das Geschoss:

extends KinematicBody2D

var speed = 750
var velocity = Vector2()

func start(pos, dir):
    rotation = dir
    position = pos
    velocity = Vector2(speed, 0).rotated(rotation)

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        velocity = velocity.bounce(collision.normal)
        if collision.collider.has_method("hit"):
            collision.collider.hit()

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()
using Godot;
using System;

public class Bullet : KinematicBody2D
{
    public int Speed = 750;
    private Vector2 _velocity = new Vector2();

    public void Start(Vector2 pos, float dir)
    {
        Rotation = dir;
        Position = pos;
        _velocity = new Vector2(speed, 0).Rotated(Rotation);
    }

    public override void _PhysicsProcess(float delta)
    {
        var collision = MoveAndCollide(_velocity * delta);
        if (collision != null)
        {
            _velocity = _velocity.Bounce(collision.Normal);
            if (collision.Collider.HasMethod("Hit"))
            {
                collision.Collider.Call("Hit");
            }
        }
    }

    public void OnVisibilityNotifier2DScreenExited()
    {
        QueueFree();
    }
}

Die Aktion erfolgt in _physics_process(). Nach Verwendung von move_and_collide() wird bei einer Kollision ein KinematicCollision2D-Objekt zurückgegeben (andernfalls lautet die Rückgabe Nil).

Wenn es eine zurückgegebene Kollision gibt, verwenden wir das normal der Kollision, um die Geschwindigkeit velocity des Geschosses mit der Vector2.bounce()-Methode widerzuspiegeln.

Wenn das kollidierende Objekt (collider) eine hit Methode hat, rufen wir es auch auf. Im Beispielprojekt haben wir der Wand einen blinkenden Farbeffekt hinzugefügt, um dies zu demonstrieren.

../../_images/k2d_bullet_bounce.gif

Plattformer Bewegungen

Versuchen wir ein weiteres beliebtes Beispiel: den 2D-Plattformer. move_and_slide() ist ideal um schnell einen funktionierenden Charakter-Controller zum Laufen zu bringen. Wenn Sie das Beispielprojekt heruntergeladen haben, finden Sie es in "Platformer.tscn".

In diesem Beispiel wird davon ausgegangen, dass Sie eine Ebene aus StaticBody2D-Objekten haben. Sie können jede Form und Größe haben. Im Beispielprojekt verwenden wir Polygon2D um die Plattformformen zu erstellen.

Hier ist der Code für den Spielerkörper:

extends KinematicBody2D

export (int) var run_speed = 100
export (int) var jump_speed = -400
export (int) var gravity = 1200

var velocity = Vector2()
var jumping = false

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if jump and is_on_floor():
        jumping = true
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    get_input()
    velocity.y += gravity * delta
    if jumping and is_on_floor():
        jumping = false
    velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;

public class KBExample : KinematicBody2D
{
    [Export] public int RunSpeed = 100;
    [Export] public int JumpSpeed = -400;
    [Export] public int Gravity = 1200;

    Vector2 velocity = new Vector2();
    bool jumping = false;

    public void GetInput()
    {
        velocity.x = 0;
        bool right = Input.IsActionPressed("ui_right");
        bool left = Input.IsActionPressed("ui_left");
        bool jump = Input.IsActionPressed("ui_select");

        if (jump && IsOnFloor())
        {
            jumping = true;
            velocity.y = JumpSpeed;
        }

        if (right)
            velocity.x += RunSpeed;
        if (left)
            velocity.x -= RunSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        GetInput();
        velocity.y += Gravity * delta;
        if (jumping && IsOnFloor())
            jumping = false;
        velocity = MoveAndSlide(velocity, new Vector2(0, -1));
    }
}
../../_images/k2d_platform.gif

Bei Verwendung von move_and_slide() gibt die Funktion einen Vektor zurück, der die Bewegung darstellt, die nach dem Auftreten der Gleit-Kollision verblieben ist. Wenn Sie diesen Wert auf die Geschwindigkeit velocity des Charakters zurücksetzen, können wir uns reibungslos auf und ab bewegen. Versuchen Sie velocity = zu entfernen und sehen Sie was passiert, wenn Sie dies nicht tun.

Beachten Sie auch, dass wir Vector2(0, -1) als Bodennormal hinzugefügt haben. Dieser Vektor zeigt gerade nach oben. Wenn der Charakter mit einem Objekt kollidiert, das diese Normalität aufweist, wird dies als Boden betrachtet.

Die Verwendung der Bodennormalen ermöglicht es uns mittels is_on_floor() zu springen. Diese Funktion gibt erst nach einer Kollision mit move_and_slide() ein true zurück, wenn die Normalen des kollidierenden Körpers innerhalb von 45 Grad des angegebenen Bodenvektors liegen. Sie können den maximalen Winkel steuern, indem Sie floor_max_angle einstellen.

Mit diesem Winkel können Sie auch andere Funktionen wie Wandsprünge implementieren, indem Sie beispielsweise is_on_wall() verwenden.