✒️ Scripting des noeuds

Dans Godot, chaque noeud peut avoir un script qui définit son comportement.

Le script hérite toujours d’une classe de noeud (comme Node2D, CharacterBody2D, etc.) et peut redéfinir des fonctions virtuelles pour réagir aux événements du moteur.

📌 Important

Un noeud ne peut avoir qu’un seul script attaché à la fois.

🔥 Exemple

Voici un exemple de script simpliste pour un personnage en vue du dessus.

extends CharacterBody2D


@export var speed: float = 200


func _ready() -> void:
    motion_mode = CharacterBody2D.MOTION_MODE_FLOATING
    $AnimationPlayer.play("spawn")


func _physics_process(delta: float) -> void:
    var direction = Input.get_vector(
        "move_west",
        "move_east",
        "move_north",
        "move_south",
    )
    velocity = direction * speed
    move_and_slide()

Cycle de vie

Le moteur appelle automatiquement certaines fonctions spéciales durant le cycle de vie d’un noeud. Ces fonctions, dites virtuelles, peuvent être redéfinies dans votre script pour contrôler le comportement du noeud.

Voici les quatre fonctions les plus communément utilisées :

Fonction Appelée quand ?
_ready() Une seule fois, quand le nœud est ” prêt “” (lui et tous ses enfants sont entrés dans l’arbre du jeu). Idéal pour l’initialisation.
_input(event) À chaque événement d’entrée (clavier, souris, etc.)
_process(delta) À chaque frame (delta = temps écoulé depuis la dernière frame)
_physics_process(delta) À intervalle fixe (60 FPS par défaut), utilisé pour la physique
📝 Note

C’est grosso-modo le même principe que Awake, Update, FixedUpdate etc. dans Unity.

Annotation @export

Les annotations en GDScript sont des mots-clés précédés du symbole @ qui jouent le rôle de modificateur.

Elles permettent d’indiquer à l’éditeur comment traiter notre code, notamment pour exposer des variables dans l’inspecteur.

Il existe de nombreuses annotations, mais la plus intéressante est l’annotation @export.

Celle-ci permet de rendre une variable modifiable directement depuis l’inspecteur de l’éditeur, sans avoir à changer le code. Elle se place devant la déclaration de la variable :

@export var <nom_de_la_variable>: <type_de_la_variable> = <valeur_par_défaut>

La valeur définie dans l’inspecteur remplace la valeur par défaut écrite dans le script. Cela permet d’avoir plusieurs noeud qui utilisent le même script, mais avec des réglages différents.

🔥 Exemple
@export var speed: float = 300.0
@export var max_health: int = 100
@export var player_name: String = "Noé"

Ces trois variables apparaissent dans l’inspecteur du noeud, et peuvent être ajustées sans ouvrir le script.

Variables exportées affichés et modifiable dans l’inspecteur

Accéder aux autres noeuds de l’arbre

Un jeu Godot étant composé de noeuds, on aura, en tout logique besoin de les manipuler depuis notre le code.

Que ce soit pour déclencher une animation, désactiver une collision, jouer un son etc., il faudra qu’on puisse accéder aux autres noeuds dans l’arbre.

Il est possible de référencer les noeuds de différentes façon :

Par chemin

Comme expliqué dans Noeuds et Scènes, les noeuds peuvent être accédés à l’aide de chemins, comme pour des fichiers.

Pour ce faire on utilise la fonction built-in get_node(path) ou le raccourci $.

extends Node2D

func _ready() -> void:
    # Accès à noeud via get_node
    var animation_player: AnimationPlayer = get_node("Zone/AnimationPlayer")
    animation_player.play()

    # Accès à noeud en utilisant la syntaxe $ (équivalent à get_node)
    var collision: CollisionShape2D = $Zone/Collision
    collision.disabled = true
📝 Note

Le raccourci $ est plus performant que get_node(path), donc il est à privilégier si on fait des accès par chemin.

⚠️ Attention

Bien que pratique, l’accès par chemin rend le script directement dépendant d’une structure d’aboresence :

Par référence directe via @export

La façon privilégié pour accéder les noeuds, c’est la référence directe.

Autres fonctionnalités

Nommer une classe avec class_name

Par défaut, les scripts GDScript sont anonymes. L’annotation class_name permet de donner un nom global à votre classe, la rendant accessible partout dans votre projet et visible dans l’éditeur.

class_name Player
extends CharacterBody2D

@export var speed: float = 300

Une fois nommée, vous pouvez référencer cette classe dans d’autres scripts et elle apparaîtra dans la liste des types disponibles lors de la création de noeuds.

Casting

L’opérateur as permet de caster une variable vers un certain type. Si la conversion échoue, la variable vaudra null.

func _ready() -> void:
    var node = get_node("Player")
    
    var player := node as Player
    if player:
        player.speed = 300
📝 Note

L’opérateur := permet de déduire automatiquement le type de la variable à partir de sa valeur d’initialisation, évitant ainsi de répéter le type explicitement.

Getters et Setters

Les getters et setters permettent d’exécuter du code personnalisé lors de la lecture ou de l’écriture d’une variable. C’est utile pour valider des valeurs, déclencher des événements ou mettre à jour l’interface.

var health: int = 100:
    set(value):
        health = clamp(value, 0, 100)
        update_health_bar()
    get:
        return health

var speed: float = 200.0:
    set(value):
        if value < 0:
            get_tree().quit() # negative speed is punished by closing the game
        speed = value 

var is_alive: bool:
    get:
        return health > 0

func update_health_bar() -> void:
    $HealthBar.value = health

Exposer des variables avec @export

L’annotation @export permet d’exposer des variables dans l’inspecteur de Godot, les rendant modifiables directement depuis l’éditeur sans toucher au code.

@export_group("Stats")
@export var health: int = 100
@export var speed: float = 200.0

Vous pouvez également exporter des références directes aux noeuds, évitant ainsi l’utilisation de chemins :

@export var animation_player: AnimationPlayer
@export var sprite: Sprite2D

func _ready() -> void:
    # Plus besoin de $AnimationPlayer
    animation_player.play("idle")
    sprite.modulate = Color.RED

Script exécutable en mode édition avec @tool

L’annotation @tool rend un script exécutable dans l’éditeur, avant même de lancer le jeu. C’est utile pour créer des outils personnalisés ou visualiser des changements en temps réel.

@tool
extends Node2D

@export var radius: float = 50.0:
    set(value):
        radius = value
        resize()

func resize() -> void:
    var shape := $Collision.shape as CircleShape2D
    shape.radius = radius
⚠️ Attention

Avec @tool, votre code s’exécute dans l’éditeur, c’est très puissant mais c’est aussi un moyen de se tirer une balle dans le pied, donc à utiliser avec précaution.

Précharger des resources avec preload

La fonction preload charge une resource au moment de la compilation du script, garantissant qu’elle est disponible immédiatement.

const BULLET_SCENE = preload("res://scenes/bullet.tscn")
const EXPLOSION_SOUND = preload("res://sounds/explosion.wav")

func shoot() -> void:
    var bullet = BULLET_SCENE.instantiate()
    add_child(bullet)

Pour un chargement dynamique (au moment de l’exécution), utilisez plutôt load().

📌 Important

L’inconvénient de preload est qu’il rend le temps de chargement initial plus long, mais load, qui réalise le chargement sur le moment même, peut produire des lags.

Dans des scénarios plus compliqués, on pourra utiliser des méthodes de chargement de ResourceLoader et afficher une barre de chargement.

Détruire un objet avec queue_free et free

Quand un noeud n’est plus nécessaire, il faut le supprimer pour libérer la mémoire.

func destroy_enemy() -> void:
    queue_free() # Supprime l'ennemi

Conseils et bonnes pratiques

Pour améliorer la lisibilité du code je vais vous demander de suivre des conventions de nommage “officielles” :

Pour encore plus de propreté je vous invite à installer le formateur GDScript, téléchargeable ici : https://github.com/GDQuest/GDScript-formatter/releases

Quelques bonnes pratiques