EduCode
EduCode
← Retour au site
Blog

Articles & Actualités

Les publications d'EduCode : nouveautés du logiciel, astuces de pseudo-code, retours d'expérience et actualités de l'environnement pédagogique.

19 publications

EduCode v3.2 arrive avec une nouveauté très attendue
Nouveauté

EduCode v3.2

EduCode v3.2 arrive avec une nouveauté très attendue.

Lire l'articleReplier l'article

EduCode 3.2 arrive avec une nouveauté très attendue : l'interaction à la souris sur les toiles.

Deux ajouts complémentaires, dans la philosophie du langage (simple, en français, lisible) :

  • Le mot-clé « cliquer » : le programme attend un clic sur le dessin et récupère sa position exacte.
  • La fonction « clique » : elle vérifie si le clic tombe dans une zone précise, rectangulaire ou circulaire.

Ensemble, ils ouvrent la porte aux boutons, aux menus graphiques, aux cibles à attraper et aux jeux de plateau entièrement jouables à la souris, de vraies petites applications interactives, en quelques lignes.

À découvrir dans la version v3.2, prochainement disponible en téléchargement.

Exemples :

maToile est une toile
dimension(maToile, 300, 200)
remplir(maToile, #0d1117)

x, y, i est un nombre vaut 0

pour i de 1 à 5
    affiche maToile
    cliquer maToile dans x, y
    cercle(maToile, x, y, 4, #e53935)

    affiche maToile
    affiche 'Clic ' + i + ' en (X : ' + x + ', Y : ' + y + ')'
fin pour

affiche 'Fin du programme.'
maToile est une toile
dimension(maToile, 300, 200)
remplir(maToile, '#0d1117')

// bouton : mêmes valeurs pour le dessin et le test
rectangle(maToile, 40, 80, 100, 45, '#1976d2')
label(maToile, 60, 108, 'Jouer', '#ffffff', 16)
affiche maToile

x, y est un nombre
clicok est un booléen vaut faux

tant que non clicok
    cliquer maToile dans x, y
    si clique(x, y, 40, 80, 100, 45) alors
        affiche 'Tu as cliqué sur le bouton !'
        clicok vaut vrai
    sinon
        affiche 'Raté, à côté du bouton.'
        clicok vaut faux
    fin si
fin tant que

affiche 'Fin du programme.'
Vue 3ème Personne : un moteur de perspective dynamique pour mondes infinis
Article

Vue 3ème Personne

Vue 3ème Personne : un moteur de perspective dynamique pour mondes infinis

Lire l'articleReplier l'article

La création d’un moteur de rendu en vue à la troisième personne est un défi passionnant, surtout lorsqu’il s’agit de représenter un monde infini avec une perspective qui s’agrandit naturellement à mesure que le joueur s’approche des éléments.

Ce projet explore une approche mathématique et procédurale permettant de simuler une caméra suiveuse immersive, sans modèle 3D, uniquement via des projections et des polygones.

Un moteur basé sur la perspective et la profondeur

Horizon, ciel et ambiance

Le rendu commence par une ligne d’horizon fixe et un ciel stylisé.
Cette base visuelle sert de repère pour la perspective et renforce l’effet de profondeur.

Rendu du terrain : l’algorithme du peintre

Le moteur dessine le monde du fond vers l’avant, une technique inspirée de l’algorithme du peintre.
Chaque couche de terrain est calculée en fonction de sa distance au joueur :

  • plus la tuile est éloignée, plus elle est petite
  • plus elle est proche, plus elle est grande

La projection utilise une formule simple mais efficace :

  • sLoin = 180 / dLoin
  • sPres = 180 / dPres
  • sCentre = 180 / dCentre

Ces valeurs déterminent la taille apparente des tuiles selon leur profondeur.

Projection 3D simulée

Chaque tuile est représentée par un quadrilatère dont les sommets sont calculés pour simuler une perspective :

  • sommet haut gauche
  • sommet haut droit
  • sommet bas droit
  • sommet bas gauche

Cette projection donne l’illusion d’un sol en 3D, sans utiliser de modèles polygonaux complexes.

Génération procédurale du terrain

Le type de sol est déterminé par des règles mathématiques :

  • Herbe
  • Chemins (via mod 14)
  • Eau (via une formule organique)

Cette logique crée un monde cohérent, infini et varié, sans stockage de données.

Décors avec grandissement dynamique
Les arbres, rochers et maisons sont générés procéduralement et agrandissent naturellement lorsqu’ils se rapprochent du joueur.

L’échelle dépend de la profondeur :

  • hTaille = sCentre * 0.85

Ce facteur donne un effet d’approche très immersif, proche d’une caméra 3D.

Personnage principal en vue 3P

Le joueur est représenté par un personnage stylisé placé en bas de l’écran.
La caméra ne bouge pas : c’est le monde qui se déplace autour du joueur, renforçant l’effet de vue à la troisième personne.

Déplacements et collisions

Les déplacements (ZQSD) sont validés par une détection procédurale :

  • impossibilité d’entrer dans l’eau
  • impossibilité d’entrer dans une maison
  • Aucune carte de collision n’est stockée : tout est calculé à la volée.

Pourquoi cette approche est intéressante ?

  • Perspective dynamique sans 3D
  • Monde infini généré en temps réel
  • Effet caméra suiveuse sans transformation complexe
  • Décors qui grandissent naturellement
  • Performances optimisées grâce au rendu du fond vers l’avant
  • Base idéale pour un jeu d’aventure, un RPG ou un simulateur

Programme :

@ Vue à la troisième personne (3P) avec grandissement de perspective

toile1 est une toile
dimension(toile1, 400, 400)

// Position du joueur dans le monde infini
jX est un nombre
jY est un nombre
jX vaut 0
jY vaut 0

action est un nombre

@ Boucle principale
tant que vrai
    effacer(toile1)
    remplir(toile1, #0f172a)

    // 1. Ciel et ligne d'horizon
    rectangle(toile1, 0, 0, 400, 120, #0f172a)
    cercle(toile1, 320, 40, 20, #f1c40f)

    // 2. Rendu du terrain du fond vers l'avant (Algorithme du peintre)
    iY est un nombre
    pour iY de 0 à 15
        rY est un nombre
        rY vaut 15 - iY

        rX est un nombre
        pour rX de - 8 à 8
            gx est un nombre
            gy est un nombre
            gx vaut jX + rX
            gy vaut jY + rY

            nx est un nombre
            ny est un nombre
            nx vaut gx + 50000
            ny vaut gy + 50000

            // Projection en perspective 3D
            dLoin est un nombre
            dPres est un nombre
            dLoin vaut rY + 0.5 + 2.0
            dPres vaut rY - 0.5 + 2.0

            sLoin est un nombre
            sPres est un nombre
            sLoin vaut 180 / dLoin
            sPres vaut 180 / dPres

            xHautG est un nombre
            yHautG est un nombre
            xHautD est un nombre
            yHautD est un nombre
            xBasD est un nombre
            yBasD est un nombre
            xBasG est un nombre
            yBasG est un nombre

            xHautG vaut 200 + (rX - 0.5) * sLoin * 1.2
            yHautG vaut 120 + sLoin * 2.5
            xHautD vaut 200 + (rX + 0.5) * sLoin * 1.2
            yHautD vaut 120 + sLoin * 2.5

            xBasD vaut 200 + (rX + 0.5) * sPres * 1.2
            yBasD vaut 120 + sPres * 2.5
            xBasG vaut 200 + (rX - 0.5) * sPres * 1.2
            yBasG vaut 120 + sPres * 2.5

            dCentre est un nombre
            dCentre vaut rY + 2.0
            sCentre est un nombre
            sCentre vaut 180 / dCentre

            cx est un nombre
            cy est un nombre
            cx vaut 200 + rX * sCentre * 1.2
            cy vaut 120 + sCentre * 2.5

            typeSol est un nombre
            typeSol vaut 0

            si nx mod 14 = 0 ou ny mod 14 = 0 alors
                typeSol vaut 1
            sinon si (nx * 3 + ny * 2) mod 31 < 2 alors
                typeSol vaut 2
            fin si

            ptsSol est un tableau
            ptsSol ajoute xHautG
            ptsSol ajoute yHautG
            ptsSol ajoute xHautD
            ptsSol ajoute yHautD
            ptsSol ajoute xBasD
            ptsSol ajoute yBasD
            ptsSol ajoute xBasG
            ptsSol ajoute yBasG

            si typeSol = 0 alors
                polygone(toile1, ptsSol, #27ae60)
            sinon si typeSol = 1 alors
                polygone(toile1, ptsSol, #d35400)
            sinon
                polygone(toile1, ptsSol, #2980b9)
            fin si

            contour(toile1, #1a252f, 1)

            // Décors
            typeDecor est un nombre
            typeDecor vaut 0

            si typeSol = 0 alors
                hash est un nombre
                hash vaut (nx * 17 + ny * 37) mod 100
                si hash < 10 alors
                    typeDecor vaut 1
                sinon si hash < 14 alors
                    typeDecor vaut 2
                sinon si hash = 15 alors
                    typeDecor vaut 3
                fin si
            fin si

            // Échelle augmentée pour accentuer l'effet d'approche
            hTaille est un nombre
            hTaille vaut sCentre * 0.85

            si typeDecor = 1 alors
                // Arbre
                ellipse(toile1, cx, cy, hTaille * 0.3, hTaille * 0.1, #0e171e)
                rectangle(toile1, cx - hTaille * 0.08, cy - hTaille * 0.6, hTaille * 0.16, hTaille * 0.6, #5d4037)
                cercle(toile1, cx, cy - hTaille * 0.85, hTaille * 0.35, #2e7d32)
            sinon si typeDecor = 2 alors
                // Rocher
                ellipse(toile1, cx, cy, hTaille * 0.25, hTaille * 0.1, #0e171e)
                cercle(toile1, cx, cy - hTaille * 0.15, hTaille * 0.2, #7f8c8d)
            sinon si typeDecor = 3 alors
                // Maison
                mLargeur est un nombre
                mHauteur est un nombre
                mLargeur vaut hTaille * 0.7
                mHauteur vaut hTaille * 0.5

                rectangle(toile1, cx - mLargeur / 2, cy - mHauteur, mLargeur, mHauteur, #bdc3c7)
                toit3P est un tableau
                toit3P ajoute cx - mLargeur * 0.55
                toit3P ajoute cy - mHauteur
                toit3P ajoute cx
                toit3P ajoute cy - mHauteur - hTaille * 0.35
                toit3P ajoute cx + mLargeur * 0.55
                toit3P ajoute cy - mHauteur
                polygone(toile1, toit3P, #c0392b)
                rectangle(toile1, cx - mLargeur * 0.15, cy - mHauteur * 0.5, mLargeur * 0.3, mHauteur * 0.5, #5d4037)
            fin si
        fin pour
    fin pour

    // 3. Personnage principal
    pX est un nombre
    pY est un nombre
    pX vaut 200
    pY vaut 340

    ellipse(toile1, pX, pY + 12, 18, 6, #0a0f14)
    rectangle(toile1, pX - 10, pY - 20, 20, 28, #e74c3c)
    rectangle(toile1, pX - 6, pY - 16, 12, 18, #795548)
    cercle(toile1, pX, pY - 28, 10, #f1c40f)

    // Interface HUD
    contour(toile1, #0)
    rectangle(toile1, 0, 0, 400, 40, rgba(17, 17, 17, 0.2))
    label(toile1, 10, 16, "Vue 3ème Personne (Caméra Suiveuse)", #ffffff, 12)

    txtCoords est un texte
    txtCoords vaut "Position X : " + jX + " | Y : " + jY
    label(toile1, 10, 32, txtCoords, #f1c40f, 10)

    affiche toile1

    appuyer ["z", "s", "q", "d"] dans action

    nouvX est un nombre
    nouvY est un nombre
    nouvX vaut jX
    nouvY vaut jY

    si action = 1 alors
        nouvY vaut jY + 1
    sinon si action = 2 alors
        nouvY vaut jY - 1
    sinon si action = 3 alors
        nouvX vaut jX - 1
    sinon si action = 4 alors
        nouvX vaut jX + 1
    fin si

    // Détection de collision
    chkX est un nombre
    chkY est un nombre
    chkX vaut nouvX + 50000
    chkY vaut nouvY + 50000

    solFutur est un nombre
    solFutur vaut 0
    si chkX mod 14 = 0 ou chkY mod 14 = 0 alors
        solFutur vaut 1
    sinon si (chkX * 3 + chkY * 2) mod 31 < 2 alors
        solFutur vaut 2
    fin si

    decorFutur est un nombre
    decorFutur vaut 0
    si solFutur = 0 alors
        hFutur est un nombre
        hFutur vaut (chkX * 17 + chkY * 37) mod 100
        si hFutur = 15 alors
            decorFutur vaut 3
        fin si
    fin si

    si solFutur <> 2 et decorFutur <> 3 alors
        jX vaut nouvX
        jY vaut nouvY
    fin si
fin tant que
Sokoban en 3D isométrique : un projet mêlant algorithmie, génération procédurale et rendu graphique
Article

Sokoban en 3D isométrique

Sokoban en 3D isométrique : un projet mêlant algorithmie, génération procédurale et rendu graphique

Lire l'articleReplier l'article

Ce programme illustre comment l’algorithmie, la logique de jeu et le rendu visuel peuvent s’intégrer pour créer une expérience complète inspirée du célèbre Sokoban.
L’ensemble repose sur une architecture qui combine génération procédurale, gestion des interactions et affichage isométrique 3D.

Génération procédurale d’un niveau jouable

Le système construit automatiquement une salle fermée, y place des caisses, des cibles et un joueur, puis applique une série de mouvements aléatoires pour obtenir une configuration réaliste et jouable.

Points clés :

  • Création d’une grille 10×10 avec murs et zones libres.
  • Placement des caisses uniquement sur des cibles, jamais sur les bords.
  • Mélange contrôlé via des déplacements simulés.

Validation stricte du niveau :

  • bon nombre de caisses,
  • aucune caisse bloquée,
  • au moins une caisse hors cible pour garantir un vrai défi.

Extrait du programme :
« si caisseSurBord() alors valide vaut faux »
« si caissesHorsCible() = 0 alors valide vaut faux »

Chaque partie démarre ainsi avec un puzzle cohérent et intéressant.

Logique de déplacement et gestion des collisions
Le déplacement du joueur suit les règles classiques du Sokoban :

  • impossibilité de traverser les murs,
  • poussée des caisses uniquement si l’espace derrière est libre,
  • distinction entre caisse sur cible (*) et caisse hors cible (b).

Extrait du programme :
« si estCible(al, ac) alors poser(al, ac, "") sinon poser(al, ac, "b") »

Cette mécanique garantit une fidélité totale aux principes du jeu original.

Rendu isométrique 3D : immersion et lisibilité

La partie graphique repose sur un rendu isométrique détaillé :

  • sols en losange,
  • murs en volume,
  • caisses en 3D avec variations visuelles,
  • personnage stylisé avec ombre, tronc, tête et yeux.

Chaque élément est dessiné via des polygones, ce qui crée une ambiance visuelle cohérente et agréable.

Extrait du programme :
« polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c) »

Le résultat est une carte isométrique claire, immersive et parfaitement adaptée à un jeu de réflexion.

Une architecture complète de mini-jeu

Ce projet combine :

  • génération procédurale,
  • gestion des états,
  • interactions clavier,
  • rendu graphique avancé,
  • boucle de jeu complète,
  • détection de victoire.

Un exemple concret de la manière dont l’algorithmie peut servir la créativité, et comment un ensemble de fonctions peut devenir une expérience ludique cohérente.

Perspectives

Ce type de moteur ouvre la voie à :

  • la création automatique de puzzles,
  • l’ajout d’animations,
  • l’intégration dans un moteur plus large,
  • l’export vers le web ou le mobile,
  • l’expérimentation IA autour de la résolution de niveaux.

Programme :

// ============================================================
//  PARAMETRES
// ============================================================
lignes est un nombre
lignes vaut 10
colonnes est un nombre
colonnes vaut 10
nbCaisses est un nombre
nbCaisses vaut 5
melange est un nombre
melange vaut 60

// ============================================================
//  GRILLE
// ============================================================
niveau est un tableau
cibles est un tableau

jl est un nombre
jc est un nombre
jl vaut 0
jc vaut 0

fonction caseAt(li, co)
    ln est un texte
    ln vaut niveau[li]
    retourne ln[co]
fin fonction

fonction cibleAt(li, co)
    ln est un texte
    ln vaut cibles[li]
    retourne ln[co]
fin fonction

procedure poser(li, co, nc)
    ln est un texte
    ln vaut niveau[li]
    nouvelle est un texte
    nouvelle vaut ""
    k est un nombre
    k vaut 1
    tant que k <= longueur(ln)
        si k = co alors
            nouvelle vaut nouvelle + nc
        sinon
            nouvelle vaut nouvelle + ln[k]
        fin si
        k vaut k + 1
    fin tant que
    niveau[li] vaut nouvelle
fin procedure

procedure poserCible(li, co)
    ln est un texte
    ln vaut cibles[li]
    nouvelle est un texte
    nouvelle vaut ""
    k est un nombre
    k vaut 1
    tant que k <= longueur(ln)
        si k = co alors
            nouvelle vaut nouvelle + "c"
        sinon
            nouvelle vaut nouvelle + ln[k]
        fin si
        k vaut k + 1
    fin tant que
    cibles[li] vaut nouvelle
fin procedure

fonction estCible(li, co)
    si cibleAt(li, co) = "c" alors
        retourne vrai
    fin si
    retourne faux
fin fonction

fonction estLibre(li, co)
    ca est un texte
    ca vaut caseAt(li, co)
    si (ca = "#") ou (ca = "b") ou (ca = "*") alors
        retourne faux
    fin si
    retourne vrai
fin fonction

// ============================================================
//  GENERATION DU NIVEAU
// ============================================================
procedure construireSalle()
    vide niveau
    vide cibles
    li est un nombre
    li vaut 0
    tant que li < lignes
        ligneN est un texte
        ligneN vaut ""
        ligneC est un texte
        ligneC vaut ""
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            bord est un booleen
            bord vaut faux
            si (li = 0) ou (li = lignes - 1) alors
                bord vaut vrai
            fin si
            si (co = 1) ou (co = colonnes) alors
                bord vaut vrai
            fin si
            si bord alors
                ligneN vaut ligneN + "#"
            sinon
                ligneN vaut ligneN + "."
            fin si
            ligneC vaut ligneC + "."
            co vaut co + 1
        fin tant que
        niveau ajoute ligneN
        cibles ajoute ligneC
        li vaut li + 1
    fin tant que
fin procedure

procedure placerCaisses()
    poses est un nombre
    poses vaut 0
    essais est un nombre
    essais vaut 0
    tant que (poses < nbCaisses) et (essais < 800)
        essais vaut essais + 1
        rl est un nombre
        rl vaut hasard(2, lignes - 3)
        rc est un nombre
        rc vaut hasard(3, colonnes - 2)
        si caseAt(rl, rc) = "." alors
            appelle poser(rl, rc, "*")
            appelle poserCible(rl, rc)
            poses vaut poses + 1
        fin si
    fin tant que
fin procedure

procedure placerJoueur()
    ok est un booleen
    ok vaut faux
    essais est un nombre
    essais vaut 0
    tant que (non ok) et (essais < 800)
        essais vaut essais + 1
        rl est un nombre
        rl vaut hasard(1, lignes - 2)
        rc est un nombre
        rc vaut hasard(2, colonnes - 1)
        si caseAt(rl, rc) = "." alors
            appelle poser(rl, rc, "j")
            jl vaut rl
            jc vaut rc
            ok vaut vrai
        fin si
    fin tant que
fin procedure

procedure tirerUnCoup()
    dl est un nombre
    dc est un nombre
    choix est un nombre
    choix vaut hasard(1, 4)
    dl vaut 0
    dc vaut 0
    si choix = 1 alors
        dl vaut - 1
    sinon si choix = 2 alors
        dl vaut 1
    sinon si choix = 3 alors
        dc vaut - 1
    sinon
        dc vaut 1
    fin si

    reculL est un nombre
    reculL vaut jl + dl
    reculC est un nombre
    reculC vaut jc + dc
    caisseL est un nombre
    caisseL vaut jl - dl
    caisseC est un nombre
    caisseC vaut jc - dc

    si estLibre(reculL, reculC) alors
        derriere est un texte
        derriere vaut caseAt(caisseL, caisseC)
        tire est un booleen
        tire vaut faux
        si (derriere = "b") ou (derriere = "*") alors
            surBord est un booleen
            surBord vaut faux
            si (jl = 1) ou (jl = lignes - 2) alors
                surBord vaut vrai
            fin si
            si (jc = 2) ou (jc = colonnes - 1) alors
                surBord vaut vrai
            fin si
            si (non surBord) et (hasard(0, 1) = 1) alors
                tire vaut vrai
            fin si
        fin si

        si estCible(jl, jc) alors
            appelle poser(jl, jc, "c")
        sinon
            appelle poser(jl, jc, ".")
        fin si

        si tire alors
            si estCible(caisseL, caisseC) alors
                appelle poser(caisseL, caisseC, "c")
            sinon
                appelle poser(caisseL, caisseC, ".")
            fin si
            si estCible(jl, jc) alors
                appelle poser(jl, jc, "*")
            sinon
                appelle poser(jl, jc, "b")
            fin si
        fin si

        appelle poser(reculL, reculC, "j")
        jl vaut reculL
        jc vaut reculC
    fin si
fin procedure

fonction caisseSurBord()
    li est un nombre
    li vaut 1
    tant que li < lignes - 1
        co est un nombre
        co vaut 2
        tant que co < colonnes
            car est un texte
            car vaut caseAt(li, co)
            si (car = "b") ou (car = "*") alors
                si (li = 1) ou (li = lignes - 2) ou (co = 2) ou (co = colonnes - 1) alors
                    retourne vrai
                fin si
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne faux
fin fonction

fonction compteCaisses()
    n est un nombre
    n vaut 0
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            car est un texte
            car vaut caseAt(li, co)
            si (car = "b") ou (car = "*") alors
                n vaut n + 1
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne n
fin fonction

fonction caissesHorsCible()
    n est un nombre
    n vaut 0
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            si caseAt(li, co) = "b" alors
                n vaut n + 1
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne n
fin fonction

procedure genererNiveau()
    valide est un booleen
    valide vaut faux
    tentatives est un nombre
    tentatives vaut 0

    tant que (non valide) et (tentatives < 60)
        tentatives vaut tentatives + 1
        appelle construireSalle()
        appelle placerCaisses()
        appelle placerJoueur()
        n est un nombre
        n vaut 0
        tant que n < melange
            appelle tirerUnCoup()
            n vaut n + 1
        fin tant que

        valide vaut vrai
        si compteCaisses() <> nbCaisses alors
            valide vaut faux
        fin si
        si caisseSurBord() alors
            valide vaut faux
        fin si
        si caissesHorsCible() = 0 alors
            valide vaut faux
        fin si
    fin tant que
fin procedure

// ============================================================
//  DESSIN ISOMETRIQUE 3D
// ============================================================
tw est un nombre
tw vaut 56
th est un nombre
th vaut 28

g est une toile
dimension(g, 650, 400)

procedure dessiner()
    dégradé(g, [#1e2a38, #2c3e50])

    ox est un nombre
    ox vaut 300
    oy est un nombre
    oy vaut 45  // <-- Ajusté pour centrer parfaitement la map 10x10 en hauteur

    somm est un nombre
    somm vaut 1
    tant que somm <= lignes + colonnes
        li est un nombre
        li vaut 0
        tant que li < lignes
            co est un nombre
            co vaut 1
            tant que co <= colonnes
                si (li + co) = somm alors
                    car est un texte
                    car vaut caseAt(li, co)

                    cx est un nombre
                    cx vaut ox + (co - li) * (tw / 2)
                    cy est un nombre
                    cy vaut oy + (co + li) * (th / 2)

                    // 1. Sol
                    si car <> "#" alors
                        polygone(g, cx, cy - th/2, cx + tw/2, cy, cx, cy + th/2, cx - tw/2, cy, #3a4a5c)
                        ligne(g, cx, cy - th/2, cx + tw/2, cy, #2c3e50, 1)
                        ligne(g, cx + tw/2, cy, cx, cy + th/2, #2c3e50, 1)
                        ligne(g, cx, cy + th/2, cx - tw/2, cy, #2c3e50, 1)
                        ligne(g, cx - tw/2, cy, cx, cy - th/2, #2c3e50, 1)

                        // Cible : caisse 3D rouge transparente
                        si estCible(li, co) alors
                            si car <> "*" alors
                                hcib est un nombre
                                hcib vaut 24
                                polygone(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.35))
                                polygone(g, cx - tw/2, cy - hcib, cx, cy + th/2 - hcib, cx, cy + th/2, cx - tw/2, cy, rgba(192, 57, 43, 0.45))
                                polygone(g, cx, cy + th/2 - hcib, cx + tw/2, cy - hcib, cx + tw/2, cy, cx, cy + th/2, rgba(146, 43, 33, 0.45))

                                ligne(g, cx, cy - th/2 - hcib, cx + tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx + tw/2, cy - hcib, cx, cy + th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx, cy + th/2 - hcib, cx - tw/2, cy - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx - tw/2, cy - hcib, cx, cy - th/2 - hcib, rgba(231, 76, 60, 0.8), 1)
                                ligne(g, cx, cy + th/2 - hcib, cx, cy + th/2, rgba(231, 76, 60, 0.8), 1)
                            fin si
                        fin si
                    fin si

                    // 2. Mur
                    si car = "#" alors
                        h est un nombre
                        h vaut 28
                        polygone(g, cx, cy - th/2 - h, cx + tw/2, cy - h, cx, cy + th/2 - h, cx - tw/2, cy - h, #243342)
                        polygone(g, cx - tw/2, cy - h, cx, cy + th/2 - h, cx, cy + th/2, cx - tw/2, cy, #141c26)
                        polygone(g, cx, cy + th/2 - h, cx + tw/2, cy - h, cx + tw/2, cy, cx, cy + th/2, #1a2531)
                        ligne(g, cx, cy + th/2 - h, cx, cy + th/2, #101820, 1)
                        ligne(g, cx, cy + th/2 - h, cx - tw/2, cy - h, #101820, 1)
                        ligne(g, cx, cy + th/2 - h, cx + tw/2, cy - h, #101820, 1)
                    fin si

                    // 3. Caisse
                    si (car = "b") ou (car = "*") alors
                        cTop est un texte
                        cTop vaut #f5b041
                        cLeft est un texte
                        cLeft vaut #d68910
                        cRight est un texte
                        cRight vaut #b9770e

                        si car = "*" alors
                            cTop vaut #2ecc71
                            cLeft vaut #27ae60
                            cRight vaut #1e8449
                        fin si

                        hc est un nombre
                        hc vaut 24
                        polygone(g, cx, cy - th/2 - hc, cx + tw/2, cy - hc, cx, cy + th/2 - hc, cx - tw/2, cy - hc, cTop)
                        polygone(g, cx - tw/2, cy - hc, cx, cy + th/2 - hc, cx, cy + th/2, cx - tw/2, cy, cLeft)
                        polygone(g, cx, cy + th/2 - hc, cx + tw/2, cy - hc, cx + tw/2, cy, cx, cy + th/2, cRight)
                        ligne(g, cx, cy + th/2 - hc, cx, cy + th/2, #875c06, 1)
                    fin si

                    // 4. Joueur (Personnage amélioré)
                    si car = "j" alors
                        // Ombre au sol
                        ellipse(g, cx, cy, 18, 9, rgba(0, 0, 0, 0.4))

                        // Dimensions du corps
                        pw est un nombre
                        pw vaut 20
                        ph est un nombre
                        ph vaut 10
                        pj est un nombre
                        pj vaut 18 // hauteur du tronc

                        // Tronc (Pull rouge)
                        polygone(g, cx, cy - ph/2 - pj, cx + pw/2, cy - pj, cx, cy + ph/2 - pj, cx - pw/2, cy - pj, #e74c3c)
                        polygone(g, cx - pw/2, cy - pj, cx, cy + ph/2 - pj, cx, cy + ph/2, cx - pw/2, cy, #c0392b)
                        polygone(g, cx, cy + ph/2 - pj, cx + pw/2, cy - pj, cx + pw/2, cy, cx, cy + ph/2, #922b21)

                        // Tête (Cercle couleur peau)
                        cTop est un nombre
                        cTop vaut cy - pj - 10
                        cercle(g, cx, cTop, 11, #f5cba7)

                        // Yeux
                        cercle(g, cx - 4, cTop - 2, 2, #2c3e50)
                        cercle(g, cx + 4, cTop - 2, 2, #2c3e50)
                    fin si

                fin si
                co vaut co + 1
            fin tant que
            li vaut li + 1
        fin tant que
        somm vaut somm + 1
    fin tant que

    affiche g
fin procedure

// ============================================================
//  DEPLACEMENT
// ============================================================
procedure deplacer(dl, dc)
    nl est un nombre
    nl vaut jl + dl
    nc est un nombre
    nc vaut jc + dc
    dest est un texte
    dest vaut caseAt(nl, nc)
    bloque est un booleen
    bloque vaut faux

    si dest = "#" alors
        bloque vaut vrai
    fin si

    si (non bloque) et ((dest = "b") ou (dest = "*")) alors
        al est un nombre
        al vaut nl + dl
        ac est un nombre
        ac vaut nc + dc
        apres est un texte
        apres vaut caseAt(al, ac)
        si (apres = "#") ou (apres = "b") ou (apres = "*") alors
            bloque vaut vrai
        sinon
            si estCible(al, ac) alors
                appelle poser(al, ac, "*")
            sinon
                appelle poser(al, ac, "b")
            fin si
            si estCible(nl, nc) alors
                appelle poser(nl, nc, "c")
            sinon
                appelle poser(nl, nc, ".")
            fin si
        fin si
    fin si

    si non bloque alors
        si estCible(jl, jc) alors
            appelle poser(jl, jc, "c")
        sinon
            appelle poser(jl, jc, ".")
        fin si
        appelle poser(nl, nc, "j")
        jl vaut nl
        jc vaut nc
    fin si
fin procedure

fonction gagne()
    li est un nombre
    li vaut 0
    tant que li < lignes
        co est un nombre
        co vaut 1
        tant que co <= colonnes
            si caseAt(li, co) = "b" alors
                retourne faux
            fin si
            co vaut co + 1
        fin tant que
        li vaut li + 1
    fin tant que
    retourne vrai
fin fonction

// ============================================================
//  PARTIE
// ============================================================
appelle genererNiveau()

fini est un booleen
fini vaut faux
action est un nombre
action vaut 0
coups est un nombre
coups vaut 0

tant que non fini
    appelle dessiner()

    // Attente de la frappe d'une touche au clavier (z, s, q, d, r, x)
    appuyer ["z", "s", "q", "d", "r", "x"] dans action

    // action = 1 (z), 2 (s), 3 (q), 4 (d), 5 (r), 6 (x)
    si action = 1 alors
        appelle deplacer(-1, 0)
        coups vaut coups + 1
    sinon si action = 2 alors
        appelle deplacer(1, 0)
        coups vaut coups + 1
    sinon si action = 3 alors
        appelle deplacer(0, -1)
        coups vaut coups + 1
    sinon si action = 4 alors
        appelle deplacer(0, 1)
        coups vaut coups + 1
    sinon si action = 5 alors
        appelle genererNiveau()
        coups vaut 0
    sinon si action = 6 alors
        fini vaut vrai
    fin si

    si non fini alors
        si gagne() alors
            fini vaut vrai
        fin si
    fin si
fin tant que

effacer(g)
appelle dessiner()
si gagne() alors
    couleur(#2ecc71, "GAGNE en " + convertir_texte(coups) + " coups ! Bravo !")
sinon
    couleur(#e74c3c, "Partie terminee. A bientot !")
fin si
Construire une île procédurale avec du bruit fractal
Article

Construire une île

Construire une île procédurale avec du bruit fractal

Lire l'articleReplier l'article

La génération procédurale est un formidable terrain d’apprentissage : elle permet de créer des mondes crédibles, variés et naturels… à partir de simples formules mathématiques.
Dans cet atelier, j’ai développé un générateur d’île procédurale destiné à comprendre les bases du bruit, du relief et des biomes.

L’objectif : montrer comment un paysage complet peut émerger d’un hachage pseudo-aléatoire et de quelques interpolations bien choisies.

Paramètres du monde

L’île est rendue sur une grille de 100 × 100 blocs, chaque bloc étant un “gros pixel” de 4×4.
Quelques constantes définissent le comportement global :

  • GRAINE : change la forme de l’île
  • ECHELLE : contrôle la taille des collines
  • BLOC : résolution du rendu

Modifier une seule valeur suffit à obtenir une nouvelle île.

Comprendre le bruit procédural

Hachage pseudo-aléatoire

La fonction hache(x, y) génère un nombre pseudo-aléatoire stable pour chaque coordonnée.
Cela garantit que le terrain ne scintille pas : une même position produit toujours la même valeur.

Fraction et interpolation

EduCode ne disposant pas de frac(), elle est recréée pour isoler la partie décimale.
Ensuite, une interpolation douce (courbe en S) permet d’obtenir des transitions naturelles entre les valeurs des coins.

Bruit multi-octaves

Le relief final est obtenu en superposant plusieurs couches :

  • grandes collines
  • détails intermédiaires
  • micro-variations

Chaque octave ajoute de la richesse au paysage, comme en modélisation 3D.

Génération de l’île

Une île n’est pas qu’un relief : c’est aussi une forme.
Pour cela, un masque radial est appliqué :

  • au centre : hauteur maximale
  • vers les bords : la hauteur diminue progressivement
  • l’exposant accentue la pente des côtes

Ce simple calcul transforme un bruit brut en une île entourée d’océan.

Biomes : donner vie au paysage
Selon la hauteur finale, chaque bloc reçoit une couleur correspondant à un biome :

  • Océan profond
  • Mer
  • Plage
  • Herbe claire
  • Forêt
  • Roche / sommet

En quelques lignes, le terrain devient lisible, naturel et immersif.

Résultat : une île unique, stable et pédagogique
Ce générateur montre que :

  • un paysage peut être créé sans aucune image, uniquement via des fonctions mathématiques
  • la génération procédurale est un excellent outil pour comprendre le bruit, les interpolations et les biomes
  • chaque île est différente, mais toujours cohérente
  • l’approche est idéale pour des ateliers, des cours ou des projets créatifs

Conclusion

Il est possible de créer une île complète, réaliste et variée à partir de quelques fonctions de bruit et d’un masque radial.
La génération procédurale est un formidable levier pédagogique : elle permet de comprendre la logique derrière les mondes ouverts, les jeux vidéo, les simulations et les outils créatifs.

Programme :

@ ATELIER PEDAGOGIQUE : Generateur d'ile procedural

// ==========================================
// 1. REGLAGES
// ==========================================
constante LARGEUR vaut 400
constante HAUTEUR vaut 400
constante BLOC vaut 4 // gros pixel (4 => grille 100x100)
constante GRAINE vaut 7 // change ce nombre = nouvelle ile
constante ECHELLE vaut 55 // taille des collines (+ grand = + large)

_toile est une toile
dimension(_toile, LARGEUR, HAUTEUR)

// ==========================================
// 2. OUTILS DE BRUIT
// ==========================================
// Partie decimale d'un nombre (EduCode n'a pas de "frac").
fonction frac(v)
    retourne v - arrondi_inferieur(v)
fin fonction

// Hachage pseudo-aleatoire : a partir de (x, y) renvoie un nombre
// "au hasard" mais TOUJOURS LE MEME pour les memes coordonnees.
// C'est ce qui rend le terrain stable (pas de scintillement).
fonction hache(x, y)
    n est un nombre
    n vaut sinus(x * 127.1 + y * 311.7 + GRAINE * 13.13) * 43758.5453
    retourne frac(absolue(n))
fin fonction

// Bruit LISSE : interpole entre les valeurs de hachage des 4 coins
// de la case -> transitions douces au lieu de sauts brusques.
fonction bruit(x, y)
    x0 est un nombre
    x0 vaut arrondi_inferieur(x)
    y0 est un nombre
    y0 vaut arrondi_inferieur(y)
    fx est un nombre
    fx vaut frac(x)
    fy est un nombre
    fy vaut frac(y)

    // Adoucissement (courbe en S) pour des collines rondes
    ux est un nombre
    ux vaut fx * fx * (3 - 2 * fx)
    uy est un nombre
    uy vaut fy * fy * (3 - 2 * fy)

    // Valeurs des 4 coins
    x est un nombre
    x vaut hache(x0, y0)
    b est un nombre
    b vaut hache(x0 + 1, y0)
    c est un nombre
    c vaut hache(x0, y0 + 1)
    d est un nombre
    d vaut hache(x0 + 1, y0 + 1)

    // Interpolation horizontale puis verticale
    ab est un nombre
    ab vaut x + (b - x) * ux
    cd est un nombre
    cd vaut c + (d - c) * ux
    retourne ab + (cd - ab) * uy
fin fonction

// Bruit multi-octaves : on superpose plusieurs echelles de bruit
// (grosses collines + details fins) -> cotes decoupees, plus naturel.
fonction relief(x, y)
    total est un nombre
    total vaut 0
    total vaut total + bruit(x / ECHELLE, y / ECHELLE) * 0.55
    total vaut total + bruit(x / (ECHELLE / 2), y / (ECHELLE / 2)) * 0.30
    total vaut total + bruit(x / (ECHELLE / 4), y / (ECHELLE / 4)) * 0.15
    retourne total
fin fonction

// ==========================================
// 3. DESSIN DE L'ILE
// ==========================================
cx est un nombre
cx vaut LARGEUR / 2
cy est un nombre
cy vaut HAUTEUR / 2
rayonMax est un nombre
rayonMax vaut LARGEUR / 2

py est un nombre
py vaut 0
tant que py < HAUTEUR
    px est un nombre
    px vaut 0
    tant que px < LARGEUR

        // Hauteur brute donnee par le bruit
        h est un nombre
        h vaut relief(px, py)

        // Masque radial : distance au centre, normalisee entre 0 et 1
        dist est un nombre
        dist vaut racine(puissance(px - cx, 2) + puissance(py - cy, 2)) / rayonMax

        // On abaisse la hauteur vers les bords (dist grand => forte baisse)
        // L'exposant accentue la cote pres du bord.
        h vaut h - puissance(dist, 2) * 1.15

        // --- Choix du biome selon la hauteur finale ---
        _couleur est un texte
        si h < -0.05 alors
            _couleur vaut #2C6BA8 // ocean profond
        sinon si h < 0.08 alors
            _couleur vaut #3E86C4 // ocean
        sinon si h < 0.14 alors
            _couleur vaut #E4D9A8 // plage (sable)
        sinon si h < 0.30 alors
            _couleur vaut #6FA84A // herbe claire
        sinon si h < 0.48 alors
            _couleur vaut #3F7A34 // foret
        sinon
            _couleur vaut #7C8B84 // roche / sommet
        fin si

        rectangle(_toile, px, py, BLOC, BLOC, _couleur)

        px vaut px + BLOC
    fin tant que
    py vaut py + BLOC
fin tant que

// ==========================================
// 4. LEGENDE
// ==========================================
label(_toile, 14, 24, "île procédural", #FFFFFF, 13)
affiche _toile
Construire un moteur 3D… en salle de classe : quand l’apprentissage du code devient une aventure créative
Article

Construire un moteur 3D

Construire un moteur 3D… en salle de classe : quand l’apprentissage du code devient une aventure créative

Lire l'articleReplier l'article

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
Construire un monde isométrique infini grâce à la génération procédurale
Article

Construire un monde isométrique infini

Construire un monde isométrique infini grâce à la génération procédurale

Lire l'articleReplier l'article

Développer un moteur capable de créer un univers isométrique infini, cohérent et entièrement dynamique représente un défi passionnant. Ce projet explore une approche où chaque tuile, chaque décor et chaque élément du terrain est généré en temps réel, sans aucune carte préchargée ni tableau de données.
Le monde n’existe que lorsqu’il est rendu à l’écran.

Architecture du moteur

Un espace infini basé sur des coordonnées
Le joueur évolue dans un monde théoriquement illimité.
Chaque tuile est calculée à partir de sa position (gx, gy) et projetée en isométrie via une transformation simple :

  • (rX - rY) * 24 pour l’axe horizontal
  • (rX + rY) * 12 pour l’axe vertical

Cette méthode garantit un rendu fluide, même lors de déplacements rapides.

Génération procédurale du sol

Le terrain repose sur des règles mathématiques :

  • Herbe par défaut
  • Chemins lorsque nx mod 14 = 0 ou ny mod 14 = 0
  • Eau via une formule organique : (nx 3 + ny 2) mod 31 < 2

Ces règles créent un monde structuré, avec des routes régulières et des rivières naturelles.

Rendu isométrique avec relief

Chaque tuile est composée de :

  • une tranche 3D pour le volume
  • un losange supérieur pour la surface

Le résultat : un style isométrique propre, lisible et immersif.

Décors procéduraux

Un hash mathématique détermine la présence d’éléments naturels ou architecturaux :

  • arbres
  • rochers
  • maisons

Chaque décor est dessiné via des polygones et formes géométriques, ce qui donne une identité visuelle unique.

Déplacements et collisions

Les déplacements (ZQSD) sont validés par une vérification procédurale :

  • type de sol futur
  • type de décor futur

Impossible d’entrer dans l’eau ou dans une maison : la logique reste cohérente partout dans le monde, sans tableau de collision.

Intérêt du projet

Monde infini sans stockage

  • Performances optimisées : seules les tuiles visibles sont calculées
  • Simplicité d’extension : nouveaux biomes, décors, règles
  • Approche pédagogique pour comprendre la génération procédurale

Conclusion

Ce projet démontre qu’un monde infini, cohérent et vivant peut émerger uniquement de règles mathématiques. La génération procédurale ouvre des perspectives fascinantes pour la création d’univers interactifs.

Programme :

@ Carte Isométrique Infinie et Dynamique avec Génération Procédurale

toile1 est une toile
dimension(toile1, 500, 450)

// Position du joueur dans le monde infini
jX est un nombre
jY est un nombre
jX vaut 0
jY vaut 0

action est un nombre

@ Boucle principale
tant que vrai
    effacer(toile1)
    remplir(toile1, #111923)

    // 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
            gx est un nombre
            gy est un nombre
            gx vaut jX + rX
            gy vaut jY + rY

            // Coordonnées écran relatives au centre (200, 200)
            cx est un nombre
            cy est un nombre
            cx vaut 200 + (rX - rY) * 24
            cy vaut 200 + (rX + rY) * 12

            // Culling : Rendu uniquement si la tuile touche la toile 400x400
            si cx >= - 60 et cx <= 460 et cy >= - 60 et cy <= 460 alors

                // Offsets positifs pour le calcul procédural
                nx est un nombre
                ny est un nombre
                nx vaut gx + 50000
                ny vaut gy + 50000

                // 1. Génération procédurale du Sol (sans tableau)
                typeSol est un nombre
                typeSol vaut 0 // 0 = Herbe

                // Réseau de routes infini
                si nx mod 14 = 0 ou ny mod 14 = 0 alors
                    typeSol vaut 1 // Chemin
                    // Rivières organiques procédurales
                sinon si (nx * 3 + ny * 2) mod 31 < 2 alors
                    typeSol vaut 2 // Eau
                fin si

                // Tranche 3D (épais/relief du sol)
                ptsBord est un tableau
                ptsBord ajoute cx - 24
                ptsBord ajoute cy
                ptsBord ajoute cx
                ptsBord ajoute cy + 12
                ptsBord ajoute cx + 24
                ptsBord ajoute cy
                ptsBord ajoute cx + 24
                ptsBord ajoute cy + 4
                ptsBord ajoute cx
                ptsBord ajoute cy + 16
                ptsBord ajoute cx - 24
                ptsBord ajoute cy + 4
                polygone(toile1, ptsBord, #16222d)

                // Face supérieure de la tuile (Losange 48x24)
                ptsSol est un tableau
                ptsSol ajoute cx
                ptsSol ajoute cy - 12
                ptsSol ajoute cx + 24
                ptsSol ajoute cy
                ptsSol ajoute cx
                ptsSol ajoute cy + 12
                ptsSol ajoute cx - 24
                ptsSol ajoute cy

                si typeSol = 0 alors
                    polygone(toile1, ptsSol, #27ae60)
                sinon si typeSol = 1 alors
                    polygone(toile1, ptsSol, #d35400)
                sinon
                    polygone(toile1, ptsSol, #2980b9)
                fin si

                contour(toile1, #1a252f, 1)

                // 2. Génération procédurale du Décor
                typeDecor est un nombre
                typeDecor vaut 0

                si typeSol = 0 alors
                    hash est un nombre
                    hash vaut (nx * 17 + ny * 37) mod 100
                    si hash < 10 alors
                        typeDecor vaut 1 // Arbre
                    sinon si hash < 14 alors
                        typeDecor vaut 2 // Rocher
                    sinon si hash = 15 alors
                        typeDecor vaut 3 // Maison
                    fin si
                fin si

                // Dessin des décors
                si typeDecor = 1 alors
                    rectangle(toile1, cx - 2, cy - 18, 4, 12, #5d4037)
                    cercle(toile1, cx, cy - 22, 10, #2e7d32)
                    cercle(toile1, cx - 3, cy - 25, 6, #388e3c)
                sinon si typeDecor = 2 alors
                    cercle(toile1, cx, cy - 4, 7, #7f8c8d)
                    cercle(toile1, cx - 2, cy - 6, 4, #95a5a6)
                sinon si typeDecor = 3 alors
                    fGauche est un tableau
                    fGauche ajoute cx - 12
                    fGauche ajoute cy
                    fGauche ajoute cx
                    fGauche ajoute cy + 6
                    fGauche ajoute cx
                    fGauche ajoute cy - 18
                    fGauche ajoute cx - 12
                    fGauche ajoute cy - 24
                    polygone(toile1, fGauche, #bdc3c7)

                    fDroite est un tableau
                    fDroite ajoute cx
                    fDroite ajoute cy + 6
                    fDroite ajoute cx + 12
                    fDroite ajoute cy
                    fDroite ajoute cx + 12
                    fDroite ajoute cy - 24
                    fDroite ajoute cx
                    fDroite ajoute cy - 18
                    polygone(toile1, fDroite, #95a5a6)

                    toit est un tableau
                    toit ajoute cx - 12
                    toit ajoute cy - 24
                    toit ajoute cx
                    toit ajoute cy - 32
                    toit ajoute cx + 12
                    toit ajoute cy - 24
                    toit ajoute cx
                    toit ajoute cy - 18
                    polygone(toile1, toit, #c0392b)

                    rectangle(toile1, cx - 3, cy - 2, 5, 7, #5d4037)
                fin si

                // 3. Personnage principal (centré)
                si rX = 0 et rY = 0 alors
                    ellipse(toile1, cx, cy + 3, 9, 4, #0a0f14)
                    rectangle(toile1, cx - 3, cy - 14, 6, 12, #e74c3c)
                    cercle(toile1, cx, cy - 18, 5, #f1c40f)
                fin si

            fin si
        fin pour
    fin pour

    // Interface HUD & Coordonnées GPS
    contour(toile1, #0)
    rectangle(toile1, 0, 0, 400, 45, rgba(17, 17, 17, 0.2))
    label(toile1, 10, 18, "Monde Isométrique Infini", #ffffff, 13)

    txtCoords est un texte
    txtCoords vaut "Position X : " + jX + " | Y : " + jY
    label(toile1, 10, 35, txtCoords, #f1c40f, 11)

    affiche toile1

    appuyer ["z", "s", "q", "d"] dans action

    nouvX est un nombre
    nouvY est un nombre
    nouvX vaut jX
    nouvY vaut jY

    si action = 1 alors
        nouvY vaut jY - 1
    sinon si action = 2 alors
        nouvY vaut jY + 1
    sinon si action = 3 alors
        nouvX vaut jX - 1
    sinon si action = 4 alors
        nouvX vaut jX + 1
    fin si

    // Verification des collisions procédurales à la volée
    chkX est un nombre
    chkY est un nombre
    chkX vaut nouvX + 50000
    chkY vaut nouvY + 50000

    solFutur est un nombre
    solFutur vaut 0
    si chkX mod 14 = 0 ou chkY mod 14 = 0 alors
        solFutur vaut 1
    sinon si (chkX * 3 + chkY * 2) mod 31 < 2 alors
        solFutur vaut 2
    fin si

    decorFutur est un nombre
    decorFutur vaut 0
    si solFutur = 0 alors
        hFutur est un nombre
        hFutur vaut (chkX * 17 + chkY * 37) mod 100
        si hFutur = 15 alors
            decorFutur vaut 3
        fin si
    fin si

    // Collision avec l'eau et les bâtiments
    si solFutur <> 2 et decorFutur <> 3 alors
        jX vaut nouvX
        jY vaut nouvY
    fin si

fin tant que