J’aime les challenges. Un soir, je m’étais demandé jusqu’où je pouvais pousser le CSS avec ses différentes propriétés et sélecteurs en 2025. C’est alors qu’il m’est venu l’idée de créer un POC (Proof of concept) sur la reproduction du jeu original Pokémon Version Rouge / Bleu sorti en 1999 en Europe avec uniquement du HTML et du CSS. Jusqu’où peut-on aller ?
Les mécaniques de jeu
Sur le jeu Pokémon, il y a deux mécanique de jeu principaux :
- L’exploration dans l’overworld
- Les phases de combat
Je me suis principalement focalisé sur la première mécanique. Vient alors la première question : comment déplacer son personnage ? La première chose qui viendrait à l’esprit serait qu’à l’appuie sur une touche sur le clavier, le personnage réalise une action de mouvement mais comment le faire via du CSS ? Et bien on ne peut pas. Il faut donc trouver une alternative.
Tout d’abord, il faut savoir que l’écran de l’overworld est décomposé en cases (tiles) qui font 16 pixels de largeur par 16 pixels de hauteur. Les tiles sont au nombre de 10 verticalement et de 9 horizontalement. L’idée serait donc qu’au clic sur une case adjacente au personnage, on ait une action de déplacement. Par exemple, si on clique sur la case placé au dessus de la case où se trouve le personnage, alors une action de déplacement s’effectue.
Comment réaliser cela techniquement ? L’approche qui peut être intéressant est d’utiliser l’élément <input type="radio" /> pour avoir un état de position. En effet, un input radio peut être soit coché (:checked) soit non coché. C’est exactement ce qu’il nous faut : un état binaire pour chaque case de la grille.


La magie des radio inputs
La beauté de cette approche réside dans le fait qu’un groupe de radio inputs ne peut avoir qu’un seul élément coché à la fois. Si tous nos inputs partagent le même attribut name, alors un seul peut être sélectionné simultanément. C’est parfait pour représenter la position unique du personnage sur la carte !Structurellement, voici comment j’ai organisé le HTML :
<input type="radio" name="position" id="tile-0-0" />
<input type="radio" name="position" id="tile-0-1" />
<input type="radio" name="position" id="tile-0-2" />
<!-- ... et ainsi de suite pour chaque case -->
Maintenant, comment faire apparaître le personnage sur la bonne case ? C’est là que le sélecteur :checked entre en jeu. Avec des combinateurs de sélecteurs CSS, on peut cibler des éléments en fonction de l’état coché d’un input :
#tile-3-4:checked ~ .game-screen .player {
top: 48px; /* 3 tiles * 16px */
left: 64px; /* 4 tiles * 16px */
}
Le combinateur ~ (sélecteur de frère général) permet de cibler le personnage lorsque l’input correspondant est coché. Multiplié par les 90 cases de la grille, ça fait beaucoup de CSS, mais ça fonctionne !
Gérer les déplacements
Maintenant que le personnage peut se positionner sur n’importe quelle case, il faut s’assurer qu’on ne puisse cliquer que sur les cases adjacentes. On ne peut quand même pas téléporter notre dresseur d’un bout à l’autre de la carte !
Pour cela, j’ai utilisé des <label> positionnés de manière absolue sur chaque case adjacente possible. L’astuce est de les afficher uniquement quand le personnage est sur une case donnée :
#tile-3-4:checked ~ .game-screen label[for="tile-2-4"],
#tile-3-4:checked ~ .game-screen label[for="tile-4-4"],
#tile-3-4:checked ~ .game-screen label[for="tile-3-3"],
#tile-3-4:checked ~ .game-screen label[for="tile-3-5"] {
display: block;
cursor: pointer;
}
Ainsi, seuls les labels pour les cases adjacentes (haut, bas, gauche, droite) sont cliquables. Quand on clique sur un label, il active son input radio associé via l’attribut for, et le personnage se déplace !
Les animations : donner vie au mouvement
Un déplacement instantané, c’est bien, mais manque de charme. Les vrais jeux Pokémon avaient une petite animation de marche. Grâce aux transition CSS, on peut facilement recréer cet effet :
.player {
transition: top 0.3s ease-out, left 0.3s ease-out;
}
Le personnage glisse maintenant élégamment d’une case à l’autre. Pour aller plus loin, j’ai même ajouté une animation de sprite pour simuler les pas du personnage grâce à animation et steps() :
.player {
animation: walk 0.6s steps(4) infinite;
}
@keyframes walk {
from { background-position-x: 0; }
to { background-position-x: -64px; }
}
L’animation ne se joue que pendant le déplacement, ce qui est géré en démarrant l’animation uniquement lors d’un changement de position.
Les obstacles : on ne passe pas !
Dans Pokémon, on ne peut pas traverser les arbres ou les maisons. Il faut donc désactiver certaines cases. La solution ? Tout simplement ne pas créer de label pour ces cases-là. Pas de label = pas de clic possible = obstacle infranchissable.
Pour les zones d’herbe haute qui déclenchent des combats dans le jeu original, j’ai utilisé une classe CSS spéciale qui modifie l’apparence de la case pour indiquer visuellement que c’est une zone différente.
Les limites rencontrées
Soyons honnêtes, cette approche a ses limites :
- Le CSS devient massif : avec 90 cases et 4 directions possibles par case, on se retrouve avec des centaines de règles CSS. Un préprocesseur comme SASS avec des boucles devient indispensable pour générer tout ça de manière maintenable.
- Pas d’aléatoire : impossible de générer des rencontres aléatoires avec des Pokémon sauvages. Le CSS est déterministe, pas de Math.random() ici !
- Complexité exponentielle : pour ajouter d’autres mécaniques (inventaire, combats, menu), il faudrait multiplier les états. Un input radio par position + un par élément de menu + un par Pokémon… Ça devient vite ingérable.
- Performance : le navigateur doit recalculer énormément de règles CSS à chaque clic. Sur une grande carte, ça peut ralentir.
Alors, jusqu’où peut-on aller ?
Ce POC démontre qu’en 2025, le CSS a atteint une maturité impressionnante. Avec les sélecteurs avancés, les pseudo-classes, les animations et les transitions, on peut créer des interactions complexes sans une ligne de JavaScript.
Est-ce raisonnable de recréer un jeu entier ainsi ? Probablement pas. Mais est-ce fascinant de voir jusqu’où on peut pousser une technologie en dehors de son usage prévu ? Absolument !
Ce projet m’a appris une chose essentielle : les contraintes stimulent la créativité. En m’interdisant JavaScript, j’ai dû repenser complètement l’approche d’un problème que je pensais trivial. Et finalement, j’ai découvert des techniques CSS que je n’aurais jamais explorées autrement.
Si vous voulez voir le résultat final et explorer le code, je vous invite à consulter le CodePen du projet. N’hésitez pas à le forker et à l’améliorer !
Et vous, jusqu’où avez-vous poussé le CSS ? Quels sont les projets les plus fous que vous avez réalisés avec des « simples » feuilles de style ? 🎮


Laisser un commentaire