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.
Checking the stable version of the documentation...
Typage statique en GDScript
Dans ce guide, vous apprendrez :
comment utiliser le typage statique avec GDScript ;
que les types statiques peuvent vous aider à éviter les bugs ;
que le typage statique améliore votre expérience avec l'éditeur.
Vous êtes libre de choisir où et comment utiliser cette nouvelle fonctionnalité de langage : vous pouvez ne l'utiliser que dans certains fichiers sensibles GDScript, l'utiliser partout ou ne pas l'utiliser.
Les types statiques peuvent être utilisés sur des variables, constantes, fonctions, paramètres et types de retour.
Un bref aperçu du typage statique
Avec le typage statique, GDScript peut détecter encore plus d’erreurs lors de l’écriture du code. Cela vous donne, à vous et à vos collègues, plus d’informations pendant que vous travaillez, comme les types d’arguments apparaissant lorsque vous appelez une méthode. Le typage statique améliore la saisie semi-automatique de l'éditeur et la documentation de vos scripts.
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
Les types statiques vous offrent également de meilleures options de complétion du code. Ci-dessous, vous pouvez voir la différence entre les options de complétion de type dynamique et de type statique.
Vous avez probablement rencontré un manque de suggestions de saisie semi-automatique après un point :
Cela est dû au code dynamique. Godot ne peut pas savoir quel type de valeur vous transmettez à la fonction. Cependant, si vous écrivez le type explicitement, vous obtiendrez toutes les méthodes, propriétés, constantes, etc. à partir de la valeur :
Astuce
Si vous préférez la saisie statique, nous vous recommandons d'activer le paramètre d'éditeur Éditeur de texte > Complétion > Ajouter des indices de type. Pensez également à activer ``certains avertissements <Warning system>`_ qui sont désactivés par défaut.
De plus, le GDScript typé améliore les performances en utilisant des opcodes optimisés lorsque les types d'opérandes/arguments sont connus au moment de la compilation. D'autres optimisations de GDScript sont prévues à l'avenir, telles que la compilation JIT/AOT.
Dans l'ensemble, la programmation typée vous donne une expérience plus structurée. Elle aide à prévenir les erreurs et améliore l'aspect d'auto-documentation de vos scripts. Ceci est particulièrement utile lorsque vous travaillez en équipe ou sur un projet à long terme : des études ont montré que les développeurs passent la plupart de leur temps à lire le code d'autres personnes, ou des scripts qu'ils ont écrits dans le passé et ont oubliés. Plus le code est clair et structuré, plus il est rapide à comprendre, plus vous pouvez travailler rapidement.
Comment utiliser le typage statique
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
Note
Il n'y a pas de différence entre
=et:=pour les constantes.Vous n'avez pas besoin d'écrire des indications de type pour les constantes, car Godot les définit automatiquement à partir de la valeur assignée. Mais vous pouvez toujours le faire pour rendre l'intention de votre code plus claire. De plus, cela est utile pour les tableaux typés (comme
const A: Array[int] = [1, 2, 3]), car les tableaux non typés sont utilisés par défaut.
Qu'est-ce qu'un indice de type peut être
Voici une liste complète de ce qui peut être utilisé comme indice de type :
Variant. Tout type. Dans la plupart des cas, cela n'est pas très différent d'une déclaration non typée, mais augmente la lisibilité. En tant que type de retour, cela force la fonction à renvoyer explicitement une valeur.(Only return type)
void. Indicates that the function does not return any value.Classes natives (
Object,Node,Area2D,Camera2D, etc.).Les énumérations nommées globales, natives et personnalisées. Notez qu'un type d'énumération est simplement un
int, il n'y a aucune garantie que la valeur appartienne à l'ensemble des valeurs d'énumération.Les constantes (y compris les locales) si elles contiennent une classe ou une énumération préchargée.
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
Définissez le type de retour d'une fonction à l'aide de la flèche ->
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
Covariance et contravariance
Lors de l'héritage des méthodes de classe de base, vous devez suivre le principe de substitution de Liskov.
Covariance : lorsque vous héritez d’une méthode, vous pouvez spécifier un type de retour plus spécifique (sous-type) que la méthode parent.
Contravariance : lorsque vous héritez d'une méthode, vous pouvez spécifier un type de paramètre moins spécifique (supertype) que la méthode parent.
Exemple :
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:
# ...
Spécifier le type d'élément d'un Array
Pour définir le type d'un Array, placez le nom du type entre [].
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
Le tableau restera non typé, mais la variable name dans la boucle for sera toujours de type String.
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"
La conversion de type
Le changement de type est un concept clé dans les langages typés. Le casting est la conversion d'une valeur d'un type à un autre.
Imaginez un Enemy dans votre jeu, avec extends Area2D. Vous voulez qu'il entre en collision avec le joueur Player, un CharacterBody2D avec un script attaché appelé PlayerController. Vous utilisez le signal body_entered pour détecter la collision. Avec le code qui est écrit, le corps que vous détecterez sera un PhysicsBody2D générique, et non votre PlayerController via le rappel _on_body_entered.
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()
Comme il s'agit d'un type personnalisé, si body n'hérite pas de PlayerController, la variable player sera défini avec null. Nous pouvons utiliser cela pour vérifier si body est le joueur ou non. Nous obtiendrons également l'auto-complétion complète de la variable player grâce à cette conversion.
Note
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()
Note
Si vous essayez de convertir avec un type intégré et que cela échoue, Godot lancera une erreur.
Lignes sécurisées
Vous pouvez également utiliser le casting pour garantir la sécurité des lignes. Les lignes sécurisées sont un outil qui vous indique quand des lignes de code ambiguës sont sécurisées en termes de type. Comme vous pouvez mélanger et assortir du code typé et dynamique, Godot n'a parfois pas suffisamment d'informations pour savoir si une instruction déclenchera une erreur ou non au moment de l'exécution.
Cela se produit lorsque vous avez un nœud enfant. Prenons un timer par exemple : avec du code dynamique, vous pouvez obtenir le nœud avec $Timer. GDScript supporte le duck-typing, donc même si votre timer est de type Timer, c'est aussi un Node et un Object, deux classes auxquelles il appartient. Avec le GDScript dynamique, vous ne vous souciez pas non plus du type du nœud tant qu'il possède les méthodes que vous avez besoin d'appeler.
Vous pouvez utiliser la conversion de type pour dire à Godot le type que vous attendez lorsque vous obtenez un nœud : ($Timer as Timer), ($Player as CharacterBody2D), etc. Godot s'assurera que le type fonctionne et si c'est le cas, le numéro de ligne deviendra vert à gauche de l'éditeur de script.
Ligne non sécurisée (ligne 7) contre ligne sécurisée (lignes 6 et 8)
Note
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.
Même si la déclaration node_2 est marquée comme une ligne non sécurisée, elle est plus fiable que la déclaration node_1. En effet, si vous modifiez le type de nœud dans la scène et oubliez accidentellement de le modifier dans le script, l'erreur sera détectée immédiatement lors du chargement de la scène. Contrairement à node_1, qui sera silencieusement converti en null et l'erreur sera détectée plus tard.
Note
Vous pouvez désactiver les lignes sécurisées ou modifier leur couleur dans les paramètres de l'éditeur.
Typé ou dynamique : s'en tenir à un style
Le GDScript typé et le GDScript dynamique peuvent coexister dans le même projet. Mais il est préférable de s'en tenir à l'un ou l'autre pour assurer la cohérence de votre base de code et pour vos pairs. Il est plus facile pour tout le monde de travailler ensemble si vous suivez les mêmes lignes directrices, et plus rapide pour lire et comprendre le code des autres.
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
Système d'avertissement
Note
La documentation sur le système d'avertissement de GDScript a été déplacée vers Système d’avertissement de GDScript.
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.
Nous avons un certain nombre d'avertissements destinés spécifiquement aux utilisateurs de GDScript typé. Par défaut, ces avertissements sont désactivés, vous pouvez les activer dans les paramètres du projet (Debug > GDScript, assurez-vous que Advanced Settings est activé).
Vous pouvez activer l'avertissement UNTYPED_DECLARATION si vous souhaitez toujours utiliser des types statiques. De plus, vous pouvez activer l'avertissement INFERRED_DECLARATION si vous préférez une syntaxe plus lisible et plus fiable, mais plus détaillée.
Les avertissements UNSAFE_* rendent les opérations non sécurisées plus visibles que les lignes non sécurisées. Actuellement, les avertissements UNSAFE_* ne couvrent pas tous les cas couverts par les lignes non sécurisées.
Opérations courantes non sécurisées et leurs équivalents sécurisés
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 |
Équivalents triés statiquement |
|---|---|
(untyped
clamp() does not work on Color) |
|
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.
Avertissements UNSAFE_PROPERTY_ACCESS et UNSAFE_METHOD_ACCESS
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()
Avertissement UNSAFE_CAST
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
Cas dans lesquels vous ne pouvez pas spécifier de types
Pour conclure cette introduction, mentionnons les cas où vous ne pouvez pas utiliser les indications de type. Cela déclenchera une erreur de syntaxe.
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,
}
Nested types are not currently supported:
var teams: Array[Array[Character]] = []
Résumé
GDScript typé est un outil puissant. Il vous aide à écrire du code plus structuré, à éviter les erreurs courantes et à créer des systèmes évolutifs et fiables. Les types statiques améliorent les performances de GDScript et d'autres optimisations sont prévues à l'avenir.