Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Manuelles Zeichnen in 2D

Einführung

Godot verfügt über Nodes zum Zeichnen von Sprites, Polygonen, Partikeln, Text und vielen anderen gängigen Erfordernissen der Spieleentwicklung. Wenn Sie jedoch etwas Spezielles benötigen, das von den Standard-Nodes nicht abgedeckt wird, können Sie jeden 2D-Node (z. B. Control oder Node2D-basiert) mit benutzerdefinierten Befehlen auf dem Bildschirm zeichnen lassen.

Das manuelle Zeichnen in einem 2D-Node ist wirklich nützlich. Hier einige Beispiele, warum:

  • Zeichnen von Formen oder Logik, die vorhandene Nodes nicht leisten können, z. B. ein Bild mit Trails oder ein spezielles animiertes Polygon.

  • Das Zeichnen einer großen Anzahl einfacher Objekte, z.B. eines Rasters oder eines Spielfelds für ein 2D-Spiel. Das benutzerdefinierte Zeichnen vermeidet den Overhead, der durch die Verwendung einer großen Anzahl von Nodes entsteht, und kann so die Speichernutzung verringern und die Performance verbessern.

  • Erstellen eines benutzerdefinierten UI-Steuerelements. Es gibt eine Vielzahl von Steuerelementen, aber wenn Sie ungewöhnliche Anforderungen haben, benötigen Sie wahrscheinlich ein benutzerdefiniertes Steuerelement.

Zeichnen

Fügen Sie ein Skript zu einem beliebigen von CanvasItem abgeleiteten Node hinzu, wie zum Beispiel Control oder Node2D. Überschreiben Sie dann die Funktion _draw().

extends Node2D

func _draw():
    pass  # Your draw commands here.

Es gibt viele Zeichenbefehle, die alle in der Klassenreferenz CanvasItem beschrieben werden. Es gibt viele davon, und wir werden einige davon in den folgenden Beispielen sehen.

Aktualisieren

Die Funktion _draw wird nur einmal aufgerufen, und dann werden die Zeichenbefehle gecached und vermerkt, so dass weitere Aufrufe unnötig sind.

Wenn ein erneutes Zeichnen erforderlich ist, weil sich eine Variable oder etwas anderes geändert hat, rufen Sie CanvasItem.queue_redraw in demselben Node auf und ein neuer _draw()-Aufruf wird erfolgen.

Hier ist ein etwas komplexeres Beispiel, bei dem wir eine Texturvariable haben, die jederzeit geändert werden kann, und mit einem setter ein Neuzeichnen der Textur erzwingen, wenn sie geändert wird:

extends Node2D

@export var texture : Texture2D:
    set(value):
        texture = value
        queue_redraw()

func _draw():
    draw_texture(texture, Vector2())

Um es in Aktion zu sehen, können Sie die Textur des Godot-Symbols auf dem Editor einstellen, indem Sie die Standarddatei icon.svg per Drag&Drop vom FileSystem-Tab auf die Texture-Property im Inspector-Tab ziehen. Wenn Sie den Wert der Texture-Property ändern, während das vorherige Skript läuft, wird die Textur ebenfalls automatisch geändert.

In einigen Fällen kann es notwendig sein, jedes Frame neu zu zeichnen. Dazu rufen Sie queue_redraw von der Methode _process auf, etwa so:

extends Node2D

func _draw():
    pass  # Your draw commands here.

func _process(_delta):
    queue_redraw()

Koordinaten und Ausrichtung der Linienbreite

Die Zeichen-API verwendet das Koordinatensystem des CanvasItems, nicht unbedingt Pixelkoordinaten. Das bedeutet, dass _draw() den Koordinatenraum verwendet, der nach der Anwendung der CanvasItem-Transformation entsteht. Zusätzlich können Sie eine benutzerdefinierte Transformation darauf anwenden, indem Sie draw_set_transform oder draw_set_transform_matrix verwenden.

Wenn Sie draw_line verwenden, sollten Sie die Breite der Linie berücksichtigen. Wenn Sie eine Breite verwenden, die eine ungerade Größe hat, sollte die Position des Start- und Endpunktes um 0.5 verschoben werden, um die Linie zentriert zu halten, wie unten gezeigt.

../../_images/draw_line.png
func _draw():
    draw_line(Vector2(1.5, 1.0), Vector2(1.5, 4.0), Color.GREEN, 1.0)
    draw_line(Vector2(4.0, 1.0), Vector2(4.0, 4.0), Color.GREEN, 2.0)
    draw_line(Vector2(7.5, 1.0), Vector2(7.5, 4.0), Color.GREEN, 3.0)

Dasselbe gilt für die Methode draw_rect mit filled = false.

../../_images/draw_rect.png
func _draw():
    draw_rect(Rect2(1.0, 1.0, 3.0, 3.0), Color.GREEN)
    draw_rect(Rect2(5.5, 1.5, 2.0, 2.0), Color.GREEN, false, 1.0)
    draw_rect(Rect2(9.0, 1.0, 5.0, 5.0), Color.GREEN)
    draw_rect(Rect2(16.0, 2.0, 3.0, 3.0), Color.GREEN, false, 2.0)

Zeichnen mit Anti-Aliasing

Godot bietet Methodenparameter in draw_line an, um Antialiasing zu aktivieren, aber nicht alle benutzerdefinierten Zeichenmethoden bieten diesen antialiased-Parameter an.

Für benutzerdefinierte Zeichenmethoden, die keinen Parameter Antialiasing zur Verfügung stellen, können Sie stattdessen 2D MSAA aktivieren, was das Rendering im gesamten Viewport beeinflusst. Dies bietet qualitativ hochwertiges Antialiasing, aber zu höheren Performancekosten und nur für bestimmte Elemente. Siehe 2D-Antialiasing für weitere Informationen.

Hier ist ein Vergleich einer Linie mit minimaler Breite (width=-1), gezeichnet mit antialiased=false, antialiased=true und antialiased=false mit 2D MSAA 2x, 4x und 8x aktiviert.

../../_images/draw_antialiasing_options.webp

Tools

Das Zeichnen eigener Nodes kann auch beim Ausführen im Editor erwünscht sein. Dies kann als Vorschau oder Visualisierung eines Features oder eines Verhaltens verwendet werden.

Um dies zu tun, können Sie die Tool-Annotation sowohl für GDScript als auch für C# verwenden. Siehe das Beispiel unten und Code im Editor ausführen für weitere Informationen.

Beispiel 1: Zeichnen eines benutzerdefinierten Shapes

Wir werden nun die benutzerdefinierten Zeichenfunktionen der Godot-Engine verwenden, um etwas zu zeichnen, für das Godot keine Funktionen bereitstellt. Wir werden das Godot-Logo neu erstellen, aber mit Code - nur mit Zeichenfunktionen.

Sie müssen dafür eine Funktion programmieren und es selbst zeichnen.

Bemerkung

Die folgenden Anweisungen verwenden einen festen Satz von Koordinaten, der für hochauflösende Bildschirme (größer als 1080p) zu klein sein könnte. Wenn dies bei Ihnen der Fall ist und die Zeichnung zu klein ist, sollten Sie die Skalierung des Fensters unter Menü > Projekt > Projekteinstellungen > Anzeige/Fenster/Strecken/Skalierung erhöhen, um das Projekt an eine höhere Auflösung anzupassen (eine Skalierung von 2 oder 4 funktioniert in der Regel gut).

Zeichnen eines benutzerdefinierten Polygons

Es gibt zwar einen eigenen Node zum Zeichnen von benutzerdefinierten Polygonen ( Polygon2D), aber wir werden in diesem Fall ausschließlich Zeichenfunktionen auf niedrigerer Ebene verwenden, um sie auf demselben Node zu kombinieren und später komplexere Geometrien erstellen zu können.

Zunächst werden wir eine Reihe von Punkten - oder X- und Y-Koordinaten als Basis unserer Form definieren:

extends Node2D

var coords_head : Array = [
    [ 22.952, 83.271 ],  [ 28.385, 98.623 ],
    [ 53.168, 107.647 ], [ 72.998, 107.647 ],
    [ 99.546, 98.623 ],  [ 105.048, 83.271 ],
    [ 105.029, 55.237 ], [ 110.740, 47.082 ],
    [ 102.364, 36.104 ], [ 94.050, 40.940 ],
    [ 85.189, 34.445 ],  [ 85.963, 24.194 ],
    [ 73.507, 19.930 ],  [ 68.883, 28.936 ],
    [ 59.118, 28.936 ],  [ 54.494, 19.930 ],
    [ 42.039, 24.194 ],  [ 42.814, 34.445 ],
    [ 33.951, 40.940 ],  [ 25.637, 36.104 ],
    [ 17.262, 47.082 ],  [ 22.973, 55.237 ]
]

Dieses Format ist zwar kompakt, aber es ist nicht das Format, das Godot versteht, um ein Polygon zu zeichnen. In einem anderen Szenario könnten wir diese Koordinaten aus einer Datei laden oder die Positionen berechnen müssen, während die Anwendung läuft, so dass eine Transformation erforderlich sein könnte.

Um diese Koordinaten in das richtige Format umzuwandeln, werden wir eine neue Methode float_array_to_Vector2Array() erstellen. Dann überschreiben wir die Funktion _ready(), die Godot nur einmal - zu Beginn der Ausführung - aufruft, um diese Koordinaten in eine Variable zu laden:

var head : PackedVector2Array

func float_array_to_Vector2Array(coords : Array) -> PackedVector2Array:
    # Convert the array of floats into a PackedVector2Array.
    var array : PackedVector2Array = []
    for coord in coords:
        array.append(Vector2(coord[0], coord[1]))
    return array

func _ready():
    head = float_array_to_Vector2Array(coords_head);

Um schließlich unsere erste Geometrie zu zeichnen, verwenden wir die Methode draw_polygon und übergeben die Punkte (als Array von Vector2-Koordinaten) und ihre Farbe, wie folgt:

func _draw():
    # We are going to paint with this color.
    var godot_blue : Color = Color("478cbf")
    # We pass the PackedVector2Array to draw the shape.
    draw_polygon(head, [ godot_blue ])

Wenn Sie es ausführen, sollten Sie etwa Folgendes sehen:

../../_images/draw_godot_logo_polygon.webp

Beachten Sie, dass der untere Teil des Logos segmentiert aussieht - das liegt daran, dass nur eine geringe Anzahl von Punkten verwendet wurde, um diesen Teil zu definieren. Um eine glatte Kurve zu simulieren, könnten wir mehr Punkte zu unserem Array hinzufügen oder vielleicht eine mathematische Funktion verwenden, um eine Kurve zu interpolieren und eine geglättete Form aus dem Code zu erstellen (siehe Beispiel 2).

Polygone werden immer ihren letzten definierten Punkt mit ihrem ersten verbinden, um eine geschlossene Form zu erhalten.

Zeichnen von verbundenen Linien

Das Zeichnen einer Folge von verbundenen Linien, die sich nicht zu einem Polygon schließen, ist der vorherigen Methode sehr ähnlich. Wir werden eine zusammenhängende Menge von Linien verwenden, um den Mund des Godot-Logos zu zeichnen.

Zunächst definieren wir die Liste der Koordinaten, für die Form des Mundes, wie folgt:

var coords_mouth = [
    [ 22.817, 81.100 ], [ 38.522, 82.740 ],
    [ 39.001, 90.887 ], [ 54.465, 92.204 ],
    [ 55.641, 84.260 ], [ 72.418, 84.177 ],
    [ 73.629, 92.158 ], [ 88.895, 90.923 ],
    [ 89.556, 82.673 ], [ 105.005, 81.100 ]
]

Wir werden diese Koordinaten in eine Variable laden und eine weitere Variable mit der konfigurierbaren Linienstärke definieren:

var mouth : PackedVector2Array
var _mouth_width : float = 4.4

func _ready():
    head = float_array_to_Vector2Array(coords_head);
    mouth = float_array_to_Vector2Array(coords_mouth);

Und schließlich werden wir die Methode draw_polyline verwenden, um die Linie tatsächlich zu zeichnen, etwa so:

func _draw():
    # We will use white to draw the line.
    var white : Color = Color.WHITE
    var godot_blue : Color = Color("478cbf")

    draw_polygon(head, [ godot_blue ])

    # We draw the while line on top of the previous shape.
    draw_polyline(mouth, white, _mouth_width)

Sie sollten die folgende Ausgabe erhalten:

../../_images/draw_godot_logo_polyline.webp

Im Gegensatz zu draw_polygon() können Polylinien nur eine einzige Farbe für alle ihre Punkte haben (das zweite Argument). Diese Methode hat 2 zusätzliche Argumente: die Breite der Linie (die standardmäßig so klein wie möglich ist) und die Aktivierung oder Deaktivierung der Kantenglättung (standardmäßig ist sie deaktiviert).

Die Reihenfolge der _draw-Aufrufe ist wichtig - wie bei den Node-Positionen in der Baumhierarchie werden die verschiedenen Formen von oben nach unten gezeichnet, was dazu führt, daß die neuesten Formen die früheren verdecken, wenn sie sich überlappen. In diesem Fall soll der Mund über den Kopf gezeichnet werden, also setzen wir ihn danach.

Beachten Sie, dass wir Farben auf verschiedene Arten definieren können, entweder mit einem hexadezimalen Code oder einem vordefinierten Farbnamen. In der Klasse Color finden Sie weitere Konstanten und Möglichkeiten, Farben zu definieren.

Zeichnen von Kreisen

Um die Augen zu erstellen, werden wir 4 zusätzliche Aufrufe hinzufügen, um die Formen der Augen in verschiedenen Größen, Farben und Positionen zu zeichnen.

Um einen Kreis zu zeichnen, positionieren Sie ihn basierend auf seinem Mittelpunkt mit der Methode draw_circle. Der erste Parameter ist ein Vector2 mit den Koordinaten des Mittelpunkts, der zweite ist der Radius und der dritte ist die Farbe:

func _draw():
    var white : Color = Color.WHITE
    var godot_blue : Color = Color("478cbf")
    var grey : Color = Color("414042")

    draw_polygon(head, [ godot_blue ])
    draw_polyline(mouth, white, _mouth_width)

    # Four circles for the 2 eyes: 2 white, 2 grey.
    draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
    draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
    draw_circle(Vector2(43.423, 65.92), 6.246, grey)
    draw_circle(Vector2(84.626, 66.008), 6.246, grey)

Nach der Ausführung sollten Sie etwa folgendes Ergebnis erhalten:

../../_images/draw_godot_logo_circle.webp

Für partielle, nicht ausgefüllte Bögen (Teile einer Form zwischen bestimmten, beliebigen Winkeln) können Sie die Methode draw_arc verwenden.

Zeichnen von Linien

Um die letzte Form (die Nase) zu zeichnen, verwenden wir eine Linie, um sie anzunähern.

draw_line kann verwendet werden, um ein einzelnes Segment zu zeichnen, indem man seine Start- und Endkoordinaten als Argumente angibt, wie hier:

func _draw():
    var white : Color = Color.WHITE
    var godot_blue : Color = Color("478cbf")
    var grey : Color = Color("414042")

    draw_polygon(head, [ godot_blue ])
    draw_polyline(mouth, white, _mouth_width)
    draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
    draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
    draw_circle(Vector2(43.423, 65.92), 6.246, grey)
    draw_circle(Vector2(84.626, 66.008), 6.246, grey)

    # Draw a short but thick white vertical line for the nose.
    draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)

Sie sollten nun folgende Form auf dem Bildschirm sehen können:

../../_images/draw_godot_logo_line.webp

Wenn mehrere unverbundene Linien gleichzeitig gezeichnet werden sollen, können Sie zusätzliche Performance erzielen, indem Sie alle Linien in einem einzigen Aufruf zeichnen, indem Sie die Methode draw_multiline verwenden.

Zeichnen von Text

Während die Verwendung des Label-Nodes die gebräuchlichste Art ist, Text zu Ihrer Anwendung hinzuzufügen, enthält die Low-Level-Funktion _draw eine Funktionalität, um Text zu Ihrer eigenen Node-Zeichnung hinzuzufügen. Wir werden sie verwenden, um den Namen "GODOT" unter dem Roboterkopf hinzuzufügen.

Wir werden die Methode draw_string verwenden, um dies zu tun:

var default_font : Font = ThemeDB.fallback_font;

func _draw():
    var white : Color = Color.WHITE
    var godot_blue : Color = Color("478cbf")
    var grey : Color = Color("414042")

    draw_polygon(head, [ godot_blue ])
    draw_polyline(mouth, white, _mouth_width)
    draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
    draw_circle(Vector2(85.524, 65.4825), 9.3905, white)
    draw_circle(Vector2(43.423, 65.92), 6.246, grey)
    draw_circle(Vector2(84.626, 66.008), 6.246, grey)
    draw_line(Vector2(64.273, 60.564), Vector2(64.273, 74.349), white, 5.8)

    # Draw GODOT text below the logo with the default font, size 22.
    draw_string(default_font, Vector2(20, 130), "GODOT",
                HORIZONTAL_ALIGNMENT_CENTER, 90, 22)

Hier laden wir zunächst in die defaultFont-Variable die konfigurierte Default-Schriftart des Themes (stattdessen kann eine benutzerdefinierte Schriftart eingestellt werden) und übergeben dann die folgenden Parameter: Schriftart, Position, Text, horizontale Ausrichtung, Breite und Schriftgröße.

Auf Ihrem Bildschirm sollten Sie folgendes sehen:

../../_images/draw_godot_logo_text.webp

Zusätzliche Parameter sowie andere Methoden, die sich auf Text und Zeichen beziehen, sind in der Klassenreferenz CanvasItem zu finden.

Anzeigen der Zeichnung während der Bearbeitung

Während der bisherige Code in der Lage ist, das Logo in einem laufenden Fenster zu zeichnen, wird es nicht in der 2D-Ansicht des Editors angezeigt. In bestimmten Fällen möchten Sie Ihr eigenes Node2D oder Steuerelement auch im Editor anzeigen, um es entsprechend zu positionieren und zu skalieren, wie es die meisten anderen Nodes tun.

Um das Logo direkt im Editor anzuzeigen (ohne es auszuführen), können Sie die @tool-Annotation verwenden, um die benutzerdefinierte Zeichnung des Nodes auch während der Bearbeitung anzeigen zu lassen, etwa so:

@tool
extends Node2D

Sie müssen Ihre Szene speichern, Ihr Projekt neu erstellen (nur für C#) und die aktuelle Szene manuell über den Menüpunkt Szene > Gespeicherte Szene neu laden neu laden, um den aktuellen Node in der 2D-Ansicht zu aktualisieren, wenn Sie das erste Mal die @tool-Annotation hinzufügen oder entfernen.

Animation

Wenn wir die benutzerdefinierte Form zur Laufzeit ändern wollten, könnten wir die aufgerufenen Methoden oder ihre Argumente zur Ausführungszeit ändern oder eine Transformation anwenden.

Wenn wir zum Beispiel wollen, dass sich die Form, die wir gerade entworfen haben, dreht, könnten wir die folgende Variable und den folgenden Code zu den Methoden _ready und _process hinzufügen:

extends Node2D

@export var rotation_speed : float = 1  # In radians per second.

func _ready():
    rotation = 0
    ...

func _process(delta: float):
    rotation -= rotation_speed * delta

Das Problem mit dem obigen Code ist, dass wir die Punkte ungefähr auf einem Rechteck erstellt haben, das von der oberen linken Ecke, der (0, 0)-Koordinate, ausgeht und sich nach rechts und unten erstreckt, so dass wir sehen, dass die Drehung unter Verwendung der oberen linken Ecke als Pivotpunkt erfolgt. Eine Änderung der Positionstransformation am Node hilft uns hier nicht weiter, da die Rotationstransformation zuerst angewendet wird.

Wir könnten zwar alle Koordinaten der Punkte so umschreiben, dass sie um (0, 0) zentriert sind, einschließlich negativer Koordinaten, aber das wäre eine Menge Arbeit.

Ein möglicher Weg, dies zu umgehen, ist die Verwendung der Low-Level-Methode draw_set_transform, um dieses Problem zu beheben. Dabei werden alle Punkte im eigenen Space des CanvasItems übersetzt und dann mit einer regulären Node-Transformation zurück an ihren ursprünglichen Platz verschoben, entweder im Editor oder im Code, wie hier:

func _ready():
    rotation = 0
    position = Vector2(60, 60)
    ...

func _draw():
    draw_set_transform(Vector2(-60, -60))
    ...

Dies ist das Ergebnis, das sich um einen Pivot-Punkt auf (60, 60) dreht:

../../_images/draw_godot_rotation.webp

Wenn das, was wir animieren wollten, eine Property innerhalb des _draw() -Aufrufs war, müssen wir daran denken, queue_redraw() aufzurufen, um einen Refresh zu erzwingen, da es sonst nicht auf dem Bildschirm aktualisiert werden würde.

So können wir zum Beispiel den Roboter dazu bringen, seinen Mund zu öffnen und zu schließen, indem wir die Breite seiner Mundlinie einer Sinuskurve (sin) folgen lassen:

var _mouth_width : float = 4.4
var _max_width : float = 7
var _time : float = 0

func _process(delta : float):
    _time += delta
    _mouth_width = abs(sin(_time) * _max_width)
    queue_redraw()

func _draw():
    ...
    draw_polyline(mouth, white, _mouth_width)
    ...

Wenn es ausgeführt wird, sieht das ungefähr so aus:

../../_images/draw_godot_mouth_animation.webp

Bitte beachten Sie, dass _mouth_width eine benutzerdefinierte Property wie jede andere ist und dass sie oder jede andere, die als Zeichenargument verwendet wird, mit Hilfe von Default-Methoden wie einem Tween oder einem AnimationPlayer-Node animiert werden kann. Der einzige Unterschied ist, dass ein queue_redraw() Aufruf benötigt wird, um die Änderungen auf dem Bildschirm zu zeigen.

Beispiel 2: Zeichnen einer dynamischen Linie

Das vorherige Beispiel war nützlich, um zu lernen, wie man Nodes mit benutzerdefinierten Shapes und Animationen zeichnet und verändert. Dies könnte einige Vorteile haben, wie z.B. die Verwendung von exakten Koordinaten und Vektoren zum Zeichnen anstelle von Bitmaps - was bedeutet, dass sie bei der Transformation auf dem Bildschirm gut skaliert werden. In einigen Fällen könnten ähnliche Ergebnisse erzielt werden, indem man Funktionen auf höherer Ebene mit Nodes wie sprites oder AnimatedSprites, die SVG-Ressourcen laden (die ebenfalls mit Vektoren definierte Bilder sind), und dem Node AnimationPlayer kombiniert.

In anderen Fällen ist das nicht möglich, weil wir vor der Ausführung des Codes nicht wissen, wie die resultierende grafische Darstellung aussehen wird. Hier werden wir sehen, wie man eine dynamische Linie zeichnet, deren Koordinaten vorher nicht bekannt sind und die von den Eingaben des Benutzers beeinflusst werden.

Zeichnen einer geraden Linie zwischen 2 Punkten

Nehmen wir an, wir wollen eine gerade Linie zwischen zwei Punkten zeichnen, wobei der erste Punkt in der oberen linken Ecke (0, 0) liegt und der zweite Punkt durch die Position des Cursors auf dem Bildschirm bestimmt wird.

Wir könnten eine dynamische Linie zwischen diesen beiden Punkten wie folgt ziehen:

extends Node2D

var point1 : Vector2 = Vector2(0, 0)
var width : int = 10
var color : Color = Color.GREEN

var _point2 : Vector2

func _process(_delta):
    var mouse_position = get_viewport().get_mouse_position()
    if mouse_position != _point2:
        _point2 = mouse_position
        queue_redraw()

func _draw():
    draw_line(point1, _point2, color, width)

In diesem Beispiel erhalten wir die Position der Maus im Default-Viewport in jedem Frame mit der Methode get_mouse_position. Wenn sich die Position seit der letzten Zeichenanforderung geändert hat (eine kleine Optimierung, um ein erneutes Zeichnen bei jedem Frame zu vermeiden), werden wir ein erneutes Zeichnen einplanen. Unsere Methode _draw() hat nur eine Zeile: Sie fordert das Zeichnen einer grünen Linie mit einer Breite von 10 Pixeln zwischen der oberen linken Ecke und der ermittelten Position an.

Die Breite, Farbe und Position des Startpunktes kann mit den entsprechenden Propertys konfiguriert werden.

Nach der Ausführung sollte es wie folgt aussehen:

../../_images/draw_line_between_2_points.webp

Zeichnen eines Bogens zwischen 2 Punkten

Das obige Beispiel funktioniert, aber wir möchten diese 2 Punkte vielleicht mit einem anderen Shape oder einer anderen Funktion als einer geraden Linie verbinden.

Versuchen wir nun, einen Bogen (einen Teil eines Kreises) zwischen beiden Punkten zu erstellen.

Durch den Export von Startpunkt, Segmenten, Breite, Farbe und Antialiasing können wir diese Propertys sehr einfach direkt im Inspektorfenster des Editors ändern:

extends Node2D

@export var point1 : Vector2 = Vector2(0, 0)
@export_range(1, 1000) var segments : int = 100
@export var width : int = 10
@export var color : Color = Color.GREEN
@export var antialiasing : bool = false

var _point2 : Vector2
../../_images/draw_dynamic_exported_properties.webp

Um den Bogen zu zeichnen, können wir die Methode draw_arc verwenden. Es gibt viele Bögen, die durch 2 Punkte gehen, also werden wir für dieses Beispiel den Halbkreis wählen, der seinen Mittelpunkt im mittleren Punkt zwischen den 2 Anfangspunkten hat.

Die Berechnung dieses Bogens ist komplizierter als im Fall der Linie:

func _draw():
    # Calculate the arc parameters.
    var center : Vector2 = Vector2((_point2.x - point1.x) / 2,
                                   (_point2.y - point1.y) / 2)
    var radius : float = point1.distance_to(_point2) / 2
    var start_angle : float = (_point2 - point1).angle()
    var end_angle : float = (point1 - _point2).angle()
    if end_angle < 0:  # end_angle is likely negative, normalize it.
        end_angle += TAU

    # Finally, draw the arc.
    draw_arc(center, radius, start_angle, end_angle, segments, color,
             width, antialiasing)

Der Mittelpunkt des Halbkreises ist der mittlere Punkt zwischen den beiden Punkten. Der Radius ist die Hälfte des Abstands zwischen den beiden Punkten. Die Start- und Endwinkel sind die Winkel des Vektors von Punkt1 zu Punkt2 und umgekehrt. Beachten Sie, dass wir den Endwinkel in positive Werte normalisieren mussten, denn wenn der Endwinkel kleiner ist als der Startwinkel, wird der Bogen gegen den Uhrzeigersinn gezeichnet, was wir in diesem Fall nicht wollen (der Bogen würde auf dem Kopf stehen).

Das Ergebnis sollte in etwa so aussehen, wobei der Bogen nach unten und zwischen den Punkten verläuft:

../../_images/draw_arc_between_2_points.webp

Sie können mit den Parametern im Inspektor spielen, um verschiedene Ergebnisse zu erzielen: Ändern Sie die Farbe, die Breite, das Antialiasing und erhöhen Sie die Anzahl der Segmente, um die Glätte der Kurve zu erhöhen, allerdings zum Preis von zusätzlicher Rechenzeit.