Up to date
This page is up to date for Godot 4.3.
If you still find outdated information, please open an issue.
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.
Imaginons que vous programmez un système d’inventaire. Vous codez une classe Item, puis un Inventaire. Pour ajouter des éléments à l'inventaire, les personnes qui travaillent avec votre code doivent toujours passer un Item à la méthode Inventaire.add(). Avec les types, vous pouvez imposer ceci :
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
Pour définir le type d'une variable, d'un paramètre ou d'une constante, écrivez deux points après le nom de la variable, suivi de son type. Par exemple : var health: int. Ceci force le type de la variable à rester toujours le même : :
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 essaiera de déduire les types si vous écrivez deux points, mais que vous omettez le 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.
Vous pouvez utiliser n'importe quelle classe, y compris vos classes personnalisées, en tant que types. Il y a deux façons de les utiliser dans des scripts. La première méthode consiste à précharger le script que vous voulez utiliser comme type dans une constante :
const Rifle = preload("res://player/weapons/rifle.gd")
var my_rifle: Rifle
La seconde méthode consiste à utiliser le mot-clé class_name lors de la création. Pour l'exemple ci-dessus, votre rifle.gd ressemblerait à ceci :
class_name Rifle
extends Node2D
Si vous utilisez class_name, Godot enregistre globalement le type Rifle dans l'éditeur et vous pouvez l'utiliser n'importe où, sans avoir à le précharger dans une constante :
var my_rifle: Rifle
Définissez le type de retour d'une fonction à l'aide de la flèche ->
Pour définir le type de retour d'une fonction, écrivez un tiret et un signe supérieur -> après sa déclaration, suivi du type de retour :
func _process(delta: float) -> void:
pass
Le type void signifie que la fonction ne renvoie rien. Vous pouvez utiliser n'importe quel type, comme pour les variables :
func hit(damage: float) -> bool:
health_points -= damage
return health_points <= 0
Vous pouvez également utiliser vos propres classes comme types de retour :
# 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
[], []=, 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"
Depuis Godot 4.2, vous pouvez également spécifier un type pour la variable de boucle dans une boucle for. Par exemple, vous pouvez écrire :
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.
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.
Vous pouvez vérifier si ce PhysicsBody2D est votre joueur avec le mot-clé de conversion as , et utiliser à nouveau les deux points : pour forcer la variable à utiliser ce type. Ceci force la variable à s'en tenir au type PlayerController :
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
Le mot-clé as convertit silencieusement la variable en null en cas d'incompatibilité de type lors de l'exécution, sans erreur/avertissement. Bien que cela puisse être pratique dans certains cas, cela peut également entraîner des bogues. Utilisez le mot-clé as uniquement si ce comportement est prévu. Une alternative plus sûre consiste à utiliser le mot-clé is :
if not (body is PlayerController):
push_error("Bug: body is not PlayerController.")
var player: PlayerController = body
if not player:
return
player.damage()
ou l'instruction assert() :
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
Les lignes sécurisées ne signifient pas toujours un code meilleur ou plus fiable. Voir la note ci-dessous sur le mot-clé as. Par exemple :
@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.
Le code typé prend un peu plus de temps à écrire, mais vous bénéficiez des avantages décrits plus haut. Voici un exemple du même script vide, dans un style dynamique :
extends Node
func _ready():
pass
func _process(delta):
pass
Et avec le typage statique :
extends Node
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
Comme vous pouvez le voir, vous pouvez également utiliser des types avec les méthodes virtuelles du moteur. Les rappels de signaux, comme toutes les méthodes, peuvent également utiliser des types. Voici un signal body_entered dans un style dynamique ::
func _on_area_2d_body_entered(body):
pass
Et le même rappel, avec des indications de type :
func _on_area_entered(area: CollisionObject2D) -> 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
Avertissements UNSAFE_PROPERTY_ACCESS et UNSAFE_METHOD_ACCESS
Dans cet exemple, nous cherchons à définir une propriété et à appeler une méthode sur un objet auquel est attaché un script avec class_name MyScript et qui étend Node2D (extends Node2D). Si nous avons une référence à l'objet en tant que Node2D (par exemple, tel qu'il nous a été transmis par le système physique), nous pouvons d'abord vérifier si la propriété et la méthode existent, puis les définir et les appeler si c'est le cas :
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.
Cependant, ce code génèrera les avertissements UNSAFE_PROPERTY_ACCESS et UNSAFE_METHOD_ACCESS car la propriété et la méthode ne sont pas présentes dans le type référencé - dans le cas présent, un Node2D. Pour sécuriser ces opérations, vous pouvez d'abord vérifier si l'objet est de type MyScript à l'aide du mot-clé is puis déclarer une variable de type MyScript sur laquelle vous pouvez définir ses propriétés et appeler ses méthodes :
if node_2d is MyScript:
var my_script: MyScript = node_2d
my_script.some_property = 20
my_script.some_function()
Autrement, vous pouvez déclarer une variable et utiliser l'opérateur as pour essayer de convertir l'objet. Vous devrez ensuite vérifier si le transtypage a réussi en confirmant que la variable a été affectée :
var my_script := node_2d as MyScript
if my_script != null:
my_script.some_property = 20
my_script.some_function()
Avertissement UNSAFE_CAST
Dans cet exemple, nous souhaitons qu'un Label connecté à un objet entrant dans notre zone de collision indique le nom de la zone. Une fois que l'objet entre dans la zone de collision, le système physique envoie un signal avec un objet Node2D, et la solution la plus simple (mais pas typée statiquement) pour faire ce que nous voulons pourrait être obtenue comme ceci :
func _on_body_entered(body: Node2D) -> void:
body.label.text = name # Produces UNSAFE_PROPERTY_ACCESS warning.
Ce morceau de code génère un avertissement UNSAFE_PROPERTY_ACCESS car label n'est pas défini dans Node2D. Pour résoudre ce problème, nous pourrions d'abord vérifier si la propriété label existe et la convertir en type Label avant de définir sa propriété de texte comme suit :
func _on_body_entered(body: Node2D) -> void:
if "label" in body:
(body.label as Label).text = name # Produces UNSAFE_CAST warning.
Cependant, cela génère un avertissement UNSAFE_CAST car body.label est de type Variant. Pour obtenir en toute sécurité la propriété dans le type souhaité, vous pouvez utiliser la méthode Object.get()``qui renvoie l'objet sous forme de valeur ``Variant ou renvoie null si la propriété n'existe pas. Vous pouvez ensuite déterminer si la propriété contient un objet du bon type à l'aide du mot-clé is et enfin déclarer une variable typée statiquement avec l'objet :
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.
Vous ne pouvez pas spécifier le type de membres individuels dans un tableau ou dans un dictionnaire :
var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy] var character: Dictionary = { name: String = "Richard", money: int = 1000, inventory: Inventory = $Inventory, }
Les types imbriqués ne sont actuellement pas pris en charge :
var teams: Array[Array[Character]] = []
Les dictionnaires typés ne sont actuellement pas pris en charge :
var map: Dictionary[Vector2i, Item] = {}
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.