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.

Statische Typisierung in GDScript

In dieser Anleitung lernen Sie:

  • wie man die statische Typisierung in GDScript verwendet;

  • dass statische Typen zur Vermeidung von Bugs beitragen können;

  • dass die statische Typisierung Ihren Umgang mit dem Editor verbessert.

Wo und wie Sie dieses neue Sprachfeature verwenden, liegt ganz bei Ihnen: Sie können es nur in einigen kritischen GDScript-Dateien verwenden, es überall verwenden, oder es überhaupt nicht verwenden.

Statische Typen können für Variablen, Konstanten, Funktionen, Parameter und Rückgabetypen verwendet werden.

Ein kurzer Überblick über statische Typisierung

Mit statischer Typisierung kann GDScript mehr Fehler erkennen, ohne dass der Code überhaupt ausgeführt wird. Außerdem geben Typisierungshinweise Ihnen und Ihren Teamkollegen mehr Informationen während der Arbeit, da die Typen der Argumente beim Aufruf einer Methode angezeigt werden. Die statische Typisierung verbessert die Autovervollständigung im Editor und die Dokumentation Ihrer Skripte.

Imagine you're programming an inventory system. You code an Item class, then an Inventory. To add items to the inventory, the people who work with your code should always pass an Item to the Inventory.add() method. With types, you can enforce this:

class_name Inventory


func add(reference: Item, amount: int = 1):
    var item := find_item(reference)
    if not item:
        item = _instance_item_from_db(reference)
    item.amount += amount

Statische Typen bieten auch bessere Optionen für die Code-Vervollständigung. Nachfolgend sehen Sie den Unterschied bei Code-Vervollständigung zwischen einer dynamischen und einer statischen Typisierung.

Wahrscheinlich haben Sie schon einmal festgestellt, dass nach einem Punkt keine Vorschläge für die automatische Code-Vervollständigung gemacht werden:

Vervollständigungsoptionen für dynamisch typisierten Code.

Dies ist auf den dynamischen Code zurückzuführen. Godot kann nicht wissen, welchen Werttyp Sie an die Funktion übergeben. Wenn Sie den Typ jedoch explizit angeben, erhalten Sie alle Methoden, Propertys, Konstanten usw. von dem Wert:

Vervollständigungsoptionen für statisch typisierten Code.

Tipp

Wenn Sie statische Typisierung bevorzugen, empfehlen wir Ihnen, die Editoreinstellung Texteditor > Vervollständigung > Typhinweise hinzufügen zu aktivieren. Ziehen Sie auch in Erwägung, einige Warnungen zu aktivieren, die standardmäßig deaktiviert sind.

Außerdem verbessert typisiertes GDScript die Performance durch die Verwendung optimierter Opcodes, wenn die Typen der Operanden/Argumente zur Kompilierungszeit bekannt sind. Für die Zukunft sind weitere GDScript-Optimierungen geplant, z. B. die JIT/AOT-Kompilierung.

Insgesamt bietet die typisierte Programmierung ein besser strukturiertes Erlebnis. Es hilft, Fehler zu vermeiden und verbessert den selbstdokumentierenden Aspekt Ihrer Skripte. Dies ist besonders hilfreich, wenn Sie in einem Team oder an einem langfristigen Projekt arbeiten: Studien haben gezeigt, dass Entwickler die meiste Zeit damit verbringen, den Code anderer Leute oder Skripte zu lesen, die sie in der Vergangenheit geschrieben und vergessen haben. Je klarer und strukturierter der Code ist, desto schneller ist er zu verstehen, desto schneller können Sie vorankommen.

Wie man statische Typisierung einsetzt

To define the type of a variable, parameter, or constant, write a colon after the name, followed by its type. E.g. var health: int. This forces the variable's type to always stay the same:

var damage: float = 10.5
const MOVE_SPEED: float = 50.0
func sum(a: float = 0.0, b: float = 0.0) -> float:
    return a + b

Godot will try to infer types if you write a colon, but you omit the type:

var damage := 10.5
const MOVE_SPEED := 50.0
func sum(a := 0.0, b := 0.0) -> float:
    return a + b

Bemerkung

  1. Es gibt keinen Unterschied zwischen = und := für Konstanten.

  2. Sie brauchen keine Type-Hints für Konstanten zu schreiben, da Godot diese automatisch anhand des zugewiesenen Wertes festlegt. Sie können dies aber trotzdem tun, um die Absicht Ihres Codes zu verdeutlichen. Außerdem ist dies für typisierte Arrays nützlich (wie const A: Array[int] = [1, 2, 3]), da untypisierte Arrays standardmäßig verwendet werden.

Was ein Type-Hint sein kann

Hier finden Sie eine vollständige Liste der Elemente, die als Type-Hint verwendet werden können:

  1. Variant. Beliebiger Typ. In den meisten Fällen ist dies nicht viel anders als eine Deklaration ohne Typisierung, erhöht aber die Lesbarkeit. Als Rückgabetyp zwingt es die Funktion, explizit einen Wert zurückzugeben.

  2. (Nur Rückgabetyp) void. Gibt an, dass die Funktion keinen Wert zurückgibt.

  3. Built-in-Typen.

  4. Native Klassen (Object, Node, Area2D, Camera2D, etc.).

  5. Globale Klassen.

  6. Innere Klassen.

  7. Globale, native und benannte Enums. Beachten Sie, dass ein Enum-Typ nur ein int ist, es gibt keine Garantie, dass der Wert zur Menge der Enum-Werte gehört.

  8. Konstanten (einschließlich lokaler Konstanten), wenn sie eine vorgeladene Klasse oder Enum enthalten.

You can use any class, including your custom classes, as types. There are two ways to use them in scripts. The first method is to preload the script you want to use as a type in a constant:

const Rifle = preload("res://player/weapons/rifle.gd")
var my_rifle: Rifle

The second method is to use the class_name keyword when you create the script. For the example above, your rifle.gd would look like this:

class_name Rifle
extends Node2D

If you use class_name, Godot registers the Rifle type globally in the editor, and you can use it anywhere, without having to preload it into a constant:

var my_rifle: Rifle

Geben Sie den Rückgabetyp einer Funktion mit dem Pfeil -> an

To define the return type of a function, write a dash and a right angle bracket -> after its declaration, followed by the return type:

func _process(delta: float) -> void:
    pass

The type void means the function does not return anything. You can use any type, as with variables:

func hit(damage: float) -> bool:
    health_points -= damage
    return health_points <= 0

You can also use your own classes as return types:

# Adds an item to the inventory and returns it.
func add(reference: Item, amount: int) -> Item:
    var item: Item = find_item(reference)
    if not item:
        item = ItemDatabase.get_instance(reference)

    item.amount += amount
    return item

Kovarianz und Kontravarianz

Bei der Vererbung von Basisklassen-Methoden sollten Sie das Liskov-Substitutionsprinzip befolgen.

Kovarianz: Wenn Sie eine Methode erben, können Sie einen Rückgabetyp angeben, der spezifischer ist (Subtyp) als die Parent-Methode.

Kontravarianz: Wenn Sie eine Methode erben, können Sie einen Parametertyp angeben, der weniger spezifisch ist (Supertyp) als die Parent-Methode.

Beispiel:

class_name Parent


func get_property(param: Label) -> Node:
    # ...
class_name Child extends Parent


# `Control` is a supertype of `Label`.
# `Node2D` is a subtype of `Node`.
func get_property(param: Control) -> Node2D:
    # ...

Spezifizieren des Elementtyps eines Arrays

Um den Typ eines Arrays zu definieren, schließen Sie den Typnamen in [] ein.

An array's type applies to for loop variables, as well as some operators like [], [...] = (assignment), and +. Array methods (such as push_back) and other operators (such as ==) are still untyped. Built-in types, native and custom classes, and enums may be used as element types. Nested array types (like Array[Array[int]]) are not supported.

var scores: Array[int] = [10, 20, 30]
var vehicles: Array[Node] = [$Car, $Plane]
var items: Array[Item] = [Item.new()]
var array_of_arrays: Array[Array] = [[], []]
# var arrays: Array[Array[int]] -- disallowed

for score in scores:
    # score has type `int`

# The following would be errors:
scores += vehicles
var s: String = scores[0]
scores[0] = "lots"

Since Godot 4.2, you can also specify a type for the loop variable in a for loop. For instance, you can write:

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names:
    pass

Das Array bleibt untypisiert, aber die Variable name innerhalb der for-Schleife wird immer vom Typ String sein.

Specify the element type of a Dictionary

To define the type of a Dictionary's keys and values, enclose the type name in [] and separate the key and value type with a comma.

A dictionary's value type applies to for loop variables, as well as some operators like [] and [...] = (assignment). Dictionary methods that return values and other operators (such as ==) are still untyped. Built-in types, native and custom classes, and enums may be used as element types. Nested typed collections (like Dictionary[String, Dictionary[String, int]]) are not supported.

var fruit_costs: Dictionary[String, int] = { "apple": 5, "orange": 10 }
var vehicles: Dictionary[String, Node] = { "car": $Car, "plane": $Plane }
var item_tiles: Dictionary[Vector2i, Item] = { Vector2i(0, 0): Item.new(), Vector2i(0, 1): Item.new() }
var dictionary_of_dictionaries: Dictionary[String, Dictionary] = { { } }
# var dicts: Dictionary[String, Dictionary[String, int]] -- disallowed

for fruit in fruit_costs:
    # `fruit` has type `String`

# The following would be errors:
fruit_costs["pear"] += vehicles
var s: String = fruit_costs["apple"]
fruit_costs["orange"] = "lots"

Typ-Casting

Typ-Casting ist ein wichtiges Konzept in typisierten Sprachen. Casting ist die Umwandlung eines Wertes von einem Typ in einen anderen.

Stellen Sie sich einen Enemy in Ihrem Spiel vor, der per extend eine Area2D erweitert. Sie wollen, dass er mit dem Player kollidiert, einem CharacterBody2D mit einem Skript namens PlayerController, das an ihm hängt. Sie benutzen das Signal body_entered, um die Kollision zu erkennen. Mit typisiertem Code wird der Body, den Sie erkennen, ein generischer PhysicsBody2D sein, und nicht Ihr PlayerController beim _on_body_entered-Callback.

You can check if this PhysicsBody2D is your Player with the as keyword, and using the colon : again to force the variable to use this type. This forces the variable to stick to the PlayerController type:

func _on_body_entered(body: PhysicsBody2D) -> void:
    var player := body as PlayerController
    if not player:
        return

    player.damage()

Da wir es mit einem benutzerdefinierten Typ zu tun haben, wird die Variable player auf null gesetzt, wenn der body nicht PlayerController erweitert. Wir können dies benutzen, um zu überprüfen, ob der Body der Spieler ist oder nicht. Dank dieses Casts erhalten wir auch eine vollständige Autovervollständigung für die Player-Variable.

Bemerkung

The as keyword silently casts the variable to null in case of a type mismatch at runtime, without an error/warning. While this may be convenient in some cases, it can also lead to bugs. Use the as keyword only if this behavior is intended. A safer alternative is to use the is keyword:

if not (body is PlayerController):
    push_error("Bug: body is not PlayerController.")

var player: PlayerController = body
if not player:
    return

player.damage()

You can also simplify the code by using the is not operator:

if body is not PlayerController:
    push_error("Bug: body is not PlayerController")

Alternatively, you can use the assert() statement:

assert(body is PlayerController, "Bug: body is not PlayerController.")

var player: PlayerController = body
if not player:
    return

player.damage()

Bemerkung

Wenn Sie versuchen, mit einem Built-in-Typ zu casten und dies fehlschlägt, gibt Godot einen Fehler aus.

Sichere Zeilen

Sie können auch Casting verwenden, um sichere Zeilen zu gewährleisten. Sichere Zeilen sind ein Werkzeug, das Ihnen sagt, wann mehrdeutige Codezeilen typsicher sind. Da Sie typisierten und dynamischen Code vermischen können, verfügt Godot manchmal nicht über genügend Informationen, um zu wissen, ob eine Anweisung zur Laufzeit einen Fehler auslösen wird oder nicht.

Dies geschieht, wenn Sie einen Child-Node erhalten. Nehmen wir zum Beispiel einen Timer: mit dynamischem Code können Sie den Node mit $Timer erhalten. GDScript unterstützt duck-typing, d.h. selbst wenn Ihr Timer vom Typ Timer ist, ist er auch ein Node und ein Object, zwei Klassen, die er erweitert. Mit dynamischem GDScript ist der Typ des Nodes auch egal, solange er die Methoden hat, die Sie aufrufen müssen.

Sie können das Casting verwenden, um Godot mitzuteilen, welchen Typ Sie erwarten, wenn Sie einen Node erhalten: ($Timer as Timer), ($Player as CharacterBody2D), usw. Godot überprüft, ob der Typ funktioniert, und wenn ja, wird die Zeilennummer links im Skript-Editor grün angezeigt.

Unsichere vs. sichere Zeile

Unsichere Zeile (Zeile 7) vs. sichere Zeilen (Zeilen 6 und 8)

Bemerkung

Safe lines do not always mean better or more reliable code. See the note above about the as keyword. For example:

@onready var node_1 := $Node1 as Type1 # Safe line.
@onready var node_2: Type2 = $Node2 # Unsafe line.

Auch wenn die node_2-Deklaration als unsichere Zeile markiert ist, ist sie zuverlässiger als die Node_1-Deklaration. Denn wenn Sie den Node-Typ in der Szene ändern und versehentlich vergessen, ihn im Skript zu ändern, wird der Fehler sofort entdeckt, wenn die Szene geladen wird. Im Gegensatz zu node_1, das stillschweigend zu null gecastet wird und der Fehler erst später entdeckt wird.

Bemerkung

Sie können sichere Zeilen deaktivieren oder ihre Farbe in den Editoreinstellungen ändern.

Typisiert oder dynamisch: Halten Sie sich an einen Stil

Typisiertes GDScript und dynamisches GDScript können im selben Projekt koexistieren. Aus Gründen der Konsistenz in Ihrer Codebasis und für Ihre Kollegen ist es jedoch empfehlenswert, durchgängug einen der beiden Stile zu verwenden. Es ist für alle einfacher, zusammenzuarbeiten, wenn Sie dieselben Richtlinien befolgen, und macht es einfacher und schneller, den Code anderer Leute zu lesen und zu verstehen.

Typed code takes a little more writing, but you get the benefits we discussed above. Here's an example of the same, empty script, in a dynamic style:

extends Node


func _ready():
    pass


func _process(delta):
    pass

And with static typing:

extends Node


func _ready() -> void:
    pass


func _process(delta: float) -> void:
    pass

As you can see, you can also use types with the engine's virtual methods. Signal callbacks, like any methods, can also use types. Here's a body_entered signal in a dynamic style:

func _on_area_2d_body_entered(body):
    pass

And the same callback, with type hints:

func _on_area_2d_body_entered(body: PhysicsBody2D) -> void:
    pass

Warnsystem

Bemerkung

Die ausführliche Dokumentation über das GDScript-Warnsystem wurde nach GDScript Warnungs-System verschoben.

Godot gives you warnings about your code as you write it. The engine identifies sections of your code that may lead to issues at runtime, but lets you decide whether or not you want to leave the code as it is.

Es gibt eine Reihe von Warnungen, die sich speziell an Benutzer von typisiertem GDScript richten. Standardmäßig sind diese Warnungen deaktiviert, Sie können sie in den Projekteinstellungen aktivieren (Debug > GDScript, stellen Sie sicher, dass Erweiterte Einstellungen aktiviert ist).

Sie können die Warnung UNTYPED_DECLARATION aktivieren, wenn Sie immer statische Typen verwenden wollen. Zusätzlich können Sie die Warnung INFERRED_DECLARATION aktivieren, wenn Sie eine lesbarere und zuverlässigere, aber ausführlichere Syntax bevorzugen.

UNSAFE_*-Warnungen machen unsichere Operationen deutlicher als unsichere Zeilen. Derzeit decken UNSAFE_*-Warnungen nicht alle Fälle ab, die von unsicheren Zeilen abgedeckt werden.

Häufige unsichere Vorgänge und ihre sicheren Entsprechungen

Global scope methods

The following global scope methods are not statically typed, but they have typed counterparts available. These methods return statically typed values:

Method

Statically typed equivalents

abs()

ceil()

clamp()

floor()

lerp()

round()

sign()

snapped()

When using static typing, use the typed global scope methods whenever possible. This ensures you have safe lines and benefit from typed instructions for better performance.

UNSAFE_PROPERTY_ACCESS und UNSAFE_METHOD_ACCESS-Warnungen

In this example, we aim to set a property and call a method on an object that has a script attached with class_name MyScript and that extends Node2D. If we have a reference to the object as a Node2D (for instance, as it was passed to us by the physics system), we can first check if the property and method exist and then set and call them if they do:

if "some_property" in node_2d:
    node_2d.some_property = 20  # Produces UNSAFE_PROPERTY_ACCESS warning.

if node_2d.has_method("some_function"):
    node_2d.some_function()  # Produces UNSAFE_METHOD_ACCESS warning.

However, this code will produce UNSAFE_PROPERTY_ACCESS and UNSAFE_METHOD_ACCESS warnings as the property and method are not present in the referenced type - in this case a Node2D. To make these operations safe, you can first check if the object is of type MyScript using the is keyword and then declare a variable with the type MyScript on which you can set its properties and call its methods:

if node_2d is MyScript:
    var my_script: MyScript = node_2d
    my_script.some_property = 20
    my_script.some_function()

Alternatively, you can declare a variable and use the as operator to try to cast the object. You'll then want to check whether the cast was successful by confirming that the variable was assigned:

var my_script := node_2d as MyScript
if my_script != null:
    my_script.some_property = 20
    my_script.some_function()

UNSAFE_CAST-Warnung

In this example, we would like the label connected to an object entering our collision area to show the area's name. Once the object enters the collision area, the physics system sends a signal with a Node2D object, and the most straightforward (but not statically typed) solution to do what we want could be achieved like this:

func _on_body_entered(body: Node2D) -> void:
    body.label.text = name  # Produces UNSAFE_PROPERTY_ACCESS warning.

This piece of code produces an UNSAFE_PROPERTY_ACCESS warning because label is not defined in Node2D. To solve this, we could first check if the label property exist and cast it to type Label before settings its text property like so:

func _on_body_entered(body: Node2D) -> void:
    if "label" in body:
        (body.label as Label).text = name  # Produces UNSAFE_CAST warning.

However, this produces an UNSAFE_CAST warning because body.label is of a Variant type. To safely get the property in the type you want, you can use the Object.get() method which returns the object as a Variant value or returns null if the property doesn't exist. You can then determine whether the property contains an object of the right type using the is keyword, and finally declare a statically typed variable with the object:

func _on_body_entered(body: Node2D) -> void:
    var label_variant: Variant = body.get("label")
    if label_variant is Label:
        var label: Label = label_variant
        label.text = name

Fälle, in denen Sie keine Typen spezifizieren können

Zum Abschluss dieser Einführung wollen wir noch die Fälle erwähnen, in denen Sie keine Type-Hints verwenden können. Dies führt zu einem Syntaxfehler.

  1. You can't specify the type of individual elements in an array or a dictionary:

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]
var character: Dictionary = {
    name: String = "Richard",
    money: int = 1000,
    inventory: Inventory = $Inventory,
}
  1. Nested types are not currently supported:

var teams: Array[Array[Character]] = []

Zusammenfassung

Typisiertes GDScript ist ein leistungsstarkes Werkzeug. Es hilft Ihnen, strukturierteren Code zu schreiben, häufige Fehler zu vermeiden und skalierbare und zuverlässige Systeme zu erstellen. Statische Typen verbessern die Leistung von GDScript, und weitere Optimierungen sind für die Zukunft geplant.