Un projet qui illustre parfaitement ce que signifie apprendre à coder en pensant différemment : un moteur 3D voxel isométrique, entièrement développé dans EduCode.
Ce programme permet aux élèves de manipuler un environnement 3D, de construire des blocs, de gérer une caméra, de zoomer, de tourner la scène, et même d’afficher un personnage animé. Le tout dans un langage pédagogique pensé pour rendre la logique accessible.
Un moteur 3D… expliqué simplement
Le cœur du moteur repose sur une boucle de rendu qui projette chaque bloc en isométrie. Comme le dit le code :
Un moteur 3D… expliqué simplement
Le cœur du moteur repose sur une boucle de rendu qui projette chaque bloc en isométrie. Comme le dit le code :
« graph est une toile » « tant que vrai… effacer(graph) »
Chaque frame reconstruit la scène : sol, blocs empilés, personnage, interface HUD. Les élèves découvrent ainsi les notions de :
- Projection isométrique
- Gestion de dictionnaires pour stocker les blocs
- Rendu multi‑faces (haut, gauche, droite)
- Caméra orientable (0°, 90°, 180°, 270°)
- Zoom dynamique
- Interaction clavier en temps réel
- Deuxième élément
- Troisième élément
Construire, détruire, expérimenter
Le moteur intègre une vraie logique de gameplay :
- E → Empiler un bloc
- R → Casser le bloc du dessus
- C → Changer de matériau (Pierre, Brique, Bois, Verre, Feuillage)
- Z / S / Q / D → Se déplacer
- T → Tourner la caméra
- + / - → Zoomer
Chaque bloc est stocké dans un dictionnaire avec sa hauteur et son type. Le code le montre très bien :
« typesBlocs[cle3D] vaut typeBlocConstruction »
Les élèves comprennent ainsi comment fonctionne un moteur de jeu : stockage, rendu, interaction.
Un rendu 3D immersif
Le moteur dessine chaque bloc avec trois faces colorées, créant un effet 3D convaincant. Le personnage lui-même est composé de formes simples : rectangles arrondis, ellipses, cercles… une manière ludique d’aborder la composition graphique.
Pourquoi ce projet compte
Parce qu’il montre que l’apprentissage du code peut être créatif, visuel, motivant.
EduCode n’est pas seulement un outil : c’est un terrain de jeu intellectuel où les élèves apprennent à :
- structurer leurs idées,
- comprendre la logique spatiale,
- manipuler des données,
- créer un univers interactif.
Et comme le rappelle le programme lui-même :
« Apprendre à coder, c'est avant tout apprendre à penser ! »
Conclusion
Ce moteur 3D n’est pas juste un exercice : c’est une preuve que, même avec un langage pédagogique, on peut créer des expériences riches, immersives et formatrices.
Programme :
graph est une toile
dimension(graph, 600, 500)
// Position du joueur
jX est un nombre
jY est un nombre
jX vaut 0
jY vaut 0
// Facteur de zoom (échelle de rendu)
echelle est un nombre
echelle vaut 1,0
// Orientation de la caméra (0: 0°, 1: 90°, 2: 180°, 3: 270°)
orientation est un nombre
orientation vaut 0
// Dictionnaires pour la hauteur et les types de blocs personnalisés
hauteursBlocs est un dictionnaire
hauteursBlocs vaut [:]
typesBlocs est un dictionnaire
typesBlocs vaut [:]
// Type de bloc actif (1: Pierre, 2: Brique, 3: Bois, 4: Verre, 5: Feuillage)
typeBlocConstruction est un nombre
typeBlocConstruction vaut 1
action est un nombre
@ Boucle principale
tant que vrai
effacer(graph)
remplir(graph, #090d16) // Fond ciel de nuit profond
// Calcul des pas isométriques dynamiques selon le zoom
dx est un nombre
dy est un nombre
dx vaut 24 * echelle
dy vaut 12 * echelle
// Balayage du champ de vision (rayon autour du joueur)
rY est un nombre
rX est un nombre
pour rY de - 10 à 10
pour rX de - 10 à 10
// Rotation des coordonnées du monde autour du joueur selon l'orientation
offX est un nombre
offY est un nombre
si orientation = 0 alors
offX vaut rX
offY vaut rY
sinon si orientation = 1 alors
offX vaut - rY
offY vaut rX
sinon si orientation = 2 alors
offX vaut - rX
offY vaut - rY
sinon
offX vaut rY
offY vaut - rX
fin si
gx est un nombre
gy est un nombre
gx vaut jX + offX
gy vaut jY + offY
// Projection isométrique centrée sur la toile (300, 250)
cx est un nombre
cy est un nombre
cx vaut 300 + (rX - rY) * dx
cy vaut 250 + (rX + rY) * dy
// Culling élargi pour le rendu
si cx >= - 100 et cx <= 700 et cy >= - 100 et cy <= 600 alors
cleCoord est un texte
cleCoord vaut convertir_texte(gx) + "_" + convertir_texte(gy)
hauteurTotale est un nombre
hauteurTotale vaut 0
si contient(hauteursBlocs, cleCoord) alors
hauteurTotale vaut hauteursBlocs[cleCoord]
fin si
// --- 1. RENDU DU SOL DE BASE ---
baseYVal est un nombre
baseYVal vaut cy
profondeurSol est un nombre
profondeurSol vaut 20 * echelle
// Face Gauche du sol de base
ptsSolFG est un tableau
ptsSolFG ajoute cx - dx
ptsSolFG ajoute baseYVal
ptsSolFG ajoute cx
ptsSolFG ajoute baseYVal + dy
ptsSolFG ajoute cx
ptsSolFG ajoute baseYVal + dy + profondeurSol
ptsSolFG ajoute cx - dx
ptsSolFG ajoute baseYVal + profondeurSol
polygone(graph, ptsSolFG, #1e293b)
// Face Droite du sol de base
ptsSolFD est un tableau
ptsSolFD ajoute cx
ptsSolFD ajoute baseYVal + dy
ptsSolFD ajoute cx + dx
ptsSolFD ajoute baseYVal
ptsSolFD ajoute cx + dx
ptsSolFD ajoute baseYVal + profondeurSol
ptsSolFD ajoute cx
ptsSolFD ajoute baseYVal + dy + profondeurSol
polygone(graph, ptsSolFD, #0f172a)
// Face Supérieure du sol de base (Herbe)
ptsSolTop est un tableau
ptsSolTop ajoute cx
ptsSolTop ajoute baseYVal - dy
ptsSolTop ajoute cx + dx
ptsSolTop ajoute baseYVal
ptsSolTop ajoute cx
ptsSolTop ajoute baseYVal + dy
ptsSolTop ajoute cx - dx
ptsSolTop ajoute baseYVal
polygone(graph, ptsSolTop, #22c55e)
contour(graph, #15803d, 1)
// --- 2. RENDU DES BLOCS EMPILÉS EN HAUTEUR ---
si hauteurTotale > 0 alors
hauteurCube est un nombre
hauteurCube vaut 24 * echelle
pour k de 0 à hauteurTotale - 1
cle3D_rendu est un texte
cle3D_rendu vaut cleCoord + "_" + convertir_texte(k)
typeBlocVal est un nombre
si contient(typesBlocs, cle3D_rendu) alors
typeBlocVal vaut typesBlocs[cle3D_rendu]
sinon
typeBlocVal vaut 1
fin si
couleurTop est un texte
couleurGauche est un texte
couleurDroite est un texte
// Matériaux : 1=Pierre, 2=Brique, 3=Bois, 4=Verre, 5=Feuillage
si typeBlocVal = 1 alors
couleurTop vaut #f1f5f9
couleurGauche vaut #94a3b8
couleurDroite vaut #64748b
sinon si typeBlocVal = 2 alors
couleurTop vaut #f87171
couleurGauche vaut #dc2626
couleurDroite vaut #b91c1c
sinon si typeBlocVal = 3 alors
couleurTop vaut #fbbf24
couleurGauche vaut #d97706
couleurDroite vaut #b45309
sinon si typeBlocVal = 4 alors
// Verre (semi-transparent)
couleurTop vaut rgba(224, 242, 254, 0.65)
couleurGauche vaut rgba(148, 163, 184, 0.55)
couleurDroite vaut rgba(100, 116, 139, 0.55)
sinon
// Feuillage (semi-transparent)
couleurTop vaut rgba(74, 222, 128, 0.8)
couleurGauche vaut rgba(22, 163, 74, 0.75)
couleurDroite vaut rgba(21, 128, 61, 0.75)
fin si
lCY est un nombre
lCY vaut cy - hauteurCube - (k * hauteurCube)
// Face Gauche
ptsFG est un tableau
ptsFG ajoute cx - dx
ptsFG ajoute lCY
ptsFG ajoute cx
ptsFG ajoute lCY + dy
ptsFG ajoute cx
ptsFG ajoute lCY + dy + hauteurCube
ptsFG ajoute cx - dx
ptsFG ajoute lCY + hauteurCube
polygone(graph, ptsFG, couleurGauche)
// Face Droite
ptsFD est un tableau
ptsFD ajoute cx
ptsFD ajoute lCY + dy
ptsFD ajoute cx + dx
ptsFD ajoute lCY
ptsFD ajoute cx + dx
ptsFD ajoute lCY + hauteurCube
ptsFD ajoute cx
ptsFD ajoute lCY + dy + hauteurCube
polygone(graph, ptsFD, couleurDroite)
// Face Supérieure
ptsTop est un tableau
ptsTop ajoute cx
ptsTop ajoute lCY - dy
ptsTop ajoute cx + dx
ptsTop ajoute lCY
ptsTop ajoute cx
ptsTop ajoute lCY + dy
ptsTop ajoute cx - dx
ptsTop ajoute lCY
polygone(graph, ptsTop, couleurTop)
contour(graph, #475569, 1)
fin pour
fin si
// --- 3. PERSONNAGE PRINCIPAL 3D ---
si rX = 0 et rY = 0 alors
decalagePersoY est un nombre
decalagePersoY vaut hauteurTotale * 24 * echelle
pCY est un nombre
pCY vaut cy - decalagePersoY
// Ombre au sol
ellipse(graph, cx, pCY + (2 * echelle), 12 * echelle, 6 * echelle, rgba(0, 0, 0, 0.4))
// Jambes
rectangle_arrondi(graph, cx - (4 * echelle), pCY - (10 * echelle), 8 * echelle, 10 * echelle, 2, #1e3a8a)
// Bras gauche
rectangle_arrondi(graph, cx - (9 * echelle), pCY - (21 * echelle), 4 * echelle, 9 * echelle, 2, #fcd34d)
// Corps
rectangle_arrondi(graph, cx - (5 * echelle), pCY - (22 * echelle), 10 * echelle, 13 * echelle, 2, #38bdf8)
rectangle(graph, cx - (1.5 * echelle), pCY - (22 * echelle), 3 * echelle, 13 * echelle, #e0f2fe)
// Bras droit
rectangle_arrondi(graph, cx + (5 * echelle), pCY - (21 * echelle), 4 * echelle, 9 * echelle, 2, #fcd34d)
// Tête
rectangle_arrondi(graph, cx - (6 * echelle), pCY - (32 * echelle), 12 * echelle, 11 * echelle, 3, #fcd34d)
// Cheveux
rectangle_arrondi(graph, cx - (6.5 * echelle), pCY - (34 * echelle), 13 * echelle, 5 * echelle, 2, #ea580c)
// Yeux
cercle(graph, cx - (2.5 * echelle), pCY - (27 * echelle), 1.5 * echelle, #0f172a)
cercle(graph, cx + (2.5 * echelle), pCY - (27 * echelle), 1.5 * echelle, #0f172a)
fin si
fin si
fin pour
fin pour
// --- INTERFACE HUD & INFORMATIONS ---
contour(graph, #0)
rectangle(graph, 0, 0, 600, 65, rgba(15, 23, 42, 0.55))
label(graph, 12, 18, "Voxel 3D Engine", #ffffff, 12)
// Nom du matériau actif
nomMateriau est un texte
si typeBlocConstruction = 1 alors
nomMateriau vaut "Pierre"
sinon si typeBlocConstruction = 2 alors
nomMateriau vaut "Brique"
sinon si typeBlocConstruction = 3 alors
nomMateriau vaut "Bois"
sinon si typeBlocConstruction = 4 alors
nomMateriau vaut "Verre"
sinon
nomMateriau vaut "Feuillage"
fin si
txtInfos est un texte
txtInfos vaut "X : " + jX + " / Y : " + jY + " # Matériau : " + nomMateriau + " # Zoom : " + convertir_texte(arrondi(echelle * 100)) + "% # Rotation : " + (orientation * 90) + "°"
label(graph, 12, 34, txtInfos, #38bdf8, 11)
label(graph, 12, 52, " Bouger [Z S Q D] # Empiler [E] # Casser [R] # Matériau [C] # Zoom [ + / - ] # Rotation [T]", #ffffff, 10)
// Barre inférieure
rectangle(graph, 0, 470, 600, 30, rgba(15, 23, 42, 0.55))
label(graph, 12, 490, "EduCode v3.1", #FFFFFF, 14)
label(graph, 150, 490, "Apprendre à coder, c'est avant tout apprendre à penser !", #FFFFFF, 14)
// --- ENCADRÉ D'INVENTAIRE (EN BAS À DROITE) ---
rectangle_arrondi(graph, 510, 385, 75, 75, 6, rgba(15, 23, 42, 0.55))
contour(graph, #ffffff, 1)
// Couleurs du bloc pour l'aperçu
couleurSelTop est un texte
couleurSelGauche est un texte
couleurSelDroite est un texte
si typeBlocConstruction = 1 alors
couleurSelTop vaut #f1f5f9
couleurSelGauche vaut #94a3b8
couleurSelDroite vaut #64748b
sinon si typeBlocConstruction = 2 alors
couleurSelTop vaut #f87171
couleurSelGauche vaut #dc2626
couleurSelDroite vaut #b91c1c
sinon si typeBlocConstruction = 3 alors
couleurSelTop vaut #fbbf24
couleurSelGauche vaut #d97706
couleurSelDroite vaut #b45309
sinon si typeBlocConstruction = 4 alors
couleurSelTop vaut rgba(224, 242, 254, 0.65)
couleurSelGauche vaut rgba(148, 163, 184, 0.55)
couleurSelDroite vaut rgba(100, 116, 139, 0.55)
sinon
couleurSelTop vaut rgba(74, 222, 128, 0.8)
couleurSelGauche vaut rgba(22, 163, 74, 0.75)
couleurSelDroite vaut rgba(21, 128, 61, 0.75)
fin si
// Rendu 3D du mini-bloc d'aperçu
mcx est un nombre
mcy est un nombre
mcx vaut 547
mcy vaut 415
mdx est un nombre
mdy est un nombre
mh est un nombre
mdx vaut 16
mdy vaut 8
mh vaut 16
// Face Gauche aperçu
pSelFG est un tableau
pSelFG ajoute mcx - mdx
pSelFG ajoute mcy
pSelFG ajoute mcx
pSelFG ajoute mcy + mdy
pSelFG ajoute mcx
pSelFG ajoute mcy + mdy + mh
pSelFG ajoute mcx - mdx
pSelFG ajoute mcy + mh
polygone(graph, pSelFG, couleurSelGauche)
// Face Droite aperçu
pSelFD est un tableau
pSelFD ajoute mcx
pSelFD ajoute mcy + mdy
pSelFD ajoute mcx + mdx
pSelFD ajoute mcy
pSelFD ajoute mcx + mdx
pSelFD ajoute mcy + mh
pSelFD ajoute mcx
pSelFD ajoute mcy + mdy + mh
polygone(graph, pSelFD, couleurSelDroite)
// Face Supérieure aperçu
pSelTop est un tableau
pSelTop ajoute mcx
pSelTop ajoute mcy - mdy
pSelTop ajoute mcx + mdx
pSelTop ajoute mcy
pSelTop ajoute mcx
pSelTop ajoute mcy + mdy
pSelTop ajoute mcx - mdx
pSelTop ajoute mcy
polygone(graph, pSelTop, couleurSelTop)
contour(graph, #ffffff, 1)
affiche graph
// --- GESTION DES ENTRÉES CLAVIER ---
appuyer ["z", "s", "q", "d", "e", "r", "c", "+", "-", "t"] dans action
mvtX est un nombre
mvtY est un nombre
mvtX vaut 0
mvtY vaut 0
si action = 1 alors
// Z (Avancer)
si orientation = 0 alors
mvtX vaut 0
mvtY vaut - 1
sinon si orientation = 1 alors
mvtX vaut 1
mvtY vaut 0
sinon si orientation = 2 alors
mvtX vaut 0
mvtY vaut 1
sinon
mvtX vaut - 1
mvtY vaut 0
fin si
sinon si action = 2 alors
// S (Reculer)
si orientation = 0 alors
mvtX vaut 0
mvtY vaut 1
sinon si orientation = 1 alors
mvtX vaut - 1
mvtY vaut 0
sinon si orientation = 2 alors
mvtX vaut 0
mvtY vaut - 1
sinon
mvtX vaut 1
mvtY vaut 0
fin si
sinon si action = 3 alors
// Q (Aller à gauche)
si orientation = 0 alors
mvtX vaut - 1
mvtY vaut 0
sinon si orientation = 1 alors
mvtX vaut 0
mvtY vaut - 1
sinon si orientation = 2 alors
mvtX vaut 1
mvtY vaut 0
sinon
mvtX vaut 0
mvtY vaut 1
fin si
sinon si action = 4 alors
// D (Aller à droite)
si orientation = 0 alors
mvtX vaut 1
mvtY vaut 0
sinon si orientation = 1 alors
mvtX vaut 0
mvtY vaut 1
sinon si orientation = 2 alors
mvtX vaut - 1
mvtY vaut 0
sinon
mvtX vaut 0
mvtY vaut - 1
fin si
sinon si action = 5 alors
// EMPILER UN BLOC SOUS LE JOUEUR
cleAction est un texte
cleAction vaut convertir_texte(jX) + "_" + convertir_texte(jY)
zActuel est un nombre
si contient(hauteursBlocs, cleAction) alors
zActuel vaut hauteursBlocs[cleAction]
hauteursBlocs[cleAction] vaut hauteursBlocs[cleAction] + 1
sinon
zActuel vaut 0
hauteursBlocs[cleAction] vaut 1
fin si
cle3D est un texte
cle3D vaut cleAction + "_" + convertir_texte(zActuel)
typesBlocs[cle3D] vaut typeBlocConstruction
sinon si action = 6 alors
// CASSER LE BLOC DU DESSUS
cleAction2 est un texte
cleAction2 vaut convertir_texte(jX) + "_" + convertir_texte(jY)
si contient(hauteursBlocs, cleAction2) alors
zSommet est un nombre
zSommet vaut hauteursBlocs[cleAction2] - 1
cle3D_suppr est un texte
cle3D_suppr vaut cleAction2 + "_" + convertir_texte(zSommet)
typesBlocs supprime cle3D_suppr
hauteursBlocs[cleAction2] vaut hauteursBlocs[cleAction2] - 1
si hauteursBlocs[cleAction2] <= 0 alors
hauteursBlocs supprime cleAction2
fin si
fin si
sinon si action = 7 alors
// CHANGER DE TYPE DE MATÉRIAU (1 à 5)
typeBlocConstruction vaut typeBlocConstruction + 1
si typeBlocConstruction > 5 alors
typeBlocConstruction vaut 1
fin si
sinon si action = 8 alors
// ZOOM AVANT (+)
echelle vaut limiter(echelle + 0.15, 0.6, 2.0)
sinon si action = 9 alors
// ZOOM ARRIÈRE (-)
echelle vaut limiter(echelle - 0.15, 0.6, 2.0)
sinon si action = 10 alors
// ROTATION CAMÉRA (T)
orientation vaut orientation + 1
si orientation > 3 alors
orientation vaut 0
fin si
fin si
// Application des déplacements libres
si action >= 1 et action <= 4 alors
jX vaut jX + mvtX
jY vaut jY + mvtY
fin si
fin tant que

