Go to Hackademy website

Création d'un jeu en CSS

Victor Darras

Posté par Victor Darras dans les catégories intégration

Un jeu en CSS ? mais ça n’a aucun sens !

Une fois n’est pas coutume, nous allons aujourd’hui repousser les limites des CSS afin d’en comprendre les aspects les plus intéressants en créant un jeu ! Il est assez évident que CSS n’est pas un langage de programmation comme Ruby ou JavaScript et qu’il nous offre des possibilités limitées. Mais ça ne me fait pas peur, avec quelques astuces je suis sûr que nous pourrons créer un jeu simple mais qui saura nous donner du fil à retordre et du fun.

Le but du jeu

L’idée de notre jeu est simple, nous utilisons le curseur comme “vaisseau” et devons parcourir un couloir sinueux en évitant ses murs. Lorsque l’un des murs est touché nous revenons au point de départ et ce, avec une vie et un niveau. Pour le confort de l’exercice, ce niveau dure environ 10 secondes, mais comme vous pourrez le voir il est extensible à l’infini.

Quelques lignes de HTML

Commençons par placer les éléments qui composent notre interface de jeu. Nous mettons en place une page HTML basique avec doctype, head, un lien vers une feuille de style et un body. Dans celui-ci nous ajoutons une DIV avec l’ID game qui contient un #start-spot, 2 murs que nous appelons .wall-left et .wall-right, un écran de #gameover et un écran de succès que nous appelons #win. Ces 2 derniers écrans contiennent chacun un texte pour indiquer au joueur s’il a perdu ou gagné.

  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>CSS Game</title>
      <link rel="stylesheet" href="style.css">
    </head>
    <body>

      <div id="game">
        <div id="start-spot"></div>
        <div class="wall-left"></div>
        <div class="wall-right"></div>

        <div id="gameover"><h1>You lose</h1></div>
        <div id="win"><h1>Congratulations !<br/>You win</h1></div>
      </div>
    </body>
  </html>

On attaque les CSS

Afin de mettre en place le jeu, voici quelques règles primordiales que nous verrons :

  • clip-path pour donner la forme que l’on veut à chacun des murs
  • transform pour le déplacement et l’effet de 3d
  • animation et @keyframes pour les déplacements et l’animation des éléments

L’espace de jeu

Dans un premier temps définissons l’espace qui contiendra le jeu ; on applique un ensemble de règles pour réinitialiser les conteneurs HTML et BODY :

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  overflow: hidden;
  text-align: center;
  /* Quelques règles pour la forme */
  font-family: sans-serif;
  background: #333;
  color: #fff;
  text-transform: uppercase;
}

Dans un second temps on ajoute des styles pour définir l’espace de jeu, un écran de 800 par 600px pour un maximum de sensations ! Une box-shadow pour faire ressortir le-dit écran et une règle transform qui nous permet de donner une impression de 3d pour rendre le jeu un poil plus immersif. Le translate permet de contre-carrer le déplacement de l’écran dans la page dû à la rotation en X.

#game {
  position: relative;
  width: 800px;
  height: 600px;
  margin: 20px auto 40px;
  box-shadow: 0 1px 15px #222;
  overflow: hidden;
  -webkit-transform: perspective(200px) rotateX(15deg) translateY(-90px);
          transform: perspective(200px) rotateX(15deg) translateY(-90px);
}

Les différents écrans de jeu

Nous avons deux écrans différents qui marquent les étapes du jeu, le #gameover et le #win. Tous deux viendront se placer au dessus de l’ensemble des éléments en prenant toute la surface du jeu. L’écran de Game Over se verra affublé d’un fond coloré pour que le joueur comprenne qu’il a perdu. L’écran de succès quant à lui aura une opacité nulle qui nous permettra de faire une transition plus douce en fin de jeu. Le pointer-events: none; nous permet de l’avoir constamment au dessus du reste sans qu’il n’influence les contrôles du jeu. Ces 2 écrans ne sont donc pas affichés par défaut.

#gameover,
#win {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
}

#gameover {
  display: none;
  background: tomato;
}
#win {
  opacity: 0;
  pointer-events: none;
}

Nous stylons aussi simplement le point de départ qui indique au joueur où placer son curseur en début de partie. C’est un simple indicateur puisqu’il suffit en réalité de survoler le bloc #game comme nous le verrons plus tard.

#start-spot {
  position: absolute;
  bottom: 10px;
  left: 50%;
  width: 20px;
  height: 20px;
  -webkit-transform: translateX(-50%);
          transform: translateX(-50%);
  background: #fff;
}

CSS clip-path pour former les murs

Mettons en place les styles de base de chacun de nos murs. Afin de pouvoir voir l’ensemble du niveau mettons la hauteur des murs à 100%. Par la suite nous les étirerons afin d’avoir un effet de déroulé bien plus intéressant :

.wall-left,
.wall-right {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 100%; /* Nous changerons cette valeur pour 400% une fois le niveau dessiné */
  background: linear-gradient(#fff,#777);
}

Afin de ne pas former 2 murs parallèles qui seraient très ennuyeux à éviter (forcémment…) nous allons utiliser une règle puissante des CSS, à savoir clip-path. Cette règle permet de définir un masque préçis où l’on définit les coordonnées de chacun des points d’un polygone. Cette même règle permet aussi plus simplement de définir des ellipses ou des rectangles ou encore utiliser du SVG pour créer des courbes plus complexes dans votre éditeur favori. Pour bien comprendre comment fonctionne un clip-path nous allons définir notre polygone point par point. Pour le mur de gauche :

.wall-left {
  left: 0;
  -webkit-clip-path: polygon(
    0 0 /* Une premier point en haut à gauche de l’écran */,
    40% 0 /* Le second point en haut à droite de la forme */,
    35% 15% /* Ce point comme les suivants crée les « zig-zag » */,
    55% 35%,
    45% 45%,
    60% 60%,
    30% 80%,
    40% 100% /* Le point à gauche du point de #start-spot */,
    0 100% /* Ce dernier point en bas à gauche de l’écran %/
  );
}

Nous créons de la même manière le mur de droite, je vous laisse le loisir de jouer avec ses dimensions tout en sachant qu’il ne faut évidemment pas que les murs se chevauchent auquel cas le jeu deviendrait impossible !

.wall-right {
  right: 0;
  -webkit-clip-path: polygon(60% 0, 100% 0, 100% 100%, 60% 100%, 44% 81%, 70% 60%,57% 44%, 65% 35%, 46% 15%);
}

Il est possible d’ajouter beaucoup plus de points et/ou de les séparer les uns des autres afin de faire voyager le joueur de gauche à droite et rendre le jeu plus difficile.

Okay visuellement tout est là, dynamisons l’ensemble !

Il nous faut définir les animations dont nous aurons besoin (par soucis de simplicité je n’écris pas les versions préfixées, mais n’oublions pas de les ajouter). Pour les murs, rien de plus simple, nous partons d’une keyframe win 0% où il ne se passe rien à une keyframe 100% où le niveau sera entièrement déroulé :

@keyframes wallScroll {
  0% {
    transform: none;
  }
  100% {
    transform: translateY(100%);
  }
}

Nous ajoutons ensuite une keyframe qui affichera l’écran de fin si le joueur arrive jusqu’au bout de l’animation (et donc du niveau) :

@keyframes win {
  0% {
    opacity: 0;
  }
  99% {
    opacity: 0; /* on attend le tout dernier moment de l'animation */
  }
  100% {
    opacity: 1;
  }
}

Maintenant que nous avons nos animations il est temps de les activer au moment opportun. Tout d’abord dès que nous entrons dans l’espace de jeu, les murs commencent à se déplacer :

#game:hover .wall-left,
#game:hover .wall-right {
  -webkit-animation: walls 10s ease-in forwards;
          animation: walls 10s ease-in forwards;
}

Ensuite, dès le début du niveau, nous lançons l’animation de fin avec le même timing, ainsi dès qu’on arrive à la fin on affiche le message de félicitation :

#game:hover #win {
  -webkit-animation: win 10s linear forwards;
          animation: win 10s linear forwards;
}

En passant on retire le #start-point qui ne sera pas utile lors de notre parcours :

#game:hover #start-spot {
  display: none;
}

Et enfin le plus important ! Dès que l’on survole l’un des murs, nous affichons l’écran de Game Over au dessus de l’ensemble. On applique la même règle au survol de l’écran de Game Over pour s’assurer qu’il reste bien présent jusqu’à ce que l’utilisateur recommence une partie. Malgré l’écran de défaite affiché, les animations en arrière plan se déroulent toujours mais nous ne pouvons plus les voir. Pour revenir au début du jeu il faut alors sortir de l’écran et l’ensemble du niveau (et donc des animations) reviendra au début.

/* on utilise ici le '~' pour selectioner un élément au même niveau plus loin dans le DOM */
.wall-left:hover ~ #gameover,
.wall-right:hover ~ #gameover,
#gameover:hover {
  display: block;
  z-index: 1;
}

Pour ajouter quelques points de charisme à notre jeu nous allons centrer les textes des écrans de jeu avec une astuce relativement simple qui consiste à positionner l’élément à 50% des bords haut et gauche de l’écran puis à le déplacer lui-même de 50% de sa propre taille dans les mêmes directions :

#gameover h1,
#win h1 {
  position: absolute;
  top: 50%;
  left: 50%;
  -webkit-transform: translate3d(-50%, -50%, 0);
          transform: translate3d(-50%, -50%, 0);
}

Bonus

Notre jeu est fonctionnel, mais encore bien trop simple à mon goût. Voyons ensemble comment nous pourrions modifier le niveau pour faire perdre un peu de cheveux à nos joueurs. La modification des murs nous permettra ainsi de voir une technique dont j’ai parlé brièvement et qui est bien plus pratique pour rendre notre environnement plus intéressant, le SVG.

Un peu de level design

Pour l’exemple vous pouvez télécharger un SVG que j’ai déjà crée ou le fichier sketch mais je vous incite à ouvrir votre éditeur vectoriel favori (InkScape, Sketch ou Illustrator par exemple) pour créer 2 formes qui composeront chacun de nos murs dans un espace de 800px (la largeur de notre écran) par 600px * 4 soit 2400px qui nous permettra de scroller sur 4 fois la hauteur de notre écran. Grâce au SVG vous pourrez utiliser des courbes, et l’édition du niveau sera bien plus aisée qu’avec l’utilisation de polygon().

Une fois le SVG créé nous devons l’intégrer dans notre page HTML. Sketch par exemple exporte un SVG avec beaucoup de données inutiles, nous allons donc déclarer un SVG à la main que nous viendrons compléter avec les courbes définies dans le SVG. Dans mon exemple vous trouverez deux element path, #path-1 et #path-2 que l’on copie dans deux élements clipPath auxquels on attribue un ID que nous réutiliserons dans les CSS.

<svg><!-- On déclare un SVG (en général juste après <body>, en début de document). -->
    <defs><!-- On crée un bloc de définition, -->
        <clipPath id="left"><!-- un premier clip path -->
          <path id="path-1" d="M310.664062,2494.08984 L312.664062,2248.46875 L237.390625,2194.82812 L137.777344,2030.35938 L237.390625,1931.83594 L201.117188,1718.30469 L512.40625,1671.47266 L625.328125,1623.67187 L554.5,1528.82813 L554.671875,1367.17188 L505.5,1496.0625 L389.328125,1318.78125 L308.054687,1456.08984 L292.945312,1260.08984 L139.835938,1239.41797 L193.140625,968.167969 L594.265625,834.582031 L589.878906,810.363281 L294.828125,639.890625 L466.5625,514 L286.109375,421.609375 L403.671875,259.953125 L259.140625,-1.77635684e-15 L0,-1.77635684e-15 L0,2400 L310.664062,2494.08984 Z"></path>
        </clipPath>
        <clipPath id="right"><!-- et un second clip path. -->
          <path id="path-2" d="M507.574219,2400 L477.292969,2217.58203 L322.253906,2136.07031 L277.097656,2050.32031 L335.421875,1981.23438 L322.253906,1799.02734 L555.867188,1774.47266 L740.628906,1650.70703 L634.464844,1501 L606.171875,1092.44531 L489.804688,1280.52734 L455.144531,1174.73438 L363.847656,1236.04297 L393.441406,1148.53906 L268.179688,1157.70312 L292.765625,1043.58203 L708.695312,919.421875 L669.171875,751.890625 L469.5,639.890625 L641.234375,514 L460.78125,421.609375 L578.34375,259.953125 L433.8125,-1.77635684e-15 L800,-1.77635684e-15 L800,2400 L507.574219,2400 Z"></path>
        </clipPath>
    </defs>
</svg>

Lions maintenant notre SVG à nos déclarations clip-path dans bloc déjà existant :

.wall-left {
  left: 0;
  -webkit-clip-path: url('#left');
  clip-path: url('wall.svg#left');
}
.wall-right {
  right: 0;
  -webkit-clip-path: url('#right');
  clip-path: url('wall.svg#right');
}

Si on teste maintenant dans Chrome on peut remarquer un “saut” des murs en début de partie, c’est probablement dû à un bug de Webkit que l’on peut corriger en appliquant la règle suivante :

.wall-left, .wall-right {
  transform: translateZ(1px);
}

Un vaisseau spatial c’est quand même plus sympa qu’un curseur !

Une petite astuce qui vous permettra d’ajouter une cerise sur le gâteau de votre satisfaction, nous allons modifier le curseur de la souris pour en faire un vaisseau spatial flambant neuf à chaque partie. C’est relativement simple, il nous suffit d’ajouter une règle dans notre espace de jeu et d’ajouter un fichier image (de préférence avec des bords transparents) à la racine du projet comme ceci :

#game {
  cursor: url('vessel.png'), auto; /*
}

Si tout s’est bien passé, vous devriez obtenir quelque chose qui ressemble à ce jeu.

Conclusion

Vous l’aurez compris, il y a très peu d’intérêt à créer un jeu avec CSS mais je pense qu’il est toujours utile de jouer avec nos outils et de les pousser dans leurs retranchements (les hacker en quelque sorte). Ça nous permet d’être chaque jour plus à l’aise avec leurs comportements les plus complexes et parfois peu instinctifs.

Bien sûr je vous incite à rendre le jeu plus difficile, plus long ou mieux dessiné et à nous partager ensuite vos créations dans les commentaires.


L’équipe Synbioz.

Libres d’être ensemble.

Articles connexes

Images, Lazy loading ou Chargement paresseux

19/04/2018

Bonjour à tous, aujourd’hui un sujet à cheval entre intégration et développement front, nous allons faire en sorte d’améliorer les performances de rendu de vos pages web. Je pense pouvoir avancer...

État des lieux des polices

25/01/2018

Bonjour à tous, aujourd’hui j’aimerais revenir sur une notion centrale du webdesign, les polices. Parfois ignorées (oui je te vois dans le fond, jeune intégrateur impétueux que Google Fonts rend...

CSS Grid Layout c'est magique

05/05/2017

Bonjour à tous, aujourd’hui nous allons découvrir ensemble une des plus belles nouveautés de CSS depuis plusieurs années, les Grids. Si je vous dis que c’est l’une des plus belles nouveautés c’est...

Comment remplacer une image en erreur

10/01/2017

Bonjour à tous, aujourd’hui un article très abordable puisque nous allons voir ensemble comment mettre en place un élément de remplacement pour les images afin d’éviter le fameux : Lire la suite...