Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

GDScript-Referenz

GDScript ist eine High-Level, objektorientierte, imperative und graduell typisierte Programmiersprache, die für Godot entwickelt wurde. Sie verwendet eine auf Einrückungen basierende Syntax, ähnlich zu Sprachen wie Python. Ihr Ziel ist es, für die Godot-Engine optimiert und eng in sie integriert zu werden, was eine große Flexibilität bei der Erstellung und Integration von Inhalten ermöglicht.

GDScript ist völlig unabhängig von Python und basiert nicht darauf.

Geschichte

Bemerkung

Ältere Artikel über GDScript wurden in die Häufig gestellten Fragen verschoben.

Beispiel für GDScript

Manche Leute können besser lernen, indem sie sich die Syntax ansehen. Hier ist ein Beispiel dafür, wie GDScript aussieht.

# Everything after "#" is a comment.
# A file is a class!

# (optional) icon to show in the editor dialogs:
@icon("res://path/to/optional/icon.svg")

# (optional) class definition:
class_name MyClass

# Inheritance:
extends BaseClass


# Member variables.
var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var other_dict = {key = "value", other_key = 2}
var typed_var: int
var inferred_type := "String"

# Constants.
const ANSWER = 42
const THE_NAME = "Charly"

# Enums.
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}

# Built-in vector types.
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)


# Functions.
func some_function(param1, param2, param3):
    const local_const = 5

    if param1 < local_const:
        print(param1)
    elif param2 > 5:
        print(param2)
    else:
        print("Fail!")

    for i in range(20):
        print(i)

    while param2 != 0:
        param2 -= 1

    match param3:
        3:
            print("param3 is 3!")
        _:
            print("param3 is not 3!")

    var local_var = param1 + 3
    return local_var


# Functions override functions with the same name on the base/super class.
# If you still want to call them, use "super":
func something(p1, p2):
    super(p1, p2)


# It's also possible to call another function in the super class:
func other_something(p1, p2):
    super.something(p1, p2)


# Inner class
class Something:
    var a = 10


# Constructor
func _init():
    print("Constructed!")
    var lv = Something.new()
    print(lv.a)

Wenn Sie bereits Erfahrung mit statisch typisierten Sprachen wie C, C++ oder C# haben, aber bisher keine dynamisch typisierte verwendet haben, wird das Lesen dieses Tutorials empfohlen: GDScript: Eine Einführung in dynamische Programmiersprachen.

Bezeichner

Jeder String, der beschränkt ist auf Zeichen des Alphabets (a bis z und A bis Z), Ziffern (0 bis 9) und _, ist ein gültiger Bezeichner. Zusätzlich dürfen Bezeichner nicht mit einer Ziffer beginnen. Bei Bezeichnern wird Groß- und Kleinschreibung unterschieden (foo unterscheidet sich von FOO).

Bezeichner können auch die meisten Unicode-Zeichen enthalten, die Teil von UAX#31 sind. Dies ermöglicht die Verwendung von Bezeichnernamen, die in anderen Sprachen als Englisch geschrieben sind. Unicode-Zeichen, die als "verwechselbar" mit ASCII-Zeichen und Emoji gelten, sind in Bezeichnern nicht erlaubt.

Schlüsselwörter

Das Folgende ist eine Auflistung von Schlüsselwörtern, die von der Sprache unterstützt werden. Da Schlüsselwörter reservierte Wörter (Tokens) sind, können sie nicht als Bezeichner verwendet werden. Operatoren (wie in, not, and oder or) und Namen von Built-in-Typen, die in den folgenden Abschnitten aufgelistet werden, sind ebenfalls reserviert.

Schlüsselwörter werden im GDSkript Tokenizer definiert, falls Sie einen Blick unter die Haube werfen möchten.

Schlüsselwort

Beschreibung

if

Siehe if/else/elif.

elif

Siehe if/else/elif.

else

Siehe if/else/elif.

for

Siehe for.

while

Siehe while.

match

Siehe match.

when

Used by pattern guards in match statements.

break

Beendet die Ausführung der aktuellen for oder while-Schleife.

continue

Springt sofort zur nächsten Iteration der for oder while-Schleife.

pass

Benutzt man, wo eine Anweisung zwar syntaktisch benötigt wird, aber die Ausführung von Code eigentlich ungewünscht ist, z.B. in leeren Funktionen.

return

Gibt einen Wert aus einer Funktion zurück.

class

Definiert eine innere Klasse. Siehe Innere Klassen.

class_name

Definiert das Skript als eine global zugängliche Klasse mit dem angegebenen Namen. Siehe Registrierung benannter Klassen.

extends

Definiert von welcher Klasse die aktuelle Klasse abgeleitet soll.

is

Testet, ob eine Variable von einer gegeben Klasse abgeleitet ist oder einem gegebenen Built-in-Typen entspricht.

in

Prüft, ob ein Wert innerhalb eines Strings, eines Arrays, einer Range, eines Dictionarys oder eines Nodes liegt. Wenn es mit for verwendet wird, durchläuft es sie, anstatt zu testen.

as

Wandelt den Wert in einen gegebenen Typ um, falls möglich.

self

Refers to current class instance. See self.

super

Löst den Geltungsbereich der Parent-Methode auf. Siehe Vererbung.

signal

Defines a signal. See Signals.

func

Defines a function. See Functions.

static

Definiert eine statische Funktion oder eine statische Membervariable.

const

Defines a constant. See Constants.

enum

Defines an enum. See Enums.

var

Defines a variable. See Variables.

breakpoint

Editor-Helfer für Debugger-Breakpoints. Im Gegensatz zu Breakpoints, die durch Klicken am Seitenrand erzeugt werden, wird breakpoint im Skript selbst gespeichert. Dadurch bleibt er bei Verwendung der Versionsverwaltung auf verschiedenen Rechnern erhalten.

preload

Lädt eine Variable oder Klasse im Voraus, siehe Klassen als Ressourcen.

await

Wartet auf das Ende eines Signals oder einer Coroutine. Siehe Warten auf Signale oder Coroutinen.

yield

Wurde früher für Coroutinen verwendet. Wird übergangsweise als Schlüsselwort beibehalten.

assert

Fügt eine Assertion für eine Bedingung hinzu, protokolliert Fehler bei Fehlschlag. Wird in Nicht-Debug-Builds ignoriert. Siehe Assert-Schlüsselwort.

void

Wird verwendet, um darzustellen, dass eine Funktion keinen Wert zurückgibt.

PI

Die PI-Konstante.

TAU

Die TAU-Konstante.

INF

Unendlichkeitskonstante. Wird für Vergleiche und als Ergebnis von Berechnungen verwendet.

NAN

NAN (not a number, keine Zahl)-Konstante. Wird als unmögliches Ergebnis von Berechnungen verwendet.

Operatoren

Nachfolgend finden Sie eine Liste der unterstützten Operatoren und deren Rangfolge. Alle binären Operatoren sind links-assoziativ, einschließlich des Operators **. Das bedeutet, dass 2 ** 2 ** 3 gleich ist zu (2 ** 2) ** 3. Benutzen Sie Klammern, um explizit den benötigten Vorrang anzugeben, zum Beispiel 2 ** (2 ** 3). Der ternäre Operator if/else ist rechts-assoziativ.

Operator

Beschreibung

( )

Gruppierung (höchste Priorität)

Klammern sind eigentlich kein Operator, sondern ermöglichen es Ihnen, den Vorrang einer Operation explizit anzugeben.

x[index]

Element-Zugriff

x.attribute

Referenzierung eines Attributs

foo()

Funktionsaufruf

await x

Warten auf Signale oder Coroutinen

x is Node
x is not Node

Typprüfung

Siehe auch is_instance_of()-Funktion.

x ** y

Potenzierung

Multipliziert x mit sich selbst y mal, ähnlich dem Aufruf der Funktion pow().

~x

Bitweise Negation

+x
-x

identische Abbildung / Negation

x * y
x / y
x % y

Multiplikation / Division / Modulus (Rest)

Der Operator % wird zusätzlich für Format-Strings verwendet.

Hinweis: Diese Operatoren haben das gleiche Verhalten wie in C++, was für Benutzer, die aus Python, JavaScript usw. kommen, unerwartet sein kann. Siehe einen ausführlichen Hinweis nach der Tabelle.

x + y
x - y

Addition (oder Verkettung) / Subtraktion

x << y
x >> y

Bit-Shifting

x & y

Bitweises UND

x ^ y

Bitweises XOR

x | y

Bitweises ODER

x == y
x != y
x < y
x > y
x <= y
x >= y

Vergleich

Siehe eine ausführliche Anmerkung nach der Tabelle.

x in y
x not in y

Prüfung auf Enthaltensein

in wird auch mit dem Schlüsselwort for als Teil der Syntax verwendet.

not x
!x

Boolesches NOT und sein nicht empfohlener-Alias

x and y
x && y

Boolesches UND und sein unempfohlener Alias

x or y
x || y

Boolesches ODER und sein unempfohlener Alias

true_expr if cond else false_expr

Ternäres if/else

x as Node

Typ-Casting

x = y
x += y
x -= y
x *= y
x /= y
x **= y
x %= y
x &= y
x |= y
x ^= y
x <<= y
x >>= y

Zuweisung (niedrigste Priorität)

Sie können einen Zuweisungsoperator nicht innerhalb eines Ausdrucks verwenden.

Bemerkung

Das Verhalten einiger Operatoren kann von dem abweichen, was Sie erwarten:

  1. Wenn beide Operanden des Operators / int sind, dann wird eine Integer-Division anstelle einer mit Kommazahlen durchgeführt. Zum Beispiel 5 / 2 == 2, nicht 2.5. Wenn dies nicht erwünscht ist, verwenden Sie mindestens ein Float-Literal (x / 2.0), Cast (float(x) / y), oder multiplizieren Sie mit 1.0 (x * 1.0 / y).

  2. Der %-Operator ist nur für Ints verfügbar, für Floats verwenden Sie die Funktion fmod().

  3. Für negative Werte verwenden der %-Operator und fmod() das Abrunden, anstatt gegen negativ unendlich zu runden. Dies bedeutet, dass der Rest ein Vorzeichen hat. Wenn Sie den Rest im mathematischen Sinn benötigen, verwenden Sie stattdessen die Funktionen posmod() und fposmod().

  4. Die Operatoren == und != erlauben es manchmal, Werte unterschiedlichen Typs zu vergleichen (z.B. 1 == 1.0 ist wahr), aber in anderen Fällen können sie einen Laufzeitfehler verursachen. Wenn Sie sich über die Typen der Operanden nicht sicher sind, können Sie die Funktion is_same() verwenden (beachten Sie aber, dass sie strenger ist, was Typen und Referenzen angeht). Um Float-Zahlen zu vergleichen, verwenden Sie stattdessen die Funktionen is_equal_approx() und is_zero_approx().

Literale

Beispiel(e)

Beschreibung

null

Null-Wert

false, true

Boolesche Werte

45

Integer zur Basis 10

0x8f51

Integer zur Basis 16 (hexadezimal)

0b101010

Integer zur Basis 2 (binär)

3.14, 58.1e-10

Gleitkomma-Zahl (real)

"Hallo", 'Hi'

Normale Strings

"""Hallo""", '''Hi'''

Normale Strings in drei Anführungszeichen

r"Hallo", r'Hi'

Raw-Strings

r"""Hallo""", r'''Hi'''

Raw-Strings in drei Anführungszeichen

&"name"

StringName

^"Node/Label"

NodePath

Es gibt auch zwei Konstrukte, die wie Literale aussehen, aber eigentlich keine sind:

Beispiel

Beschreibung

$NodePath

Kurzform für get_node("NodePath")

%UniqueNode

Kurzform für get_node("%UniqueNode")

Integer und Floats können durch _ getrennt werden um die Lesbarkeit zu verbessern. Die folgenden Möglichkeiten zum Schreiben von Zahlen sind alle gültig:

12_345_678  # Equal to 12345678.
3.141_592_7  # Equal to 3.1415927.
0x8080_0000_ffff  # Equal to 0x80800000ffff.
0b11_00_11_00  # Equal to 0b11001100.

Normale String-Literale können die folgenden Escape-Sequenzen enthalten:

Escape-Sequenz

wird erweitert zu

\n

Zeilenumbruch (Line Feed)

\t

Horizontaler Tabulator

\r

Wagenrücklauf (Carriage Return)

\a

Alarm (Piepton/Klingel)

\b

Rückschritt

\f

Formfeed-Seitenumbruch

\v

Vertikaler Tabulator

\"

Doppeltes Anführungszeichen

\'

Einfaches Anführungszeichen

\\

Rückwärtsschrägstrich

\uXXXX

UTF-16 Unicode-Codepunkt XXXX (hexadezimal, Groß- und Kleinschreibung wird nicht berücksichtigt)

\UXXXXXX

UTF-32 Unicode-Codepunkt XXXXXX (hexadezimal, Groß- und Kleinschreibung wird nicht berücksichtigt)

Es gibt zwei Möglichkeiten, ein escapetes Unicode-Zeichen über 0xFFFF darzustellen:

Außerdem ermöglicht die Verwendung von \ gefolgt von einem Zeilenumbruch innerhalb eines Strings, diesen in der nächsten Zeile fortzusetzen, ohne ein Zeilenumbruchzeichen in den String selbst einzufügen.

Ein String, der in Anführungszeichen eines Typs (z. B. ") eingeschlossen ist, kann Anführungszeichen eines anderen Typs (z.B. ') enthalten, ohne dass er escaped wird. Bei Strings in dreifachen Anführungszeichen können Sie darauf verzichten, bis zu zwei aufeinanderfolgende Anführungszeichen desselben Typs zu escapen (es sei denn, sie grenzen an die Ränder des Strings).

Raw-String-Literale kodieren den String immer so, wie er im Quellcode erscheint. Dies ist besonders nützlich für reguläre Ausdrücke. Ein Raw-String-Literal verarbeitet keine Escape-Sequenzen, erkennt aber \ und \ (\') und ersetzt sie durch sich selbst. So kann ein String ein Anführungszeichen enthalten, das mit dem öffnenden Anführungszeichen übereinstimmt, aber nur, wenn ihm ein Backslash vorausgeht.

print("\tchar=\"\\t\"")  # Prints `    char="\t"`.
print(r"\tchar=\"\\t\"") # Prints `\tchar=\"\\t\"`.

Bemerkung

Einige Strings können nicht mit rohen String-Literalen dargestellt werden: Sie können keine ungerade Anzahl von Backslashes am Ende eines Strings oder ein nicht abgeschnittenes öffnendes Anführungszeichen innerhalb des Strings haben. In der Praxis spielt dies jedoch keine Rolle, da Sie einen anderen Anführungszeichen-Typ oder die Verkettung mit einem regulären String-Literal verwenden können.

GDScript unterstützt auch format strings.

Annotationen

Annotationen sind spezielle Token in GDScript, die als Modifikatoren für ein Skript oder seinen Code fungieren und beeinflussen können, wie das Skript von der Godot-Engine oder dem Editor behandelt wird.

Jede Annotation beginnt mit dem Zeichen @ und wird durch einen Namen spezifiziert. Eine detaillierte Beschreibung und ein Beispiel für jede Annotation finden Sie in der GDScript-Klassenreferenz.

Sie können damit zum Beispiel einen Wert in den Editor exportieren:

@export_range(1, 100, 1, "or_greater")
var ranged_var: int = 50

Weitere Informationen über den Export von Propertys finden Sie im Artikel GDScript-Exporte.

Jeder konstante Ausdruck, der mit dem erforderlichen Argumenttyp kompatibel ist, kann als ein Annotationsargument-Wert übergeben werden:

const MAX_SPEED = 120.0

@export_range(0.0, 0.5 * MAX_SPEED)
var initial_speed: float = 0.25 * MAX_SPEED

Annotationen können einzeln pro Zeile oder alle in derselben Zeile angegeben werden. Sie wirken sich auf die nächste Anweisung aus, die keine Annotation ist. Annotationen können Argumente haben, die in Klammern gesetzt und durch Kommas getrennt werden.

Diese beiden sind das Gleiche:

@annotation_a
@annotation_b
var variable

@annotation_a @annotation_b var variable

@onready-Annotation

Bei der Verwendung von Nodes ist es üblich, Verweise auf Teile der Szene in einer Variablen zu behalten. Da Szenen nur beim Aufrufen des aktiven Szenenbaums konfiguriert werden müssen, können die Unter-Nodes nur abgerufen werden, wenn ein Aufruf von Node._ready() erfolgt.

var my_label


func _ready():
    my_label = get_node("MyLabel")

Dies kann ein wenig mühsam werden, besonders wenn sich Nodes und externe Referenzen häufen. Dafür gibt es in GDScript die @onready-Annotation, um die Initialisierung einer Mitgliedsvariable aufzuschieben, bis _ready() aufgerufen wird. Sie kann den obigen Code durch eine einzige Zeile ersetzen:

@onready var my_label = get_node("MyLabel")

Warnung

Die Anwendung von @onready und einer @export-Annotation auf dieselbe Variable funktioniert nicht so, wie man es erwarten könnte. Die @onready-Annotation bewirkt, dass der Default-Wert gesetzt wird, nachdem die @export-Annotation in Kraft getreten ist, und überschreibt ihn:

@export var a = "init_value_a"
@onready @export var b = "init_value_b"

func _init():
    prints(a, b) # init_value_a <null>

func _notification(what):
    if what == NOTIFICATION_SCENE_INSTANTIATED:
        prints(a, b) # exported_value_a exported_value_b

func _ready():
    prints(a, b) # exported_value_a init_value_b

Daher wird die Warnung ONREADY_WITH_EXPORT erzeugt, die standardmäßig als Fehler behandelt wird. Wir empfehlen nicht, sie zu deaktivieren oder zu ignorieren.

Kommentare

Alles ab einem # bis zum Ende der Zeile wird ignoriert und als Kommentar aufgefasst.

# This is a comment.

Tipp

Im Godot-Skript-Editor werden spezielle Schlüsselwörter in Kommentaren hervorgehoben, um die Aufmerksamkeit des Benutzers auf bestimmte Kommentare zu lenken:

  • Kritisch (erscheint in rot): ALERT, ATTENTION, CAUTION, CRITICAL, DANGER, SECURITY

  • Warnung (erscheint in gelb): BUG, DEPRECATED, FIXME, HACK, TASK, TBD, TODO, WARNING

  • Hinweis (erscheint in grün): INFO, NOTE, NOTICE, TEST, TESTING

Bei diesen Schlüsselwörtern wird zwischen Groß- und Kleinschreibung unterschieden, sie müssen also in Großbuchstaben geschrieben werden, damit sie erkannt werden:

# In the example below, "TODO" will appear in yellow by default.
# The `:` symbol after the keyword is not required, but it's often used.

# TODO: Add more items for the player to choose from.

Die Liste der hervorgehobenen Schlüsselwörter und ihre Farben können im Abschnitt Texteditor > Theme > Kommentarmarkierungen der Editoreinstellungen geändert werden.

Use two hash symbols (##) instead of one (#) to add a documentation comment, which will appear in the script documentation and in the inspector description of an exported variable. Documentation comments must be placed directly above a documentable item (such as a member variable), or at the top of a file. Dedicated formatting options are also available. See GDScript Dokumentations-Kommentare for details.

## This comment will appear in the script documentation.
var value

## This comment will appear in the inspector tooltip, and in the documentation.
@export var exported_value

Code-Regionen

Code-Regionen sind spezielle Arten von Kommentaren, die der Skript-Editor als ausklappbare Bereiche versteht. Das bedeutet, dass Sie nach dem Schreiben von Code-Region-Kommentaren die Region ein- und ausklappen können, indem Sie auf den Pfeil klicken, der links neben dem Kommentar erscheint. Dieser Pfeil erscheint in einem lilafarbenen Quadrat, damit er von der normalen Code-Ausklappung unterschieden werden kann.

Die Syntax lautet wie folgt:

# Important: There must be *no* space between the `#` and `region` or `endregion`.

# Region without a description:
#region
...
#endregion

# Region with a description:
#region Some description that is displayed even when collapsed
...
#endregion

Tipp

Um eine Code-Region schnell zu erstellen, markieren Sie mehrere Zeilen im Skript-Editor, klicken Sie mit der rechten Maustaste auf die Auswahl und wählen Sie Code-Region erstellen. Die Beschreibung des Bereichs wird automatisch zur Bearbeitung ausgewählt.

Es ist möglich, Code-Regionen innerhalb anderer Code-Regionen zu verschachteln.

Hier ist ein konkretes Beispiel für die Verwendung von Code-Regionen:

# This comment is outside the code region. It will be visible when collapsed.
#region Terrain generation
# This comment is inside the code region. It won't be visible when collapsed.
func generate_lakes():
    pass

func generate_hills():
    pass
#endregion

#region Terrain population
func place_vegetation():
    pass

func place_roads():
    pass
#endregion

Dies kann nützlich sein, um große Codeabschnitte in leichter zu verstehende Abschnitte zu gliedern. Denken Sie jedoch daran, dass externe Editoren diese Funktion in der Regel nicht unterstützen. Stellen Sie also sicher, dass Ihr Code auch dann leicht verständlich ist, wenn Sie sich nicht auf das Einklappen von Codebereichen verlassen.

Bemerkung

Einzelne Funktionen und eingerückte Abschnitte (wie if und for) können im Skripteditor immer eingeklappt werden. Das bedeutet, dass Sie es vermeiden sollten, eine Code-Region zu verwenden, um eine einzelne Funktion oder einen eingerückten Abschnitt zu enthalten, da dies keinen großen Nutzen bringt. Code-Regionen funktionieren am besten, wenn sie dazu dienen, mehrere Elemente zusammenzufassen.

Fortsetzung der Zeile

Eine Codezeile in GDScript kann in der nächsten Zeile fortgesetzt werden, indem ein Backslash (\) verwendet wird. Fügen Sie einen solchen Schrägstrich am Ende einer Zeile ein, und der Code in der nächsten Zeile verhält sich so, als befände er sich an der Stelle des Backslashs. Hier ist ein Beispiel:

var a = 1 + \
2

Eine Zeile kann auf diese Weise mehrfach fortgesetzt werden:

var a = 1 + \
4 + \
10 + \
4

Built-in-Typen

Built-in-Typen werden auf dem Stack abgelegt. Sie werden als Werte übergeben. Das bedeutet, dass bei jeder Zuweisung oder bei der Übergabe als Argument an Funktionen eine Kopie erstellt wird. Die Ausnahmen sind Object, Array, Dictionary und gepackte Arrays (wie PackedByteArray), die per Referenz übergeben werden, so dass sie gemeinsam genutzt werden können. Alle Arrays, Dictionary, und einige Objekte (Node, Resource) haben eine duplicate() Methode, die es erlaubt, eine Kopie zu erstellen.

Einfache Bult-in-Typen

Eine Variable in GDScript kann mehreren Built-in-Typen zugewiesen werden.

null

null ist ein leerer Datentyp, der keine Information enthält und dem kein anderer Wert zugewiesen werden kann.

Only types that inherit from Object can have a null value (Object is therefore called a "nullable" type). Variant types must have a valid value at all times, and therefore cannot have a null value.

bool

Kurzform für "Boolean", der nur true oder false enthalten kann.

int

Kurz für "Integer": es speichert ganze Zahlen (positiv und negativ). Sie werden als 64-Bit-Wert gespeichert werden, gleichbedeutend mit int64_t in C++.

float

Speichert reelle Zahlen, einschließlich Dezimalzahlen, unter Verwendung von Gleitkommazahlen. Er wird als 64-Bit-Wert gespeichert, was double in C++ entspricht. Anmerkung: Derzeit speichern Datenstrukturen wie Vector2, Vector3 und PackedFloat32Array 32-Bit Single-Precision Float-Werte.

String

Eine Folge von Zeichen im Unicode-Format.

StringName

Ein unveränderlicher String, der für jeden Namen nur eine Instanz zulässt. Sie sind langsamer zu erstellen und können beim Multithreading zum Warten auf Locks führen. Im Gegenzug sind sie sehr schnell zu vergleichen, was sie zu guten Kandidaten für Dictionary-Keys macht.

NodePath

Ein vor-geparster Pfad zu einem Node oder einer Node-Property. Er kann einfach einem String zugewiesen und von ihm abgeleitet werden. Sie sind nützlich, um mit dem Baum zu interagieren, um einen Node zu erhalten, oder um Propertys zu beeinflussen, wie mit Tweens.

Vektor-Built-in-Typen

Vector2

2D Vektor-Typ bestehend aus x und y. Zugriff kann auch als Array erfolgen.

Vector2i

Dasselbe wie Vector2, aber die Komponenten sind Integer. Nützlich für die Darstellung von Elementen in einem 2D-Raster.

Rect2

2D Rechteckstypen mit zwei Vektor Feldern: position und size. Besitzt alternativ ein end Feld, welches position+size entspricht.

Vector3

3D-Vektor-Typ besitzt x, y und z-Felder. Diese können auch als Array aufgerufen werden.

Vector3i

Dasselbe wie Vector3, aber die Komponenten sind Integer-Zahlen. Kann für die Indizierung von Elementen in einem 3D-Raster verwendet werden.

Transform2D

Eine 3x2 Matrix, welche für 2D Transformationen verwendet wird.

Ebene

3D Ebenentyp in normalisierter Form, welcher ein normal-Vektorfeld und eine skalare Distanz d besitzt.

Quaternion

Quaternion ist ein Datentyp, welcher für die Repräsentation von 3D-Rotationen verwendet wird. Er ist sehr nützlich für das Interpolieren von Rotationen.

AABB

Eine achsparallele Bounding Box (oder 3D-Box) enthält 2 Vektorfelder: position und size. Enthält auch ein end-Feld, das position + size entspricht.

Basis

3x3 Matrix, welche für 3D Rotationen und Sklarierung verwendet wird. Es besitzt drei Vektorfelder (x, y und z) und kann auch als Array von 3D-Vektoren aufgerufen werden.

Transform3D

3D-Transform besitzt ein Basis-Feld basis und ein Vector3-Feld origin.

Engine-Built-in-Typen

Color

Der Color Datentyp besitzt jeweils ein r, g, b und a Feld. Der Zugriff kann auch über h, s und v erfolgen, was für hue/saturation/value (Farbton, Sättigung, Wert) steht.

RID

Ressource-ID (RID). Server nutzen generische RIDs, um undurchsichtige Daten zu referenzieren.

Object

Basisklasse für alles, was kein Built-in-Typ ist.

Container-Built-in-Typen

Array

Generische Abfolge beliebiger Objekttypen, einschließlich anderer Arrays oder Dictionarys (siehe unten). Die Größe des Arrays kann dynamisch geändert werden. Arrays werden ausgehend vom Index 0 indiziert. Negative Indizes beginnen vom Ende mit dem Zählen.

var arr = []
arr = [1, 2, 3]
var b = arr[1] # This is 2.
var c = arr[arr.size() - 1] # This is 3.
var d = arr[-1] # Same as the previous line, but shorter.
arr[0] = "Hi!" # Replacing value 1 with "Hi!".
arr.append(4) # Array is now ["Hi!", 2, 3, 4].

Typisierte Arrays

Mit Godot 4.0 wurde die Unterstützung für typisierte Arrays hinzugefügt. Bei Schreiboperationen prüft Godot, ob die Elementwerte dem angegebenen Typ entsprechen, so daß das Array keine ungültigen Werte enthalten kann. Der statische Analyzer von GDScript berücksichtigt typisierte Arrays, aber Array-Methoden wie front() und back() haben immer noch den Rückgabetyp Variant.

Typisierte Arrays haben die Syntax Array[Typ], wobei Typ ein beliebiger Variant-Typ, eine native oder Benutzerklasse oder eine Enum sein kann. Verschachtelte Array-Typen (wie Array[Array[int]]) werden nicht unterstützt.

var a: Array[int]
var b: Array[Node]
var c: Array[MyClass]
var d: Array[MyEnum]
var e: Array[Variant]

Array und Array[Variant] sind dasselbe.

Bemerkung

Arrays werden per Referenz übergeben, so dass der Typ des Array-Elements auch ein Attribut der speicherinternen Struktur ist, auf die eine Variable zur Laufzeit verweist. Der statische Typ einer Variablen schränkt die Strukturen ein, auf die sie verweisen kann. Daher können Sie einem Array keinen anderen Elementtyp zuweisen, selbst wenn der Typ ein Subtyp des erforderlichen Typs ist.

Wenn Sie ein typisiertes Array konvertieren wollen, können Sie ein neues Array erstellen und die Methode Array.assign() verwenden:

var a: Array[Node2D] = [Node2D.new()]

# (OK) You can add the value to the array because `Node2D` extends `Node`.
var b: Array[Node] = [a[0]]

# (Error) You cannot assign an `Array[Node2D]` to an `Array[Node]` variable.
b = a

# (OK) But you can use the `assign()` method instead. Unlike the `=` operator,
# the `assign()` method copies the contents of the array, not the reference.
b.assign(a)

Die einzige Ausnahme wurde für den Typ Array (Array[Variant]) gemacht, aus Gründen der Benutzerfreundlichkeit und der Kompatibilität mit altem Code. Allerdings werden Operationen auf nicht typisierte Arrays als unsicher angesehen.

Gepackte Arrays

PackedArrays are generally faster to iterate on and modify compared to a typed Array of the same type (e.g. PackedInt64Array versus Array[int]) and consume less memory. In the worst case, they are expected to be as fast as an untyped Array. Conversely, non-Packed Arrays (typed or not) have extra convenience methods such as Array.map that PackedArrays lack. Consult the class reference for details on the methods available. Typed Arrays are generally faster to iterate on and modify than untyped Arrays.

While all Arrays can cause memory fragmentation when they become large enough, if memory usage and performance (iteration and modification speed) is a concern and the type of data you're storing is compatible with one of the Packed Array types, then using those may yield improvements. However, if you do not have such concerns (e.g. the size of your array does not reach the tens of thousands of elements) it is likely more helpful to use regular or typed Arrays, as they provide convenience methods that can make your code easier to write and maintain (and potentially faster if your data requires such operations a lot). If the data you will store is of a known type (including your own defined classes), prefer to use a typed Array as it may yield better performance in iteration and modification compared to an untyped Array.

Dictionary

Assoziativer Container, der Values enthält, auf die durch eindeutige Keys verwiesen wird.

var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
    22: "value",
    "some_key": 2,
    "other_key": [2, 3, 4],
    "more_key": "Hello"
}

Die Tabellensyntax im Lua-Stil wird ebenfalls unterstützt. Der Lua-Stil verwendet = anstelle von : und verwendet keine Anführungszeichen, um String-Keys zu markieren (was etwas weniger Schreibaufwand bedeutet). Allerdings können die Keys in dieser Form nicht mit einer Ziffer beginnen (wie jeder GDScript-Bezeichner) und müssen String-Literale sein.

var d = {
    test22 = "value",
    some_key = 2,
    other_key = [2, 3, 4],
    more_key = "Hello"
}

Um einen Key zu einem bestehenden Dictionary hinzuzufügen, greifen Sie wie bei einem vorhandenen Key darauf zu und weisen Sie ihm etwas zu:

var d = {} # Create an empty Dictionary.
d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it.
d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value.
d["Godot"] = 3.01 # Add String "Godot" as a key and assign the value 3.01 to it.

var test = 4
# Prints "hello" by indexing the dictionary with a dynamic key.
# This is not the same as `d.test`. The bracket syntax equivalent to
# `d.test` is `d["test"]`.
print(d[test])

Bemerkung

Die Klammer-Syntax kann verwendet werden, um auf die Propertys jedes Objects zuzugreifen, nicht nur bei Dictionaries. Beachten Sie, dass beim Indizieren einer nicht vorhandenen Property ein Skriptfehler auftritt. Um dies zu vermeiden, verwenden Sie stattdessen die Methoden Object.get() und Object.set().

Signal

Ein Signal ist eine Nachricht, die von einem Objekt an diejenigen gesendet werden kann, die sie hören wollen. Der Typ Signal kann für die Weitergabe des Emitters verwendet werden.

Signale lassen sich besser verwenden, wenn man sie von aktuellen Objekten bezieht, z.B. $Button.button_up.

Callable

Enthält ein Objekt und eine Funktion, was für die Übergabe von Funktionen als Werte nützlich ist (z. B. beim Anbinden an Signale).

Zugriff auf eine Methode als Member gibt eine Callable zurück. var x = $Sprite2D.rotate setzt den Wert von x auf eine Callable mit $Sprite2D als Objekt und rotate als Methode.

Sie können es mit der Methode call aufrufen: x.call(PI).

Variablen

Variablen können als Klassen-Member oder lokal in Funktionen existieren. Sie werden mit dem Schlüsselwort var erzeugt und können optional mit einem Wert initialisiert werden.

var a # Data type is 'null' by default.
var b = 5
var c = 3.8
var d = b + c # Variables are always initialized in direct order (see below).

Variablen können optional eine Typspezifikation haben. Wenn ein Typ angegeben wird, muss die Variable immer denselben Typ haben, und der Versuch, einen inkompatiblen Wert zuzuweisen, führt zu einem Fehler.

Typen werden in der Variablendeklaration mit einem : (Doppelpunkt) nach dem Variablennamen, gefolgt vom Typ, angegeben.

var my_vector2: Vector2
var my_node: Node = Sprite2D.new()

Wenn die Variable innerhalb der Deklaration initialisiert wird, kann der Typ abgeleitet werden, sodass der Typname weggelassen werden kann:

var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.
var my_node := Sprite2D.new() # 'my_node' is of type 'Sprite2D'.

Typ-Inferenz ist nur möglich, wenn der zugewiesene Wert einen definierten Typ hat, andernfalls wird ein Fehler ausgelöst.

Gültige Typen sind:

  • Built-in-Typen (Array, Vector2, int, String, etc.).

  • Engine classes (Node, Resource, RefCounted, etc.).

  • Konstanten-Namen, wenn sie eine Skript-Ressource enthalten (MyScript, wenn man const MyScript = preload("res://my_script.gd") deklariert hat).

  • Andere Klassen im gleichen Skript, unter Berücksichtigung des Geltungsbereichs (InnerClass.NestedClass, wenn man class NestedClass innerhalb der class InnerClass im gleichen Geltungsbereich deklariert hat).

  • Skriptklassen, die mit dem Schlüsselwort class_name deklariert wurden.

  • Autoloads, die als Singletons registriert sind.

Bemerkung

Obwohl Variant eine gültige Typspezifikation ist, handelt es sich nicht um einen tatsächlichen Typ. Es bedeutet nur, dass es keinen festen Typ gibt und ist gleichbedeutend damit, dass sie gar keinen statischen Typ hat. Daher ist die Inferenz für Variant standardmäßig nicht erlaubt, da es sich wahrscheinlich um einen Fehler handelt.

Sie können diese Prüfung abschalten oder nur eine Warnung ausgeben, indem Sie sie in den Projekteinstellungen ändern. Siehe GDScript Warnungs-System für Details.

Initialisierungsreihenfolge

Membervariablen werden in der folgenden Reihenfolge initialisiert:

  1. Je nach statischem Typ der Variablen ist die Variable entweder null (untypisierte Variablen und Objekte) oder hat einen Defaultwert des Typs (0 für int, false für bool, etc.).

  2. Die angegebenen Werte werden in der Reihenfolge der Variablen im Skript von oben nach unten zugewiesen.

    • (Nur für von Node abgeleitete Klassen) Wenn die Annotation @onready auf eine Variable angewendet wird, wird ihre Initialisierung auf Schritt 5 verschoben.

  3. Falls definiert, wird die Methode _init() aufgerufen.

  4. Bei der Instanziierung von Szenen und Ressourcen werden die exportierten Werte zugewiesen.

  5. (Nur für von ``Node`` abgeleitete Klassen) @onready Variablen werden initialisiert.

  6. (Nur für von ``Node`` abgeleitete Klassen) Falls definiert, wird die Methode _ready() aufgerufen.

Warnung

Sie können einen komplexen Ausdruck als Variableninitialisierer angeben, einschließlich Funktionsaufrufen. Achten Sie darauf, dass die Variablen in der richtigen Reihenfolge initialisiert werden, sonst werden Ihre Werte möglicherweise überschrieben. Zum Beispiel:

var a: int = proxy("a", 1)
var b: int = proxy("b", 2)
var _data: Dictionary = {}

func proxy(key: String, value: int):
    _data[key] = value
    print(_data)
    return value

func _init() -> void:
    print(_data)

Dies gibt aus:

{ "a": 1 }
{ "a": 1, "b": 2 }
{  }

Um dies zu beheben, verschieben Sie die _data-Variablendefinition über die a-Definition oder entfernen Sie die leere Dictionary-Zuweisung (= {}).

Statische Variablen

Eine Klassen-Membervariable kann als statisch deklariert werden:

static var a

Statische Variablen gehören zur Klasse, nicht zu den Instanzen. Das bedeutet, dass statische Variablen im Gegensatz zu regulären Member-Variablen Werte zwischen mehreren Instanzen teilen.

Innerhalb einer Klasse können Sie von jeder Funktion aus auf statische Variablen zugreifen, sowohl auf statische als auch auf nicht-statische. Außerhalb der Klasse können Sie auf statische Variablen über die Klasse oder eine Instanz zugreifen (letzteres wird nicht empfohlen, da es weniger lesbar ist).

Bemerkung

Die Annotationen @export und @onready können nicht auf eine statische Variable angewendet werden. Lokale Variablen können nicht statisch sein.

Das folgende Beispiel definiert eine Klasse Person mit einer statischen Variable namens max_id. Wir inkrementieren die max_id in der _init() Funktion. Das macht es einfach, die Anzahl der Person-Instanzen in unserem Spiel zu tracken.

# person.gd
class_name Person

static var max_id = 0

var id
var name

func _init(p_name):
    max_id += 1
    id = max_id
    name = p_name

In diesem Code erzeugen wir zwei Instanzen unserer Klasse Person und überprüfen, dass die Klasse und jede Instanz den gleichen Wert für max_id haben, da die Variable statisch und für jede Instanz zugänglich ist.

# test.gd
extends Node

func _ready():
    var person1 = Person.new("John Doe")
    var person2 = Person.new("Jane Doe")

    print(person1.id) # 1
    print(person2.id) # 2

    print(Person.max_id)  # 2
    print(person1.max_id) # 2
    print(person2.max_id) # 2

Statische Variablen können Type-Hints, Setter und Getter haben:

static var balance: int = 0

static var debt: int:
    get:
        return -balance
    set(value):
        balance = -value

Auf eine statische Variable einer Basisklasse kann auch über eine Child-Klasse zugegriffen werden:

class A:
    static var x = 1

class B extends A:
    pass

func _ready():
    prints(A.x, B.x) # 1 1
    A.x = 2
    prints(A.x, B.x) # 2 2
    B.x = 3
    prints(A.x, B.x) # 3 3

@static_unload-Annotation

Da es sich bei GDScript-Klassen um Ressourcen handelt, verhindern statische Variablen in einem Skript, dass es entladen wird, auch wenn es keine Instanzen dieser Klasse und keine anderen Referenzen mehr gibt. Dies kann wichtig sein, wenn statische Variablen große Datenmengen speichern oder Referenzen auf andere Projektressourcen, wie z.B. Szenen, enthalten. Sie sollten diese Daten manuell bereinigen oder die Annotation @static_unload verwenden, wenn die statischen Variablen keine wichtigen Daten speichern und zurückgesetzt werden können.

Warnung

Derzeit werden Skripte aufgrund eines Bugs nie freigegeben, selbst wenn die Annotation @static_unload verwendet wird.

Beachten Sie, dass @static_unload für das gesamte Skript gilt (einschließlich der inneren Klassen) und am Anfang des Skripts stehen muss, vor class_name und extends:

@static_unload
class_name MyNode
extends Node

Siehe auch Statische Funktionen und Statischer Konstruktor.

Casting

Werte, die typisierten Variablen zugewiesen werden, müssen einen kompatiblen Typ haben. Wenn es erforderlich ist, einen Wert zu zwingen, einen bestimmten Typ zu haben, insbesondere bei Objekttypen, können Sie den Casting-Operator as verwenden.

Das Casten zwischen Objekttypen führt zu demselben Objekt, wenn der Wert vom selben Typ oder einem Untertyp des Cast-Typs ist.

var my_node2D: Node2D
my_node2D = $Sprite2D as Node2D # Works since Sprite2D is a subtype of Node2D.

Wenn der Wert kein Subtyp ist, führt die Casting-Operation zu einem null -Wert.

var my_node2D: Node2D
my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.

Bei Built-in-Typen werden sie nach Möglichkeit zwangsweise konvertiert, da sonst die Engine einen Fehler auslöst.

var my_int: int
my_int = "123" as int # The string can be converted to int.
my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error.

Casting ist auch nützlich, um bei der Interaktion mit dem Szenenbaum bessere typsichere Variablen zu haben:

# Will infer the variable to be of type Sprite2D.
var my_sprite := $Character as Sprite2D

# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.
($AnimPlayer as AnimationPlayer).play("walk")

Konstanten

Konstanten sind Werte, die Sie nicht ändern können während das Spiel läuft, ihr Wert muss zur Kompilierungszeit bekannt sein. Mit dem Schlüsselwort const können Sie einem konstanten Wert einen Namen geben. Wenn Sie versuchen, einer Konstanten nach ihrer Deklaration einen Wert zuzuweisen, wird ein Fehler angezeigt.

Wir empfehlen die Verwendung von Konstanten, wenn sich ein Wert nicht ändern soll.

const A = 5
const B = Vector2(20, 20)
const C = 10 + 20 # Constant expression.
const D = Vector2(20, 30).x # Constant expression: 20.
const E = [1, 2, 3, 4][0] # Constant expression: 1.
const F = sin(20) # 'sin()' can be used in constant expressions.
const G = x + 20 # Invalid; this is not a constant expression!
const H = A + 20 # Constant expression: 25 (`A` is a constant).

Obwohl der Typ der Konstanten aus dem zugewiesenen Wert abgeleitet wird, ist es auch möglich, eine explizite Typspezifikation hinzuzufügen:

const A: int = 5
const B: Vector2 = Vector2()

Das Zuweisen eines Werts eines inkompatiblen Typs führt zu einem Fehler.

Sie können auch Konstanten innerhalb einer Funktion erstellen, was nützlich ist, um lokale "Magic Numbers"mit Namen zu versehen.

Enums

Enums sind im Grunde ein Kürzel für Konstanten und sind nützlich, wenn Sie aufeinanderfolgende Integer zu einer Kontanten zuweisen möchten.

enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3

Wenn Sie dem Enum einen Namen übergeben, wird es alle Keys in ein konstantes Dictionary mit diesem Namen einfügen. Das bedeutet, dass alle konstanten Methoden eines Dictionarys auch mit einem benannten Enum verwendet werden können.

Wichtig

Keys in einem benannten Enum werden nicht als globale Konstanten registriert. Auf sie sollte mit dem Präfix des Enum-Namens zugegriffen werden (Name.KEY).

enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}

# Is the same as:
const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6}
# Access values with State.STATE_IDLE, etc.

func _ready():
    # Access values with Name.KEY, prints '5'
    print(State.STATE_JUMP)
    # Use dictionary methods:
    # prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
    print(State.keys())
    # prints '{ "STATE_IDLE": 0, "STATE_JUMP": 5, "STATE_SHOOT": 6 }'
    print(State)
    # prints '[0, 5, 6]'
    print(State.values())

If not assigning a value to a key of an enum it will be assigned the previous value plus one, or 0 if it is the first entry in the enum. Multiple keys with the same value are allowed.

Funktionen

Functions always belong to a class. The scope priority for variable look-up is: local → class member → global. The self variable is always available and is provided as an option for accessing class members (see self), but is not always required (and should not be sent as the function's first argument, unlike Python).

func my_function(a, b):
    print(a)
    print(b)
    return a + b  # Return is optional; without it 'null' is returned.

Eine Funktion kann jederzeit per return zurückkehren. Der Default-Rückgabewert ist null.

Wenn eine Funktion nur eine Zeile Code enthält, kann sie in eine Zeile geschrieben werden:

func square(a): return a * a

func hello_world(): print("Hello World")

func empty_function(): pass

Funktionen können auch eine Typspezifikation für die Argumente und für den Rückgabewert haben. Typen für Argumente können auf ähnliche Weise wie Variablen hinzugefügt werden:

func my_function(a: int, b: String):
    pass

Wenn ein Funktionsargument einen Defaultwert hat, kann auf den Typ geschlossen werden:

func my_function(int_arg := 42, String_arg := "string"):
    pass

Der Rückgabetyp der Funktion kann nach der Argumentliste mit dem Pfeiltoken (->) angegeben werden:

func my_int_function() -> int:
    return 0

Funktionen mit einem Rückgabetyp müssen einen richtigen Wert zurückgeben. Wenn Sie den Typ auf void setzen, gibt die Funktion nichts zurück. Void-Funktionen können mit dem Schlüsselwort return frühzeitig beendet werden, sie können jedoch keinen Wert zurückgeben.

func void_function() -> void:
    return # Can't return a value.

Bemerkung

Nicht-void-Funktionen müssen immer einen Wert zurückgeben. Wenn Ihr Code also Verzweigungsanweisungen enthält (z.B. ein if/else-Konstrukt), müssen alle möglichen Pfade eine Rückgabe haben. Wenn Sie beispielsweise ein return in einem if-Block haben, aber nicht danach, gibt der Editor einen Fehler aus, da die Funktion keinen gültigen Wert für die Rückgabe hat, wenn der Block nicht ausgeführt wird.

Funktionen referenzieren

Funktionen sind erstklassige Werte im Sinne des Callable-Objekts. Wenn Sie eine Funktion mit Namen referenzieren, ohne sie aufzurufen, wird automatisch die richtige aufrufbare Funktion generiert. Dies kann verwendet werden, um Funktionen als Argumente zu übergeben.

func map(arr: Array, function: Callable) -> Array:
    var result = []
    for item in arr:
        result.push_back(function.call(item))
    return result

func add1(value: int) -> int:
    return value + 1;

func _ready() -> void:
    var my_array = [1, 2, 3]
    var plus_one = map(my_array, add1)
    print(plus_one) # Prints `[2, 3, 4]`.

Bemerkung

Callables müssen mit der Methode call() aufgerufen werden. Sie können den Operator () nicht direkt verwenden. Dieses Verhalten wird implementiert, um Leistungsprobleme bei direkten Funktionsaufrufen zu vermeiden.

Lambda-Funktionen

Mit Lambda-Funktionen können Sie Funktionen deklarieren, die nicht zu einer Klasse gehören. Stattdessen wird ein Callable-Objekt erstellt und direkt einer Variablen zugewiesen. Dies kann nützlich sein, um aufrufbare Objekte zu erstellen, die weitergegeben werden können, ohne den Klassenbereich zu beeinträchtigen.

var lambda = func (x):
    print(x)

Um das erstellte Lambda aufzurufen, können Sie die Methode call() verwenden:

lambda.call(42) # Prints `42`.

Lambda-Funktionen können zu Debugzwecken benannt werden (der Name wird im Debugger angezeigt):

var lambda = func my_lambda(x):
    print(x)

Sie können Typhinweise für Lambda-Funktionen auf die gleiche Weise angeben wie für normale Funktionen:

var lambda := func (x: int) -> void:
    print(x)

Beachten Sie, dass, wenn Sie einen Wert aus einem Lambda zurückgeben wollen, ein explizites return erforderlich ist (Sie können return nicht weglassen):

var lambda = func (x): return x ** 2
print(lambda.call(2)) # Prints `4`.

Lambda-Funktionen erfassen die lokale Umgebung:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
lambda.call()

Warnung

Lokale Variablen werden einmal nach Wert erfasst, wenn das Lambda erstellt wird. Sie werden daher im Lambda nicht aktualisiert, wenn sie in der äußeren Funktion neu zugewiesen werden:

var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`.

Außerdem kann ein Lambda eine äußere lokale Variable nicht neu zuweisen. Nach dem Verlassen des Lambdas bleibt die Variable unverändert, da die Lambda-Erfassung sie implizit überschattet:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
    x = "Hello" # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(x) # Prints `Hello`.
lambda.call()
print(x) # Prints `42`.

Wenn Sie jedoch Datentypen mit Übergabe per Referenz verwenden (Arrays, Wörterbücher und Objekte), werden die Inhaltsänderungen weitergegeben, bis Sie die Variable neu zuweisen:

var a = []
var lambda = func ():
    a.append(1)
    print(a) # Prints `[1]`.
    a = [2] # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(a) # Prints `[2]`.
lambda.call()
print(a) # Prints `[1]`.

Statische Funktionen

Eine Funktion kann als statisch deklariert werden. Wenn eine Funktion statisch ist, hat sie keinen Zugriff auf die Member-Variablen der Instanz oder self. Eine statische Funktion hat Zugriff auf statische Variablen. Auch statische Funktionen sind nützlich, um Bibliotheken von Hilfsfunktionen zu erstellen:

static func sum2(a, b):
    return a + b

Lambda-Funktionen können nicht als statisch deklariert werden.

Siehe auch Statische Variablen und Statischer Konstruktor.

Anweisungen und Kontrollfluss

Ausdrücke sind Standard und können Zuweisungen sein, Funktionsaufrufe, Kontrollfluss-Strukturen, etc. (siehe unten). ; als Trennzeichen zwischen Ausdrücken ist optional.

Ausdrücke

Ausdrücke sind Folgen von Operatoren und ihren Operanden in geordneter Weise. Ein Ausdruck selbst kann auch eine Anweisung sein, allerdings sind nur Aufrufe als Anweisungen sinnvoll, da andere Ausdrücke keine Nebeneffekte haben.

Ausdrücke geben Werte zurück, die gültigen Zielen zugewiesen werden können. Operanden für einen Operator können ein anderer Ausdruck sein. Eine Zuweisung ist kein Ausdruck und gibt daher auch keinen Wert zurück.

Hier sind einige Beispiele für Ausdrücke:

2 + 2 # Binary operation.
-5 # Unary operation.
"okay" if x > 4 else "not okay" # Ternary operation.
x # Identifier representing variable or constant.
x.a # Attribute access.
x[4] # Subscript access.
x > 2 or x < 5 # Comparisons and logic operators.
x == y + 2 # Equality test.
do_something() # Function call.
[1, 2, 3] # Array definition.
{A = 1, B = 2} # Dictionary definition.
preload("res://icon.png") # Preload builtin function.
self # Reference to current instance.

Bezeichner, Attribute und Element-Zugriffe sind gültige Zuweisungsziele. Andere Ausdrücke können nicht auf der linken Seite einer Zuweisung stehen.

self

self can be used to refer to the current instance and is often equivalent to directly referring to symbols available in the current script. However, self also allows you to access properties, methods, and other names that are defined dynamically (i.e. are expected to exist in subtypes of the current class, or are provided using _set() and/or _get()).

extends Node

func _ready():
    # Compile time error, as `my_var` is not defined in the current class or its ancestors.
    print(my_var)
    # Checked at runtime, thus may work for dynamic properties or descendant classes.
    print(self.my_var)

    # Compile time error, as `my_func()` is not defined in the current class or its ancestors.
    my_func()
    # Checked at runtime, thus may work for descendant classes.
    self.my_func()

Warnung

Beware that accessing members of child classes in the base class is often considered a bad practice, because this blurs the area of responsibility of any given piece of code, making the overall relationship between parts of your game harder to reason about. Besides that, one can simply forget that the parent class had some expectations about it's descendants.

if/else/elif

Einfache Bedingungen werden mit der if/else/elif Syntax erstellt. Klammern um die Bedingungen zu setzten ist möglich, aber nicht zwingend notwendig. Angesichts der Tabulator-basierten Einrückung kann elif anstelle von else/if verwendet werden, um auf dem selben Level der Einrückung zu bleiben.

if (expression):
    statement(s)
elif (expression):
    statement(s)
else:
    statement(s)

Kurze Anweisungen können in dieselbe Zeile wie die Bedingung geschrieben werden:

if 1 + 1 == 2: return 2 + 2
else:
    var x = 3 + 3
    return x

Manchmal möchte man vielleicht einen unterschiedlichen initialen Wert basierend auf einem booleschen Ausdruck zuweisen. Hierfür ist der ternäre If-Ausdruck nützlich:

var x = (value) if (expression) else (value)
y += 3 if y < 10 else -1

Ternäre-if-Ausdrücke können verschachtelt werden, um mehr als 2 Fälle zu behandeln. Bei der Verschachtelung von ternären if-Ausdrücken wird empfohlen, den gesamten Ausdruck über mehrere Zeilen zu verteilen, um die Lesbarkeit zu erhalten:

var count = 0

var fruit = (
        "apple" if count == 2
        else "pear" if count == 1
        else "banana" if count == 0
        else "orange"
)
print(fruit)  # banana

# Alternative syntax with backslashes instead of parentheses (for multi-line expressions).
# Less lines required, but harder to refactor.
var fruit_alt = \
        "apple" if count == 2 \
        else "pear" if count == 1 \
        else "banana" if count == 0 \
        else "orange"
print(fruit_alt)  # banana

Man kann auch prüfen, ob ein Wert in etwas enthalten ist. Dazu können Sie eine if-Anweisung in Kombination mit dem in-Operator verwenden:

# Check if a letter is in a string.
var text = "abc"
if 'b' in text: print("The string contains b")

# Check if a variable is contained within a node.
if "varName" in get_parent(): print("varName is defined in parent!")

while

Einfache Schleifen werden mit der while-Syntax erstellt. Schleifen können mit break unterbrochen oder mit continue fortgesetzt werden (wodurch zur nächsten Iteration der Schleife übergegangen wird, ohne dass in der aktuellen Iteration weiterer Code ausgeführt wird):

while (expression):
    statement(s)

for

Um eine Range wie ein Array oder eine Tabelle zu durchlaufen, wird eine for -Schleife verwendet. Beim Iterieren über ein Array wird das aktuelle Array-Element in der Schleifenvariablen gespeichert. Beim Durchlaufen eines Dictionary wird der Key in der Schleifenvariablen gespeichert.

for x in [5, 7, 11]:
    statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names: # Typed loop variable.
    print(name) # Prints name's content.

var dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
    print(dict[i]) # Prints 0, then 1, then 2.

for i in range(3):
    statement # Similar to [0, 1, 2] but does not allocate an array.

for i in range(1, 3):
    statement # Similar to [1, 2] but does not allocate an array.

for i in range(2, 8, 2):
    statement # Similar to [2, 4, 6] but does not allocate an array.

for i in range(8, 2, -2):
    statement # Similar to [8, 6, 4] but does not allocate an array.

for c in "Hello":
    print(c) # Iterate through all characters in a String, print every letter on new line.

for i in 3:
    statement # Similar to range(3).

for i in 2.2:
    statement # Similar to range(ceil(2.2)).

Wenn Sie einem Array Werte zuweisen wollen, während es durchlaufen wird, ist es am besten, for i in array.size() zu verwenden.

for i in array.size():
    array[i] = "Hello World"

Die Schleifenvariable ist in der for-Schleife lokal, und die Zuweisung an sie ändert den Wert im Array nicht. Objekte, die als Referenz übergeben werden (wie z. B. Nodes), können weiterhin durch den Aufruf von Methoden auf der Schleifenvariable manipuliert werden.

for string in string_array:
    string = "Hello World" # This has no effect

for node in node_array:
    node.add_to_group("Cool_Group") # This has an effect

match

Eine match-Anweisung benutzt man, um die Ausführung des Programms zu verzweigen. Sie ist äquivalent zur switch-Anweisung, wie sie aus vielen anderen Sprachen bekannt ist, aber bietet darüberhinaus zusätzliche Funktionalität.

Warnung

match ist strenger als der Operator ==. Zum Beispiel entspricht 1 nicht 1.0. Die einzige Ausnahme ist die Übereinstimmung von String und StringName: zum Beispiel wird der String "hallo" als gleichwertig mit dem StringName &"hallo" angesehen.

Grundlegende Syntax

match <test value>:
    <pattern(s)>:
        <block>
    <pattern(s)> when <pattern guard>:
        <block>
    <...>

Crashkurs für jene, die mit switch-Anweisungen vertraut sind

  1. Ersetze switch mit match.

  2. Entferne case.

  3. Entferne alle breaks.

  4. Ändere default zu einem einzelnen Unterstrich.

Kontrollfluss

Die Muster werden von oben nach unten geprüft. Wenn ein Muster übereinstimmt, wird der erste entsprechende Block ausgeführt. Danach wird die Ausführung unterhalb der match-Anweisung fortgesetzt.

Bemerkung

Das in Version 3.x unterstützte spezielle Verhalten von continue in match wurde in Godot 4.0 entfernt.

Die folgenden Mustertypen sind verfügbar:

  • Literal-Muster

    Passt auf ein Literal:

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • Ausdruck-Muster

    Passt auf einen konstanten Ausdruck, einen Bezeichner oder einen Attributzugriff (A.B):

    match typeof(x):
        TYPE_FLOAT:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • Wildcard-Muster

    Dieses Muster passt auf alles. Es wird als einzelner Unterstrich geschrieben.

    Es kann als Äquivalent zu default in einer switch-Anweisung in anderen Sprachen verwendet werden:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        _:
            print("It's not 1 or 2. I don't care to be honest.")
    
  • Binding-Muster

    Ein Binding-Muster erzeugt eine neue Variable. Wie das Wildcard-Muster passt es auf alles und gibt diesem Wert einen Namen. Es ist besonders hilfreich beim Array- und Dictionary-Muster:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        var new_var:
            print("It's not 1 or 2, it's ", new_var)
    
  • Array-Muster

    Passt auf ein Array. Jedes einzelne Element des Array-Musters ist selbst ein Muster, sodass Sie sie verschachteln können.

    Die Länge des Arrays wird zuerst getestet. Sie muss dieselbe Größe wie das Muster haben, andernfalls passt das Muster nicht.

    Array mit offenem Ende: Ein Array kann größer als das Muster sein, indem das letzte Untermuster mit .. erstellt wird.

    Jedes Untermuster muss durch Kommas getrennt werden.

    match x:
        []:
            print("Empty array")
        [1, 3, "test", null]:
            print("Very specific array")
        [var start, _, "test"]:
            print("First element is ", start, ", and the last is \"test\"")
        [42, ..]:
            print("Open ended array")
    
  • Dictionary-Muster

    Funktioniert auf die gleiche Weise wie das Array-Muster, jeder Key muss ein konstantes Muster haben.

    Die Größe des Dictionary wird zuerst getestet. Es muss dieselbe Größe wie das Muster haben, andernfalls passt das Muster nicht.

    Dictionary mit offenem Ende: Ein Dictionary kann größer als das Muster sein, indem das letzte Untermuster mit .. erstellt wird.

    Jede Untermuster muss durch ein Komma getrennt werden.

    Wenn Sie keinen Value angeben, wird nur das Vorhandensein des Keys geprüft.

    Ein Value-Muster wird durch ein : vom Key-Muster getrennt.

    match x:
        {}:
            print("Empty dict")
        {"name": "Dennis"}:
            print("The name is Dennis")
        {"name": "Dennis", "age": var age}:
            print("Dennis is ", age, " years old.")
        {"name", "age"}:
            print("Has a name and an age, but it's not Dennis :(")
        {"key": "godotisawesome", ..}:
            print("I only checked for one entry and ignored the rest")
    
  • Mehrere Muster

    Sie können auch mehrere durch Komma getrennte Muster angeben. Diese Muster dürfen keine Bindings enthalten.

    match x:
        1, 2, 3:
            print("It's 1 - 3")
        "Sword", "Splash potion", "Fist":
            print("Yep, you've taken damage")
    

Muster-Guards

A pattern guard is an optional condition that follows the pattern list and allows you to make additional checks before choosing a match branch. Unlike a pattern, a pattern guard can be an arbitrary expression.

Only one branch can be executed per match. Once a branch is chosen, the rest are not checked. If you want to use the same pattern for multiple branches or to prevent choosing a branch with too general pattern, you can specify a pattern guard after the list of patterns with the when keyword:

match point:
    [0, 0]:
        print("Origin")
    [_, 0]:
        print("Point on X-axis")
    [0, _]:
        print("Point on Y-axis")
    [var x, var y] when y == x:
        print("Point on line y = x")
    [var x, var y] when y == -x:
        print("Point on line y = -x")
    [var x, var y]:
        print("Point (%s, %s)" % [x, y])
  • If there is no matching pattern for the current branch, the pattern guard is not evaluated and the patterns of the next branch are checked.

  • If a matching pattern is found, the pattern guard is evaluated.

    • Wenn er "true" ist, wird der Rumpf der Verzweigung ausgeführt und match endet.

    • Wenn sie "false" ist, werden die Muster der nächsten Verzweigung geprüft.

Klassen

Standardmäßig sind alle Skriptdateien namenlose Klassen. In diesem Fall können Sie sie nur über den Dateipfad referenzieren, entweder über einen relativen oder einen absoluten Pfad. Wenn Sie beispielsweise eine Skriptdatei character.gd benennen:

# Inherit from 'character.gd'.

extends "res://path/to/character.gd"

# Load character.gd and create a new node instance from it.

var Character = load("res://path/to/character.gd")
var character_node = Character.new()

Registrierung von benannten Klassen

Sie können Ihrer Klasse einen Namen geben, um sie als neuen Typ im Editor von Godot zu registrieren. Dazu verwenden Sie das Schlüsselwort class_name. Optional können Sie die Annotation @icon mit einem Pfad zu einem Bild verwenden, um es als Icon zu benutzen. Ihre Klasse wird dann mit ihrem neuen Icon im Editor erscheinen:

# item.gd

@icon("res://interface/icons/item.png")
class_name Item
extends Node
../../../_images/class_name_editor_register_example.png

Tipp

Bei SVG-Bildern, die als benutzerdefinierte Node-Icons verwendet werden, sollten die Optionen Editor > Mit Editor-Skalierung skalieren und Editor > Icons mit Editor-Theme konvertieren Importoptionen aktiviert sein. Dies ermöglicht es den Icons, den Skalierungs- und Theme-Einstellungen des Editors zu folgen, wenn die Icons mit der gleichen Farbpalette wie die Godot-eigenen Icons gestaltet sind.

Hier ist ein Beispiel für eine Klassendatei:

# Saved as a file named 'character.gd'.

class_name Character


var health = 5


func print_health():
    print(health)


func print_this_script_three_times():
    print(get_script())
    print(ResourceLoader.load("res://character.gd"))
    print(Character)

Wenn Sie auch extends verwenden wollen, können Sie beides in der gleichen Zeile unterbringen:

class_name MyNode extends Node

Bemerkung

Godot initialisiert nicht-statische Variablen jedes Mal, wenn Sie eine Instanz erstellen, und dies schließt Arrays und Dictionarys ein. Dies ist im Sinne der Thread-Sicherheit, da Skripte in separaten Threads initialisiert werden können, ohne dass der Benutzer dies merkt.

Warnung

The Godot editor will hide these custom classes with names that begin with the prefix "Editor" in the 'Create New Node' or 'Create New Scene' dialog windows. The classes are available for instantiation at runtime via their class names, but are automatically hidden by the editor windows along with the built-in editor nodes used by the Godot editor.

Vererbung

Eine Klasse (gespeichert als Datei) kann erben von:

  • Einer globalen Klasse.

  • Einer anderen Klassendatei.

  • Eine inneren Klasse innerhalb einer anderen Klassendatei.

Mehrfach-Vererbung ist nicht erlaubt.

Vererbung verwendet das Schlüsselwort extends:

# Inherit/extend a globally available class.
extends SomeClass

# Inherit/extend a named class file.
extends "somefile.gd"

# Inherit/extend an inner class in another file.
extends "somefile.gd".SomeInnerClass

Bemerkung

Wenn die Vererbung nicht ausdrücklich definiert ist, erbt die Klasse standardmäßig von RefCounted.

Um zu prüfen, ob eine gegebene Instanz von einer gegebenen Klasse erbt, verwendet man das Schlüsselwort is:

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# [...]

# Use 'is' to check inheritance.
if entity is Enemy:
    entity.apply_damage()

Um eine Funktion in einer Superklasse aufzurufen (d.h. eine, die in Ihrer aktuellen Klasse erweitert wurde), verwenden Sie das Schlüsselwort super:

super(args)

Dies ist besonders nützlich, weil Funktionen in erweiternden Klassen Funktionen mit demselben Namen in ihren Superklassen ersetzen. Wenn Sie sie trotzdem aufrufen wollen, können Sie super verwenden:

func some_func(x):
    super(x) # Calls the same function on the super class.

Wenn Sie eine andere Funktion aus der Superklasse aufrufen müssen, können Sie den Funktionsnamen mit dem Attribut-Operator angeben:

func overriding():
    return 0 # This overrides the method in the base class.

func dont_override():
    return super.overriding() # This calls the method as defined in the base class.

Warnung

Eines der häufigsten Mißverständnisse ist der Versuch, nichtvirtuelle Engine-Methoden wie get_class(), queue_free(), etc. zu überschreiben. Dies wird aus technischen Gründen nicht unterstützt.

In Godot 3 können Sie Methoden der Engine in GDScript per Shadowing überlagern, und es wird funktionieren, wenn Sie diese Methode in GDScript aufrufen. Allerdings wird die Engine Ihren Code nicht ausführen, wenn die Methode innerhalb der Engine bei einem Event aufgerufen wird.

In Godot 4 funktioniert sogar das Shadowing nicht immer, da GDScript die nativen Methodenaufrufe optimiert. Daher haben wir die Warnung NATIVE_METHOD_OVERRIDE hinzugefügt, die standardmäßig als Fehler behandelt wird. Wir raten dringend davon ab, diese Warnung zu deaktivieren oder zu ignorieren.

Beachten Sie, dass dies nicht für virtuelle Methoden wie _ready(), _process() und andere gilt (in der Dokumentation mit dem Qualifier virtual gekennzeichnet, und die Namen beginnen mit einem Unterstrich). Diese Methoden sind speziell für die Anpassung des Verhaltens der Engine gedacht und können in GDScript überschrieben werden. Signale und Benachrichtigungen können für diese Zwecke ebenfalls nützlich sein.

Klassen-Konstruktor

Der Klassenkonstruktor, der bei der Klasseninstanziierung aufgerufen wird, heißt _init. Wenn Sie den Basisklassenkonstruktor aufrufen möchten, können Sie auch die „super“-Syntax verwenden. Beachten Sie, dass jede Klasse einen impliziten Konstruktor hat, der immer aufgerufen wird (und die Standardwerte der Klassenvariablen definiert). super wird verwendet, um den expliziten Konstruktor aufzurufen:

func _init(arg):
   super("some_default", arg) # Call the custom base constructor.

Dies lässt sich besser anhand von Beispielen erklären. Betrachten Sie dieses Szenario:

# state.gd (inherited class).
var entity = null
var message = null


func _init(e=null):
    entity = e


func enter(m):
    message = m


# idle.gd (inheriting class).
extends "state.gd"


func _init(e=null, m=null):
    super(e)
    # Do something with 'e'.
    message = m

Hier müssen ein paar Sachen berücksichtigt werden:

  1. Wenn die geerbte Klasse (state.gd) einen _init-Konstruktor definiert, der Argumente entgegennimmt (in diesem Fall e), dann muss die erbende Klasse (idle.gd) _init ebenfalls definieren und entsprechende Parameter an _init von state.gd übergeben.

  2. idle.gd kann eine andere Anzahl von Argumenten haben als die Basisklasse state.gd.

  3. Im obigen Beispiel ist e, das an den Konstruktor state.gd übergeben wird, das gleiche e, das an idle.gd übergeben wird.

  4. Wenn der _init-Konstruktor von idle.gd 0 Argumente annimmt, muss er trotzdem einen Wert an die Basisklasse state.gd übergeben, auch wenn er nichts tut. Das bringt uns zu der Tatsache, dass man auch Ausdrücke an den Basiskonstruktor übergeben kann, nicht nur Variablen, z.B.:

    # idle.gd
    
    func _init():
        super(5)
    

Statischer Konstruktor

Ein statischer Konstruktor ist eine statische Funktion _static_init, die automatisch beim Laden der Klasse aufgerufen wird, nachdem die statischen Variablen initialisiert worden sind:

static var my_static_var = 1

static func _static_init():
    my_static_var = 2

Ein statischer Konstruktor kann keine Argumente annehmen und darf keinen Wert zurückgeben.

Innere Klassen

Eine Klassendatei kann innere Klassen beinhalten. Innere Klassen werden durch das class-Schlüsselwort definiert. Sie werden mithilfe der ClassName.new()-Funktion instanziert.

# Inside a class file.

# An inner class in this class file.
class SomeInnerClass:
    var a = 5


    func print_value_of_a():
        print(a)


# This is the constructor of the class file's main class.
func _init():
    var c = SomeInnerClass.new()
    c.print_value_of_a()

Klassen als Ressourcen

Als Dateien gespeicherte Klassen werden als GDScripts behandelt. Sie müssen von der Festplatte geladen werden, um auf sie in anderen Klassen zuzugreifen. Dies geschieht entweder mit den Funktionen load oder preload (siehe unten). Die Instanziierung einer geladenen Klassenressource erfolgt durch den Aufruf der Funktion new für das Klassenobjekt:

# Load the class resource when calling load().
var MyClass = load("myclass.gd")

# Preload the class only once at compile time.
const MyClass = preload("myclass.gd")


func _init():
    var a = MyClass.new()
    a.some_function()

Exporte

Bemerkung

Die Dokumentation über Exportieren wurde nach GDScript exportierte Propertys verschoben.

Propertys (set/get)

Manchmal möchte man, dass eine Member-Variable einer Klasse mehr tut, als nur Daten zu speichern und bei jeder Änderung ihres Wertes eine Validierung oder Berechnung durchzuführen. Es kann auch erwünscht sein, den Zugriff auf sie in irgendeiner Weise zu kapseln.

Hierfür bietet GDScript eine spezielle Syntax zur Definition von Propertys mit den Schlüsselwörtern set und get nach einer Variablendeklaration. Anschließend können Sie einen Codeblock definieren, der ausgeführt wird, wenn auf die Variable zugegriffen wird oder sie zugewiesen wird.

Beispiel:

var milliseconds: int = 0
var seconds: int:
    get:
        return milliseconds / 1000
    set(value):
        milliseconds = value * 1000

Bemerkung

Im Gegensatz zu setget in früheren Godot-Versionen werden die Proprety-Setter und -Getter immer aufgerufen (außer wie unten angegeben), auch wenn auf sie innerhalb derselben Klasse zugegriffen wird (mit oder ohne Voranstellen von self.). Dies macht das Verhalten konsistent. Wenn Sie direkten Zugriff auf den Wert benötigen, verwenden Sie eine andere Variable für den direkten Zugriff und lassen den Property-Code diesen Namen verwenden.

Alternative Syntax

Es gibt auch eine andere Notation, um bestehende Klassenfunktionen zu verwenden, wenn Sie den Code von der Variablendeklaration trennen wollen oder den Code für mehrere Propertys wiederverwenden müssen (aber Sie können nicht unterscheiden, für welche Property der Setter/Getter aufgerufen wird):

var my_prop:
    get = get_my_prop, set = set_my_prop

Dies kann auch in der gleichen Zeile geschehen:

var my_prop: get = get_my_prop, set = set_my_prop

Der Setter und der Getter müssen dieselbe Notation verwenden, das Mischen von Stilen für dieselbe Variable ist nicht erlaubt.

Bemerkung

Für inline Setter und Getter können keine Typ-Hinweise angegeben werden. Dies geschieht absichtlich, um Boilerplate-Code zu reduzieren. Wenn die Variable typisiert ist, dann ist das Argument des Setters automatisch vom gleichen Typ und der Rückgabewert des Getters muss damit übereinstimmen. Getrennte Setter/Getter-Funktionen können Type-Hints haben, und der Typ muss mit dem Typ der Variablen übereinstimmen oder ein breiterer Typ sein.

Wann setter/getter nicht aufgerufen werden

Wenn eine Variable initialisiert wird, wird der Wert des Initialisierers direkt in die Variable geschrieben. Auch wenn die Annotation @onready auf die Variable angewendet wird.

Die Verwendung des Variablennamens, um ihn in seinem eigenen Setter zu setzen oder in seinem eigenen Getter zu holen, greift direkt auf den zugrundeliegenden Member zu, erzeugt also keine unendliche Rekursion und erspart Ihnen die explizite Deklaration einer weiteren Variable:

signal changed(new_value)
var warns_when_changed = "some value":
    get:
        return warns_when_changed
    set(value):
        changed.emit(value)
        warns_when_changed = value

Das gilt auch für die alternative Syntax:

var my_prop: set = set_my_prop

func set_my_prop(value):
    my_prop = value # No infinite recursion.

Warnung

Die Ausnahme wird nicht auf andere Funktionen übertragen, die im Setter/Getter aufgerufen werden. Der folgende Code zum Beispiel verursacht eine unendliche Rekursion:

var my_prop:
    set(value):
        set_my_prop(value)

func set_my_prop(value):
    my_prop = value # Infinite recursion, since `set_my_prop()` is not the setter.

Tool-Modus

Standardmäßig werden Skripte nicht im Editor ausgeführt und nur die exportierten Propertys können geändert werden. In einigen Fällen ist es erwünscht, dass sie innerhalb des Editors laufen (solange sie keinen Spielcode ausführen oder dies manuell vermeiden). Hierfür gibt es die Annotation @tool, die am Anfang der Datei platziert werden muss:

@tool
extends Button

func _ready():
    print("Hello")

Siehe Code im Editor ausführen für weitere Informationen.

Warnung

Seien Sie vorsichtig, wenn Sie Nodes mit queue_free() oder free() in einem Tool-Skript freigeben (insbesondere der Eigentümer des Skripts selbst). Da Tool-Skripte ihren Code im Editor ausführen, kann ihr Missbrauch zum Absturz des Editors führen.

Speicher-Management

Godot implementiert die Reference Counting, um bestimmte Instanzen, die nicht mehr verwendet werden, freizugeben, anstatt einen Garbage Collector einzusetzen oder eine rein manuelle Verwaltung zu erfordern. Jede Instanz der Klasse RefCounted (oder jeder Klasse, die sie erbt, wie z.B. Resource) wird automatisch freigegeben, wenn sie nicht mehr verwendet wird. Eine Instanz einer Klasse, die keine RefCounted ist (wie Node oder der Basistyp Object), bleibt im Speicher, bis sie mit free() (oder queue_free() für Nodes) gelöscht wird.

Bemerkung

Wenn ein Node mittels free() oder queue_free() gelöscht wird, werden auch alle seine Unter-Nodes rekursiv gelöscht.

Um Referenzzyklen zu vermeiden, die nicht freigegeben werden können, wird eine WeakRef-Funktion zur Verfügung gestellt, um schwache Referenzen zu erzeugen, die den Zugriff auf das Objekt erlauben, ohne ein RefCounted am Freigeben zu hindern. Hier ist ein Beispiel:

extends Node

var my_file_ref

func _ready():
    var f = FileAccess.open("user://example_file.json", FileAccess.READ)
    my_file_ref = weakref(f)
    # the FileAccess class inherits RefCounted, so it will be freed when not in use

    # the WeakRef will not prevent f from being freed when other_node is finished
    other_node.use_file(f)

func _this_is_called_later():
    var my_file = my_file_ref.get_ref()
    if my_file:
        my_file.close()

Wenn keine Referenzen verwendet werden, kann alternativ mit is_instance_valid(instance) überprüft werden, ob ein Objekt freigegeben wurde.

Signale

Signale sind ein Werkzeug zum Senden von Nachrichten von einem Objekt, auf die andere Objekte reagieren können. Verwenden Sie das Schlüsselwort signal, um benutzerdefinierte Signale für eine Klasse zu erstellen.

extends Node


# A signal named health_depleted.
signal health_depleted

Bemerkung

Signale sind ein Callback-Mechanismus. Sie übernehmen auch die Rolle von Observern, einem gängigen Programmier-Pattern. Weitere Informationen finden Sie im Observer-Tutorial (in englischer Sprache) im E-Book Game Programming Patterns.

Sie können diese Signale mit Methoden auf die gleiche Weise verbinden, wie Sie Built-in-Signale von Nodes wie Button oder RigidBody3D verbinden.

Im Beispiel unten verbinden wir das Signal health_depleted von einem Character-Node mit einem Game-Node. Wenn der Charakter-Node das Signal aussendet, wird der _on_character_health_depleted-Node des Spiels aufgerufen:

# game.gd

func _ready():
    var character_node = get_node('Character')
    character_node.health_depleted.connect(_on_character_health_depleted)


func _on_character_health_depleted():
    get_tree().reload_current_scene()

Sie können so viele Argumente wie Sie möchten zusammen mit einem Signal aussenden.

Hier ist ein Beispiel, wo dies nützlich ist. Angenommen, wir möchten, dass eine Lebensleiste auf dem Bildschirm mit einer Animation auf Gesundheitsänderungen reagiert, aber wir möchten in unserem Szenenbaum die Benutzeroberfläche vom Spieler getrennt halten.

In unserem Skript Charakter.gd definieren wir ein Health_changed-Signal und senden es mit Signal.emit() aus, und von einem Game-Node weiter oben in unserem Szenenbaum verbinden wir es mit der Lifebar mit der Methode Signal.connect():

# character.gd

...
signal health_changed


func take_damage(amount):
    var old_health = health
    health -= amount

    # We emit the health_changed signal every time the
    # character takes damage.
    health_changed.emit(old_health, health)
...
# lifebar.gd

# Here, we define a function to use as a callback when the
# character's health_changed signal is emitted.

...
func _on_Character_health_changed(old_value, new_value):
    if old_value > new_value:
        progress_bar.modulate = Color.RED
    else:
        progress_bar.modulate = Color.GREEN

    # Imagine that `animate` is a user-defined function that animates the
    # bar filling up or emptying itself.
    progress_bar.animate(old_value, new_value)
...

Im Game-Node erhalten wir sowohl den Character als auch den Lifebar-Node und verbinden dann den Charakter, der das Signal aussendet, mit dem Empfänger, in diesem Fall dem Lifebar-Node .

# game.gd

func _ready():
    var character_node = get_node('Character')
    var lifebar_node = get_node('UserInterface/Lifebar')

    character_node.health_changed.connect(lifebar_node._on_Character_health_changed)

Dies ermöglicht es der Lifebar, auf Gesundheitsänderungen zu reagieren, ohne sie mit dem Character-Node zu koppeln.

Sie können optionale Argumentnamen in Klammern nach der Definition des Signals schreiben:

# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)

Diese Argumente werden im Node-Dock des Editors angezeigt und Godot kann sie verwenden, um Callback-Funktionen für Sie zu generieren. Sie können jedoch immer noch eine beliebige Anzahl von Argumenten angeben, wenn Sie Signale ausgeben. Es liegt an Ihnen, die richtigen Werte auszugeben.

../../../_images/gdscript_basics_signals_node_tab_1.png

GDScript kann ein Array von Werten an Verbindungen zwischen einem Signal und einer Methode binden. Wenn das Signal ausgegeben wird, empfängt die Callback-Methode die gebundenen Werte. Diese gebundenen Argumente sind für jede Verbindung einzigartig aber die Werte bleiben gleich.

Mit diesem Wertearray können Sie der Verbindung zusätzliche konstante Informationen hinzufügen, wenn Ihnen das ausgegebene Signal selbst keinen Zugriff auf alle benötigten Daten bietet.

Aufbauend auf dem obigen Beispiel möchten wir ein Protokoll des von jedem Charakter erlittenen Schadens auf dem Bildschirm anzeigen, z.B. Spieler1 hat 22 Schaden erlitten. Das health_changed-Signal gibt uns nicht den Namen des Charakters, der Schaden genommen hat. Wenn wir also das Signal mit der In-Game-Konsole verbinden, können wir den Namen des Charakters in das Binds-Array-Argument einfügen:

# game.gd

func _ready():
    var character_node = get_node('Character')
    var battle_log_node = get_node('UserInterface/BattleLog')

    character_node.health_changed.connect(battle_log_node._on_Character_health_changed.bind(character_node.name))

Unser BattleLog -Node empfängt jedes Element im Binds-Array als zusätzliches Argument:

# battle_log.gd

func _on_Character_health_changed(old_value, new_value, character_name):
    if not new_value <= old_value:
        return

    var damage = old_value - new_value
    label.text += character_name + " took " + str(damage) + " damage."

Warten auf Signale oder Coroutinen

Das Schlüsselwort await kann verwendet werden, um Coroutinen zu erzeugen, die warten, bis ein Signal ausgegeben wird, bevor sie die Ausführung fortsetzen. Die Verwendung des Schlüsselwortes await mit einem Signal oder einem Aufruf einer Funktion, die auch eine Coroutine ist, gibt die Kontrolle sofort an den Aufrufer zurück. Wenn das Signal ausgegeben wird (oder die aufgerufene Coroutine beendet wird), wird die Ausführung an dem Punkt fortgesetzt, an dem sie angehalten wurde.

Um zum Beispiel die Ausführung zu stoppen, bis der Benutzer eine Taste drückt, können Sie wie folgt vorgehen:

func wait_confirmation():
    print("Prompting user")
    await $Button.button_up # Waits for the button_up signal from Button node.
    print("User confirmed")
    return true

In diesem Fall wird die wait_confirmation zu einer Coroutine, was bedeutet, daß der Aufrufer auch darauf warten muss:

func request_confirmation():
    print("Will ask the user")
    var confirmed = await wait_confirmation()
    if confirmed:
        print("User confirmed")
    else:
        print("User cancelled")

Beachten Sie, dass die Abfrage des Rückgabewerts einer Coroutine ohne await einen Fehler auslöst:

func wrong():
    var confirmed = wait_confirmation() # Will give an error.

Wenn Sie jedoch nicht auf das Ergebnis angewiesen sind, können Sie die Funktion einfach asynchron aufrufen, wodurch die Ausführung nicht unterbrochen wird und die aktuelle Funktion nicht zu einer Coroutine wird:

func okay():
    wait_confirmation()
    print("This will be printed immediately, before the user press the button.")

Wenn Sie await mit einem Ausdruck verwenden, der weder ein Signal noch eine Coroutine ist, wird der Wert sofort zurückgegeben und die Funktion gibt die Kontrolle nicht an den Aufrufer zurück:

func no_wait():
    var x = await get_five()
    print("This doesn't make this function a coroutine.")

func get_five():
    return 5

Das bedeutet auch, dass die Rückgabe eines Signals von einer Funktion, die keine Coroutine ist, den Aufrufer auf dieses Signal warten lässt:

func get_signal():
    return $Button.button_up

func wait_button():
    await get_signal()
    print("Button was pressed")

Bemerkung

Im Gegensatz zu yield in früheren Godot-Versionen können Sie das Funktionsstatusobjekt nicht erhalten. Dies geschieht, um Typsicherheit zu gewährleisten. Mit dieser Typsicherheit kann eine Funktion nicht sagen, dass sie einen int zurückgibt, während sie in Wirklichkeit zur Laufzeit ein Funktionsstatusobjekt zurückgibt.

Assert-Schlüsselwort

Das assert-Schlüsselwort kann verwendet werden, um Bedingungen im Debug-Builds zu überprüfen. Diese Assertions werden in Nicht-Debug-Builds ignoriert. Dies bedeutet, dass der als Argument übergebene Ausdruck in einem Projekt, das im Release-Modus exportiert wird, nicht ausgewertet wird. Aus diesem Grund dürfen Assertions keine Ausdrücke enthalten, die Seiteneffekte haben. Andernfalls hängt das Verhalten des Skripts davon ab, ob das Projekt in einem Debug-Build ausgeführt wird.

# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.
assert(i == 0)

Wenn Sie ein Projekt über den Editor ausführen, wird das Projekt angehalten, wenn ein Assertion-Fehler auftritt.

Sie können optional eine benutzerdefinierte Fehlermeldung übergeben, die angezeigt wird, wenn die Assertion fehlschlägt:

assert(enemy_power < 256, "Enemy is too powerful!")