OCaml + SDL : coder une balle rebondissante
Associer OCaml et SDL est un excellent exercice pour découvrir la programmation graphique dans un langage fonctionnel. On y touche à tout : gestion d’une fenêtre, boucle d’affichage, événements clavier et un soupçon de physique. Dans ce tutoriel, nous allons construire pas à pas une petite balle qui rebondit sur les bords de la fenêtre. Le résultat est classique, mais chaque brique se réutilise dans des projets bien plus ambitieux.
Pourquoi OCaml et SDL ?
SDL (Simple DirectMedia Layer) est une bibliothèque C bas niveau qui donne accès à la fenêtre, au rendu 2D, au son et aux périphériques d’entrée. C’est le socle de nombreux jeux indépendants. Côté OCaml, le binding historique s’appelle OCamlSDL : il expose l’API de SDL sous une forme adaptée au langage, avec le typage fort et le filtrage par motifs qui font la force d’OCaml.
L’intérêt pédagogique est double. On garde la simplicité de SDL — quelques appels suffisent pour dessiner à l’écran — tout en profitant de la rigueur d’OCaml : une boucle de rendu s’écrit naturellement comme une fonction récursive prenant l’état courant en paramètre.
Installer OCamlSDL
Le plus simple est de passer par opam, le gestionnaire de paquets d’OCaml. Sur une machine où opam est déjà configuré :
opam install ocamlsdl
Cette commande installe le binding ainsi que les dépendances système SDL 1.2 si votre distribution les fournit. Sous Debian ou Ubuntu, il peut être nécessaire d’installer d’abord les en-têtes :
sudo apt install libsdl1.2-dev libsdl-gfx1.2-dev
opam install ocamlsdl
Pour compiler, on lie le programme au module sdl. Avec ocamlfind, cela donne une ligne du type :
ocamlfind ocamlopt -package sdl -linkpkg balle.ml -o balle
Ouvrir une fenêtre
Toute application SDL commence par initialiser la bibliothèque, puis par créer une surface d’affichage. En OCaml, cela tient en quelques lignes.
let () =
Sdl.init [`VIDEO];
let largeur = 640 and hauteur = 480 in
let ecran =
Sdlvideo.set_video_mode
~w:largeur ~h:hauteur ~bpp:32 [`DOUBLEBUF]
in
(* ... boucle de rendu ici ... *)
Sdl.quit ()
Sdl.init réveille le sous-système vidéo. set_video_mode demande une fenêtre de 640×480 pixels en 32 bits par pixel. Le drapeau DOUBLEBUF active le double buffering : on dessine dans un tampon caché, puis on l’échange d’un coup avec l’écran visible. C’est ce qui évite le scintillement d’une animation.
Représenter l’état de la balle
En programmation fonctionnelle, on évite les variables mutables globales. On modélise plutôt l’état de la balle comme une valeur immuable qu’on transforme à chaque image. Un enregistrement fait parfaitement l’affaire.
type balle = {
x : float; (* position horizontale *)
y : float; (* position verticale *)
vx : float; (* vitesse horizontale *)
vy : float; (* vitesse verticale *)
}
let rayon = 15.
Les positions et vitesses sont en flottants : cela évite les erreurs d’arrondi qui rendraient le mouvement saccadé. On arrondira seulement au moment de dessiner.
La physique du rebond
La mise à jour est étonnamment simple. À chaque image, on déplace la balle selon sa vitesse, puis on inverse la composante de vitesse concernée si la balle sort d’un bord.
let maj_balle b largeur hauteur =
let nx = b.x +. b.vx and ny = b.y +. b.vy in
let vx =
if nx -. rayon < 0. || nx +. rayon > float largeur
then -. b.vx else b.vx
in
let vy =
if ny -. rayon < 0. || ny +. rayon > float hauteur
then -. b.vy else b.vy
in
{ x = b.x +. vx; y = b.y +. vy; vx; vy }
Le principe : quand la balle touche un mur vertical, on inverse vx (elle repart dans l’autre sens horizontalement) ; quand elle touche le sol ou le plafond, on inverse vy. On recalcule ensuite la position à partir de la vitesse corrigée, ce qui évite que la balle ne reste « collée » au bord. Pour simuler la gravité, il suffirait d’ajouter une constante à vy à chaque image.
Dessiner et échanger les tampons
Reste à afficher la balle. On efface l’écran en le remplissant de noir, on dessine un disque à la position courante, puis on bascule le double buffer.
let dessine ecran b =
let noir = Sdlvideo.map_RGB ecran (0, 0, 0) in
Sdlvideo.fill_rect ecran noir;
Sdlgfx.filledCircleRGBA ecran
(Int32.of_int (int_of_float b.x))
(Int32.of_int (int_of_float b.y))
(Int32.of_int (int_of_float rayon))
255 120 0 255; (* balle orange *)
Sdlvideo.flip ecran
fill_rect peint le fond, filledCircleRGBA (fourni par SDL_gfx) trace un disque plein, et flip échange les tampons pour afficher le résultat.
La boucle de rendu
Le cœur du programme est une fonction récursive qui, à chaque tour, lit les événements, met à jour l’état, dessine, puis se rappelle avec le nouvel état.
let rec boucle ecran b largeur hauteur =
match Sdlevent.poll () with
| Some (Sdlevent.QUIT)
| Some (Sdlevent.KEYDOWN { Sdlevent.keysym = Sdlkey.KEY_ESCAPE; _ }) ->
() (* on quitte *)
| _ ->
dessine ecran b;
Sdltimer.delay 16; (* ~60 images par seconde *)
let b' = maj_balle b largeur hauteur in
boucle ecran b' largeur hauteur
Cette structure est très idiomatique en OCaml : l’état passe explicitement d’un appel au suivant, sans mutation cachée. Le delay 16 plafonne l’animation autour de 60 images par seconde. Un appui sur Échap ou la fermeture de la fenêtre termine proprement la récursion.
Il ne reste qu’à initialiser une balle et à lancer la boucle depuis notre main :
let depart = { x = 320.; y = 240.; vx = 4.; vy = 3. } in
boucle ecran depart largeur hauteur;
Pour aller plus loin
Vous disposez maintenant d’un squelette complet : fenêtre, boucle, physique et rendu. Les prolongements naturels sont nombreux — plusieurs balles gérées dans une liste, gravité et frottement, collisions entre objets, ou passage à SDL 2 pour un rendu accéléré. Chacun se greffe sur la même architecture fonctionnelle.
À lire ensuite
- Consolidez vos bases avec débuter avec Haskell sans prise de tête, un autre langage fonctionnel.
- Découvrez l’approche globale dans notre guide de la programmation fonctionnelle.
- Envie de 3D ? Passez à votre premier triangle en OpenGL.