Jour 1 — Premiers pas en programmation avec Godot#

← Programme · Jour 2 →

D'habitude, ce workshop d'initiation se fait avec Processing, un outil pensé pour découvrir la programmation en dessinant. Ce cours tente d'appliquer la même démarche dans Godot, pour vous familiariser avec ce moteur. Ce n'est pas une présentation complète de l'environnement Godot : on s'en sert juste comme d'une feuille de dessin programmable.

Aucune connaissance en programmation n'est nécessaire. On part d'un simple cercle, et on ajoute une seule idée nouvelle à chaque étape.

Étape Ce qu'on fait Ce qu'on apprend
0 Créer un projet, afficher un message instruction, script, console
1 Dessiner un cercle coloré coordonnées, variable, fonction
2 Le faire trembler au hasard boucle de jeu, hasard, condition
3 Créer des baballes en cliquant événement, liste, boucle for, classe
4 (si on a le temps) Des baballes en image avec Sprite2D héritage, nœuds de Godot

Comment lire ce document

Ressources :


Étape 0 — Créer un projet et dire bonjour#

  1. Ouvrez Godot et créez un nouveau projet.

    Nouveau projet

  2. Choisissez Scène 2D : Godot crée un nœud Node2D, la « toile » sur laquelle on va dessiner.

    Scène 2D

  3. Attachez-lui un script (clic droit sur le nœud → Attacher un script, langage GDScript) :

    Attacher un script

  4. Effacez tout ce qu'il y a dans le script et écrivez :

extends Node2D

func _ready():
    print("Bonjour !")
  1. Lancez le jeu avec le bouton ▶ en haut à droite. Godot demande quelle scène lancer : cliquez sur Choisir l'actuelle, puis enregistrez.

    Choisir l'actuelle

Une fenêtre grise s'ouvre… et « Bonjour ! » s'affiche dans l'onglet Sortie, en bas de l'éditeur.

Sortie

Ce qu'on vient d'apprendre

Défi essentiel : affichez trois messages différents, sur trois lignes.

Solution du défi
extends Node2D

func _ready():
    print("Bonjour !")
    print("Je m'appelle Godot.")
    print("J'apprends à programmer.")

Chaque print affiche une ligne. Les trois instructions sont exécutées dans l'ordre, de haut en bas.


Étape 1 — Afficher un cercle coloré#

Où est quoi ? Les coordonnées#

Chaque point de la fenêtre a une adresse : x (horizontal) et y (vertical), en pixels.

(0, 0) ────────────► x
  │
  │        ● (400, 300)
  │
  ▼
  y

Attention, piège : le point (0, 0) est en haut à gauche, et y augmente vers le bas (l'inverse des maths au lycée).

Le code#

extends Node2D

var rayon = 20                   # taille en pixels
var couleur = Color.ORANGE       # couleur de remplissage
var couleur_fond = Color("#1d1b33")   # couleur du fond de la fenêtre
var position_cercle = Vector2(400, 300)   # où placer le cercle (x, y)

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)   # couleur du fond

func _draw():
    draw_circle(position_cercle, rayon, couleur)

Lancez : un disque orange, plein, sans bord, sur un fond bleu nuit.

Comment ça marche ?

Les couleurs#

Plusieurs façons d'écrire une couleur :

Écriture Exemple
Un nom anglais Color.DEEP_PINK, Color.TURQUOISE, Color.GOLD
Rouge, vert, bleu entre 0 et 1 Color(1, 0.4, 0.7)
Un code comme en graphisme Color("#ff66aa")
Avec de la transparence (4ᵉ nombre) Color(1, 0.4, 0.7, 0.5)

D'autres formes : carrés, lignes et texte#

Dans _draw, on peut dessiner bien plus que des cercles :

func _draw():
    draw_circle(position_cercle, rayon, couleur)
    draw_rect(Rect2(440, 280, 40, 40), Color.TURQUOISE)                # carré
    draw_line(Vector2(320, 350), Vector2(480, 350), Color.WHITE, 3)    # ligne
    draw_string(ThemeDB.fallback_font, Vector2(320, 260), "Bonjour !", HORIZONTAL_ALIGNMENT_LEFT, -1, 24, Color.GOLD)   # texte

Défis#

  1. essentiel Changez la couleur et la taille du cercle, et la couleur du fond.
  2. essentiel Dessinez un deuxième cercle, plus petit, d'une autre couleur, collé au premier (ajoutez une ligne dans _draw, par exemple en position_cercle + Vector2(30, 0)).
  3. bonus Placez le cercle pile au centre de la fenêtre, quelle que soit sa taille.
Solution du défi 1 : couleurs et taille

Il suffit de changer les valeurs rangées dans les variables, en haut du script :

var rayon = 50                        # plus grand
var couleur = Color.DEEP_PINK         # une autre couleur
var couleur_fond = Color("#ffe066")   # un fond jaune

C'est l'intérêt des variables : on change une valeur à un seul endroit, et tout le programme suit.

Solution du défi 2 : un deuxième cercle
func _draw():
    draw_circle(position_cercle, rayon, couleur)
    draw_circle(position_cercle + Vector2(30, 0), 10, Color.TURQUOISE)   # 2e cercle, à droite du premier

Le premier cercle a un rayon de 20 et le deuxième un rayon de 10 : en plaçant le centre du deuxième à 20 + 10 = 30 pixels à droite du centre du premier, les deux cercles se touchent. position_cercle + Vector2(30, 0) veut dire « 30 pixels plus à droite que le premier cercle » : si on déplace le premier, le deuxième suit.

Solution du défi 3 : pile au centre
func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)
    position_cercle = get_viewport_rect().size / 2   # taille de la fenêtre divisée par 2

Ce qu'on vient d'apprendre : coordonnées x / y, variable, Vector2, commentaire, fonction de dessin.


Étape 2 — Faire bouger le cercle au hasard#

On veut que le cercle tremble : à chaque instant, il avance d'un pixel au hasard (ou recule, ou ne bouge pas), en x et en y.

La boucle de jeu#

Un jeu vidéo, c'est une image fixe redessinée environ 60 fois par seconde. Entre deux images, Godot appelle la fonction _process : c'est là qu'on met ce qui change avec le temps.

extends Node2D

var rayon = 20
var couleur = Color.ORANGE
var couleur_fond = Color("#1d1b33")
var position_cercle = Vector2(400, 300)

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)   # couleur du fond

func _process(delta):
    # À chaque image : on ajoute -1, 0 ou +1 à la position
    position_cercle.x = position_cercle.x + randi_range(-1, 1)
    position_cercle.y = position_cercle.y + randi_range(-1, 1)
    queue_redraw()   # la position a changé : on redessine

func _draw():
    draw_circle(position_cercle, rayon, couleur)

Comment ça marche ?

Le cercle fait une marche aléatoire (en anglais random walk). C'est lent et hésitant : c'est normal !

Raccourci : position_cercle.x += randi_range(-1, 1) fait exactement la même chose que position_cercle.x = position_cercle.x + randi_range(-1, 1).

Défis#

  1. essentiel Rendez le cercle plus nerveux : créez une variable pas = 5 et tirez au hasard entre -pas et pas.
  2. essentiel Changez la couleur au hasard à chaque image (indice : randf() donne un nombre à virgule entre 0 et 1).
  3. essentiel Le cercle finit par sortir de l'écran. S'il sort à droite, faites-le revenir à gauche.
  4. bonus Faites respirer le cercle : le rayon change aussi au hasard (sans devenir négatif !).
Solution des défis 1, 2 et 4
extends Node2D

var rayon = 20
var couleur = Color.ORANGE
var couleur_fond = Color("#1d1b33")
var position_cercle = Vector2(400, 300)
var pas = 5    # défi 1 : la taille des pas

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)

func _process(delta):
    # Défi 1 : des pas plus grands
    position_cercle.x = position_cercle.x + randi_range(-pas, pas)
    position_cercle.y = position_cercle.y + randi_range(-pas, pas)

    # Défi 2 : une couleur au hasard (rouge et vert fixes, bleu au hasard)
    couleur = Color(1, 0.4, randf())

    # Défi 4 : le rayon change au hasard, sans descendre sous 5
    rayon = rayon + randi_range(-1, 1)
    if rayon < 5:
        rayon = 5

    queue_redraw()   # la position, la couleur et le rayon ont changé : on redessine

func _draw():
    draw_circle(position_cercle, rayon, couleur)

Essayez aussi Color(randf(), randf(), randf()) : les trois composantes au hasard, ça clignote beaucoup plus !

Solution du défi 3 : la condition if
func _process(delta):
    position_cercle.x += randi_range(-pas, pas)
    position_cercle.y += randi_range(-pas, pas)

    var taille = get_viewport_rect().size
    if position_cercle.x > taille.x:     # SI le cercle dépasse à droite…
        position_cercle.x = 0            # … ALORS on le remet à gauche
    if position_cercle.x < 0:
        position_cercle.x = taille.x
    if position_cercle.y > taille.y:
        position_cercle.y = 0
    if position_cercle.y < 0:
        position_cercle.y = taille.y

    queue_redraw()

if veut dire « si » : les lignes décalées en dessous ne s'exécutent que si la condition est vraie. Comparaisons possibles : >, <, >=, <=, == (égal), != (différent).

Ce qu'on vient d'apprendre : la boucle de jeu (_process), le hasard, modifier une variable, la condition if.


Étape 3 — Créer des baballes en cliquant#

Objectif : à chaque clic, une nouvelle baballe apparaît sous la souris et se met à trembler elle aussi. On y va en trois temps.

3a — Écouter la souris : les événements#

Quand on clique, appuie sur une touche ou bouge la souris, Godot crée un événement (event) et appelle la fonction _input. Ajoutez-la à votre script :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        print("Clic en ", event.position)

Cliquez dans la fenêtre : la position du clic s'affiche dans la Sortie.

3b — Se souvenir de plusieurs cercles : la liste#

Jusqu'ici, une seule variable position_cercle retenait où était le cercle. Avec plusieurs cercles, il faut que chaque cercle retienne sa propre position.

Une variable range une valeur. Pour des dizaines de cercles, on utilise une liste (array) : une rangée de cases numérotées à partir de 0.

positions = [ (400, 300), (120, 80), (650, 410) ]
                 case 0      case 1     case 2
extends Node2D

var positions = [Vector2(400, 300)]   # une liste qui contient déjà 1 position
var rayon = 20
var couleur = Color.ORANGE
var couleur_fond = Color("#1d1b33")

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)   # couleur du fond

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        positions.append(event.position)   # on ajoute la position du clic à la fin de la liste
        print("Nombre de cercles : ", positions.size())

func _process(delta):
    # Pour chaque numéro de case i, de 0 jusqu'à la fin de la liste…
    for i in range(positions.size()):
        positions[i] += Vector2(randi_range(-1, 1), randi_range(-1, 1))
    queue_redraw()

func _draw():
    # Pour chaque position p rangée dans la liste…
    for p in positions:
        draw_circle(p, rayon, couleur)

Nouveautés :

Pourquoi deux façons d'écrire le for ? Dans _draw, on ne fait que lire chaque position : for p in positions suffit. Dans _process, on veut modifier la liste : il faut passer par le numéro de case, positions[i]. Si on écrivait p += ..., on modifierait une copie de la position, et rien ne bougerait. Essayez pour voir !

3c — Des baballes toutes différentes : la classe#

Problème : toutes nos baballes ont la même couleur et la même taille. On voudrait que chacune ait les siennes. On pourrait faire trois listes (positions, rayons, couleurs)… mais on s'y perdrait vite.

Solution : on écrit une fiche « Baballe » qui regroupe tout ce qu'une baballe sait (sa position, sa taille, sa couleur) et tout ce qu'elle sait faire (bouger). C'est une classe.

Image : la classe, c'est le moule à gâteau. Chaque baballe créée avec Baballe.new(...) est un gâteau sorti du moule : un objet. Même forme, mais chaque gâteau peut avoir sa propre déco.

extends Node2D

# ----- Le moule : la classe Baballe -----
class Baballe:
    var position
    var rayon
    var couleur

    # _init est appelée à la naissance de chaque baballe
    func _init(position_de_depart):
        position = position_de_depart
        rayon = randi_range(10, 30)
        couleur = Color(randf(), randf(), randf())

    # Ce qu'une baballe sait faire
    func bouger():
        position.x += randi_range(-1, 1)
        position.y += randi_range(-1, 1)

# ----- Le jeu -----
var baballes = []   # la liste de toutes les baballes
var couleur_fond = Color("#1d1b33")

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)   # couleur du fond
    baballes.append(Baballe.new(Vector2(400, 300)))   # une première baballe au centre

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        baballes.append(Baballe.new(event.position))

func _process(delta):
    for b in baballes:
        b.bouger()
    queue_redraw()

func _draw():
    for b in baballes:
        draw_circle(b.position, b.rayon, b.couleur)

Nouveautés :

Défis#

  1. essentiel Clic droit = tout effacer (indice : MOUSE_BUTTON_RIGHT et baballes.clear()).
  2. essentiel Donnez à chaque baballe son propre pas : certaines calmes, d'autres très nerveuses.
  3. bonus Les baballes grossissent petit à petit, et disparaissent au-delà d'un rayon de 100.
  4. bonus Peinture : créez des baballes quand on glisse la souris bouton enfoncé (indice : InputEventMouseMotion).
  5. bonus Ajoutez à la classe une fonction est_touchee(point) qui dit si un point est dans la baballe, et faites éclater la baballe sur laquelle on clique (indice : position.distance_to(point) < rayon).
Solution du défi 1 : clic droit = tout effacer

Dans _input, on ajoute un deuxième test pour le bouton droit :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        baballes.append(Baballe.new(event.position))
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
        baballes.clear()   # vide la liste : plus aucune baballe

Comme _process appelle queue_redraw() à chaque image, l'écran se vide tout seul.

Solution du défi 2 : un pas pour chaque baballe

Le pas devient une variable de la classe : chaque baballe a le sien, tiré au hasard à sa naissance.

class Baballe:
    var position
    var rayon
    var couleur
    var pas

    func _init(position_de_depart):
        position = position_de_depart
        rayon = randi_range(10, 30)
        couleur = Color(randf(), randf(), randf())
        pas = randi_range(1, 8)   # 1 = calme, 8 = très nerveuse

    func bouger():
        position.x += randi_range(-pas, pas)
        position.y += randi_range(-pas, pas)

Le reste du script ne change pas : c'est tout l'intérêt de la classe.

Solution du défi 3 : grossir et disparaître

Dans la classe, bouger() devient :

    func bouger():
        position.x += randi_range(-1, 1)
        position.y += randi_range(-1, 1)
        rayon += 0.1

Et dans _process, on garde seulement les baballes assez petites :

func _process(delta):
    for b in baballes:
        b.bouger()
    baballes = baballes.filter(func(b): return b.rayon < 100)   # on ne garde que les baballes de rayon < 100
    queue_redraw()

On ne retire jamais d'éléments d'une liste pendant qu'on la parcourt avec for : c'est une source de bugs classique. filter fabrique une nouvelle liste qui ne garde que les baballes qui respectent la condition.

Solution du défi 4 : peindre avec des baballes

Quand la souris bouge, Godot envoie un événement InputEventMouseMotion. On crée une baballe seulement si le bouton gauche est enfoncé :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        baballes.append(Baballe.new(event.position))
    if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
        baballes.append(Baballe.new(event.position))

Input.is_mouse_button_pressed(...) répond à la question « ce bouton est-il enfoncé en ce moment ? ». Bougez vite ou lentement : les baballes s'espacent plus ou moins.

Solution du défi 5 : faire éclater une baballe

Dans la classe, une fonction qui répond vrai ou faux (return) :

    func est_touchee(point):
        return position.distance_to(point) < rayon   # vrai si le point est dans le cercle

Dans _input, le clic gauche cherche d'abord une baballe sous la souris. S'il en trouve une, elle éclate ; sinon, on en crée une nouvelle :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        var touchee = null                  # null = « rien pour l'instant »
        for b in baballes:
            if b.est_touchee(event.position):
                touchee = b
        if touchee != null:
            baballes.erase(touchee)         # on retire la baballe de la liste : elle disparaît
        else:
            baballes.append(Baballe.new(event.position))

Ce qu'on vient d'apprendre : événement, liste, boucle for, classe et objet.


Étape 4 (si on a le temps) — Des baballes en image : la classe Sprite2D#

Si on a le temps. Cette étape se fait si l'étape 3 est terminée assez tôt. Sinon, elle sera reprise en début de Jour 2.

Jusqu'ici, on dessinait tout nous-mêmes avec _draw. Godot sait déjà afficher des images : c'est le rôle du nœud Sprite2D. On va transformer notre Baballe en Sprite2D, pour qu'elle hérite de tout ce qu'un sprite sait faire.

Hériter d'une classe de Godot#

class Baballe extends Sprite2D:

se lit : « une Baballe est un Sprite2D, avec en plus ce qu'on ajoute ». C'est l'héritage.

Image : au lieu de fabriquer notre moule à gâteau de zéro, on part d'un moule du commerce (Sprite2D) et on y ajoute notre déco.

Tout ce que Sprite2D sait faire, notre baballe le sait aussi, sans écrire une ligne :

Propriété héritée Ce qu'elle fait
texture l'image à afficher
position où est le sprite (chaque nœud de Godot a sa position)
scale la taille (1 = taille de l'image, 0.5 = moitié)
rotation l'angle, en radians (TAU = un tour complet)
modulate une couleur qui teinte l'image

Le code#

Chaque projet Godot contient déjà une image : l'icône icon.svg. On l'utilise comme baballe.

extends Node2D

# ----- Le moule : une Baballe qui EST un Sprite2D -----
class Baballe extends Sprite2D:
    var pas = 1

    func _init(position_de_depart):
        texture = load("res://icon.svg")              # l'image à afficher
        position = position_de_depart
        scale = Vector2.ONE * randf_range(0.2, 0.6)    # taille au hasard
        modulate = Color(randf(), randf(), randf())    # teinte au hasard
        rotation = randf_range(0, TAU)                 # angle au hasard

    # Chaque baballe a SA PROPRE boucle de jeu !
    func _process(delta):
        position.x += randi_range(-pas, pas)
        position.y += randi_range(-pas, pas)
        rotation += 1 * delta                          # tourne doucement

# ----- Le jeu -----
var couleur_fond = Color("#1d1b33")

func _ready():
    RenderingServer.set_default_clear_color(couleur_fond)
    add_child(Baballe.new(Vector2(400, 300)))       # une première baballe au centre

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        add_child(Baballe.new(event.position))

Ce qui a disparu par rapport à l'étape 3 :

Lancez le jeu, puis regardez l'onglet Scène de l'éditeur et cliquez sur Distant (Remote) : chaque baballe apparaît comme un nœud Sprite2D, enfant du nœud principal. En cliquant dessus, on voit ses propriétés changer en direct dans l'inspecteur.

Votre propre image#

Glissez une image (.png, .jpg, .svg…) dans le panneau Système de fichiers de Godot, puis remplacez "res://icon.svg" par son chemin, par exemple "res://images/balle.png". Clic droit sur le fichier → Copier le chemin évite les fautes de frappe.

Défis#

  1. Utilisez votre propre image (dessin, photo, pixel art…).
  2. Donnez à chaque baballe sa propre vitesse de rotation au hasard, dans un sens ou dans l'autre.
  3. Clic droit = supprimer toutes les baballes (indice : for b in get_children(): et b.queue_free()).
  4. Faites grandir chaque baballe petit à petit (scale *= 1.001), et faites-la disparaître avec queue_free() quand elle devient trop grande.
  5. Affichez le nombre de baballes dans la console à chaque clic (indice : get_child_count()).
Solution du défi 1 : votre propre image
  1. Glissez votre image dans le panneau Système de fichiers, par exemple dans un dossier images.
  2. Clic droit sur le fichier → Copier le chemin.
  3. Dans _init, collez ce chemin à la place de "res://icon.svg" :
        texture = load("res://images/balle.png")      # le chemin de VOTRE image

Si l'image est très grande, changez aussi les nombres de randf_range dans la ligne scale (par exemple 0.05, 0.2).

Solution du défi 2 : une vitesse de rotation pour chaque baballe

On ajoute une variable à la classe, tirée au hasard à la naissance. Un nombre négatif fait tourner dans l'autre sens :

class Baballe extends Sprite2D:
    var pas = 1
    var vitesse_rotation

    func _init(position_de_depart):
        texture = load("res://icon.svg")
        position = position_de_depart
        scale = Vector2.ONE * randf_range(0.2, 0.6)
        modulate = Color(randf(), randf(), randf())
        rotation = randf_range(0, TAU)
        vitesse_rotation = randf_range(-3, 3)          # négatif = sens inverse

    func _process(delta):
        position.x += randi_range(-pas, pas)
        position.y += randi_range(-pas, pas)
        rotation += vitesse_rotation * delta           # chacune tourne à sa vitesse

delta est le temps écoulé depuis l'image précédente (en secondes) : vitesse_rotation * delta donne une vitesse en radians par seconde, la même sur un ordinateur rapide ou lent.

Solution du défi 3 : clic droit = supprimer toutes les baballes

Dans _input du jeu :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        add_child(Baballe.new(event.position))
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
        for b in get_children():      # tous les enfants du nœud principal : nos baballes
            b.queue_free()            # chacune est supprimée à la fin de l'image

Plus de liste à vider comme à l'étape 3 : les baballes sont les enfants du nœud principal, et get_children() les donne toutes.

Solution du défi 4 : grandir et disparaître

Dans le _process de la classe Baballe :

    func _process(delta):
        position.x += randi_range(-pas, pas)
        position.y += randi_range(-pas, pas)
        rotation += 1 * delta
        scale *= 1.001               # grandit de 0,1 % à chaque image
        if scale.x > 2:
            queue_free()             # « Godot, supprime-moi à la fin de l'image »

queue_free() retire le nœud de la scène et le détruit. La baballe se supprime elle-même : pas besoin de liste ni de filter comme à l'étape 3.

Solution du défi 5 : compter les baballes

Dans _input, juste après avoir ajouté la baballe :

func _input(event):
    if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
        add_child(Baballe.new(event.position))
        print("Nombre de baballes : ", get_child_count())   # nombre d'enfants du nœud principal

print accepte plusieurs valeurs séparées par des virgules : il les colle bout à bout. Le nombre s'affiche dans l'onglet Sortie.

Ce qu'on vient d'apprendre : héritage (extends), les nœuds de Godot, Sprite2D, add_child et queue_free.


Récapitulatif : le vocabulaire du jour#

Mot En une phrase Exemple
Instruction un ordre donné à l'ordinateur print("Bonjour !")
Variable une boîte avec un nom, qui range une valeur var rayon = 20
Fonction un bloc d'instructions avec un nom func _draw():
Boucle de jeu ce qui se répète à chaque image func _process(delta):
Hasard un nombre tiré au sort randi_range(-1, 1)
Condition faire quelque chose seulement si… if position_cercle.x > 800:
Événement quelque chose que le.la joueur.euse vient de faire func _input(event):
Liste une rangée de cases numérotées à partir de 0 var baballes = []
Boucle for répéter pour chaque élément for b in baballes:
Classe / objet un moule / ce qui sort du moule Baballe.new(...)
Héritage partir d'une classe existante et y ajouter des choses class Baballe extends Sprite2D:
Nœud un élément de la scène Godot, qui peut avoir des enfants add_child(...), queue_free()