Typage statique en GDScript

Dans ce guide, vous apprendrez :

  • Comment utiliser les types en GDScript

  • Les types statiques peuvent aider à éviter les bugs

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 écrire du code comme vous l'avez toujours fait !

Les types statiques peuvent être utilisés sur des variables, constantes, fonctions, paramètres et types de retour.

Note

Le GDScript typé est disponible depuis Godot 3.1.

Un bref aperçu de la saisie statique

Avec le GDScript typé, Godot 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.

Imaginons que vous programmez un système d’inventaire. Vous codez un nœud 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 :

# In 'Item.gd'.
class_name Item
# In 'Inventory.gd'.
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

Un autre avantage significatif du GDScript typé est le nouveau système d’avertissement. Depuis la version 3.1, Godot vous avertit lors de la rédaction de votre code : le moteur identifie des sections de votre code susceptibles de générer des problèmes lors de l'exécution, mais vous permet de décider si vous souhaitez ou non laisser le code tel quel. Vous en saurez plus dans un instant.

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 pour une classe appelée PlayerController.

Vous avez probablement déjà stockée un nœud dans une variable et tapé un point pour se retrouver avec aucune suggestion de saisie automatique :

options de complétion de code pour un type dynamique

Cela est dû au code dynamique. Godot ne peut pas savoir quel type de nœud ou de valeur vous transmettez à la fonction. Cependant, si vous écrivez le type explicitement, vous obtiendrez toutes les méthodes et variables publiques du nœud :

options de complétion de code pour un type statique

À l'avenir, le GDScript typé augmentera également les performances du code : la compilation en temps réel et d'autres améliorations du compilateur sont déjà sur la feuille de route !

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 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

Godot essaiera de déduire les types si vous écrivez deux points, mais vous omettez le type :

var life_points := 4
var damage := 10.5
var motion := Vector2()

Actuellement, vous pouvez utiliser trois types de… types :

  1. Built-in

  2. Classes et nœuds de base (Object, Node, Area2D, Camera2D, etc.)

  3. Vos propres classes personnalisées. Regarder la nouvelle fonctionnalité class_name pour enregistrer des types dans l'éditeur.

Note

Vous n'avez pas besoin d'écrire des indicateurs de type pour les constantes, car Godot le définit automatiquement à partir de la valeur attribuée. Mais vous pouvez toujours le faire pour clarifier votre code.

Types de variables personnalisées

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 deuxième 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

Casting de variable

Le typage est un concept clé dans les langages typés. Le casting est la conversion d'une valeur d'un type à un autre.

Imaginez un ennemi dans votre jeu, qui extends Area2D. Vous voulez qu'il entre en collision avec le joueur, un KinematicBody2D avec un script appelé PlayerController qui lui est attaché. Vous utilisez le signal on_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 playersera réglée sur 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

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 la conversion de type pour assurer la sécurité des lignes. Les lignes sécurisées sont un nouvel outil dans Godot 3.1 pour vous avertir lorsque des lignes de code ambiguës sont type-sûr. Comme vous pouvez mélanger et faire correspondre du code typé et du code dynamique, Godot n'a parfois pas assez d'informations pour savoir si une instruction va déclencher 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 KinematicBody2D), 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 dangereuse contre ligne sûre

Ligne non sécurisée (ligne 7) contre ligne sécurisée (lignes 6 et 8)

Note

Vous pouvez désactiver les lignes sécurisées ou modifier leur couleur dans les paramètres de l'éditeur.

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 nœuds comme types de retour :

# Inventory.gd

# 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

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_Area2D_body_entered(body):
    pass

Et le même rappel, avec des indications de type :

func _on_area_entered(area: CollisionObject2D) -> void:
    pass

Vous êtes libre de remplacer, par exemple, le CollisionObject2D, par votre propre type, pour convertir les paramètres automatiquement :

func _on_area_entered(bullet: Bullet) -> void:
    if not bullet:
        return

    take_damage(bullet.damage)

La variable bullet pourrait contenir n'importe quel CollisionObject2D ici, mais nous nous assurons que c'est notre Bullet, un nœud que nous avons créé pour notre projet. Si c'est autre chose, comme un Area2D, ou n'importe quel nœud qui n'hérite pas Bullet, la variable bullet sera null.

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.

Cas où vous ne pouvez pas spécifier de types

Pour conclure cette introduction, couvrons quelques cas où vous ne pouvez pas utiliser d'indices de type. Tous les exemples ci-dessous déclenchent des erreurs.

Vous ne pouvez pas utiliser les Enums comme types :

enum MoveDirection {UP, DOWN, LEFT, RIGHT}
var current_direction: MoveDirection

Vous ne pouvez pas spécifier le type de membres individuels dans un tableau. Cela vous donnera une erreur :

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]

Vous ne pouvez pas forcer l'affectation de types dans une boucle for, car chaque élément sur lequel le mot-clé for boucle est déjà associé a un type différent. Donc, vous ne pouvez pas écrire :

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

Deux scripts ne peuvent pas dépendre l'un de l'autre de manière cyclique :

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

Résumé

Le GDScript typé est un outil puissant. Disponible depuis la version 3.1 de Godot, il aide à écrire du code plus structuré, à éviter les erreurs courantes et à créer des systèmes évolutifs. À l'avenir, les types statiques apporteront également de belles performances grâce aux optimisations à venir du compilateur.