Maîtriser le Développement de Jeux Web : Créez Vos Propres Expériences Ludiques en HTML5 et JavaScript
Maîtriser le Développement de Jeux Web : Créez Vos Propres Expériences Ludiques en HTML5 et JavaScript

Fondamentaux du HTML5 Canvas : Dessiner et Animer vos Premiers Éléments de Jeu

Contexte du cours : Maîtriser le Développement de Jeux Web : Créez Vos Propres Expériences Ludiques en HTML5 et JavaScript

Introduction : L'Atelier Visuel de Vos Jeux Web

Bienvenue dans le monde fascinant du HTML5 Canvas ! Si vous aspirez à créer des jeux web interactifs et visuellement riches, le Canvas est votre pinceau et votre toile numérique. C'est l'outil fondamental qui vous permettra de dessiner des graphiques, des images et des animations directement dans votre navigateur web, le tout contrôlé par JavaScript.

Dans cette leçon, nous allons explorer les bases du Canvas, depuis sa configuration initiale jusqu'à la manipulation de formes, d'images et de texte, pour enfin aborder les principes de l'animation. Vous apprendrez à :

  • Mettre en place un élément canvas dans votre page HTML.
  • Obtenir le contexte de rendu 2D pour dessiner.
  • Dessiner des formes géométriques de base (rectangles, cercles, lignes).
  • Appliquer des styles (couleurs, épaisseurs de trait).
  • Intégrer des images et du texte.
  • Manipuler le canevas avec des transformations (translation, rotation, échelle).
  • Comprendre le cycle de vie de l'animation avec requestAnimationFrame.

Préparez-vous à donner vie à vos idées de jeu !

1. Comprendre le HTML5 Canvas

Le <canvas> est un élément HTML qui fournit une zone de dessin bitmap, sur laquelle vous pouvez utiliser JavaScript pour rendre des graphiques, des animations et même des vidéos. Il est idéal pour des applications graphiques intenses comme les jeux, la visualisation de données ou l'édition d'images.

1.1 Mettre en place l'élément <canvas>

Pour commencer, vous avez besoin d'un élément <canvas> dans votre fichier HTML. Il est recommandé de lui donner un id pour pouvoir le récupérer facilement en JavaScript, et de spécifier des attributs width et height.

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Premier Canvas de Jeu</title>
    <style>
        body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #333; }
        canvas { border: 2px solid #fff; background-color: #000; }
    </style>
</head>
<body>
    <!-- L'élément canvas où tout va se passer -->
    <canvas id="gameCanvas" width="800" height="600"></canvas>

    <!-- Votre script JavaScript viendra ici -->
    <script>
        // Le code JavaScript pour interagir avec le canvas
    </script>
</body>
</html>
  • id="gameCanvas": Permet d'identifier et de récupérer le canvas dans JavaScript.
  • width="800" height="600": Définit la taille de la zone de dessin en pixels. Il est crucial de définir ces attributs directement sur l'élément <canvas> ou via JavaScript pour éviter que le navigateur ne le redimensionne de manière imprévue. Les styles CSS width et height redimensionnent uniquement l'élément visuellement, pas sa résolution interne de dessin.

1.2 Obtenir le Contexte de Rendu 2D

Pour dessiner sur le canvas, vous avez besoin d'un contexte de rendu. Pour les jeux 2D, il s'agira du contexte '2d'.

// Récupérer l'élément canvas
const canvas = document.getElementById('gameCanvas');

// Vérifier si le canvas est pris en charge par le navigateur
if (canvas.getContext) {
    // Obtenir le contexte de rendu 2D
    const ctx = canvas.getContext('2d');

    // Votre code de dessin va ici, utilisant l'objet 'ctx'
    console.log("Canvas initialisé avec succès !");
} else {
    console.error("Votre navigateur ne supporte pas le HTML5 Canvas.");
    // Afficher un message d'erreur ou un contenu alternatif
    alert("Votre navigateur ne supporte pas le HTML5 Canvas. Veuillez le mettre à jour.");
}

L'objet ctx (pour "context") est une instance de CanvasRenderingContext2D. C'est cet objet qui expose toutes les méthodes de dessin que nous allons utiliser.

2. Dessiner des Formes Statiques

Le contexte 2D offre une multitude de fonctions pour dessiner des formes géométriques de base.

2.1 Formes de Base

Rectangles

Les rectangles sont parmi les formes les plus simples à dessiner.

  • ctx.fillRect(x, y, width, height): Dessine un rectangle plein.
  • ctx.strokeRect(x, y, width, height): Dessine le contour d'un rectangle.
  • ctx.clearRect(x, y, width, height): Efface une zone rectangulaire du canvas, la rendant transparente.

Chemins (Lignes et Formes Complexes)

Pour des formes plus complexes que des rectangles, vous utilisez des chemins. Un chemin est une série de points connectés qui peuvent être fermés pour former une forme ou laissés ouverts pour former des lignes.

  • ctx.beginPath(): Commence un nouveau chemin. Très important pour isoler les formes.
  • ctx.moveTo(x, y): Déplace le "crayon" vers le point (x, y) sans dessiner.
  • ctx.lineTo(x, y): Dessine une ligne du point actuel vers (x, y).
  • ctx.closePath(): Ferme le chemin en traçant une ligne du point actuel vers le point de départ de ce chemin.
  • ctx.stroke(): Dessine le contour du chemin actuel.
  • ctx.fill(): Remplit le chemin actuel avec la couleur de remplissage.

Cercles et Arcs

Les cercles et arcs sont dessinés en utilisant la méthode arc().

  • ctx.arc(x, y, radius, startAngle, endAngle, counterClockwise):

    • (x, y): Centre de l'arc.
    • radius: Rayon de l'arc.
    • startAngle: Angle de départ en radians.
    • endAngle: Angle de fin en radians.
    • counterClockwise: Booléen (par défaut false): true pour un sens anti-horaire, false pour horaire.

    Note : Les angles en Canvas sont en radians. Pour convertir des degrés en radians, utilisez degrés * Math.PI / 180.

2.2 Styles de Dessin

Avant de dessiner, vous pouvez définir l'apparence de vos formes.

  • ctx.fillStyle = color: Définit la couleur de remplissage pour fillRect() et fill(). Peut être un nom de couleur, un hexadécimal, RGB, RGBA.
  • ctx.strokeStyle = color: Définit la couleur du trait pour strokeRect() et stroke().
  • ctx.lineWidth = value: Définit l'épaisseur des traits en pixels.
  • ctx.lineCap = type: Définit l'apparence des extrémités de ligne (butt, round, square).
  • ctx.lineJoin = type: Définit l'apparence des angles de ligne (bevel, round, miter).

Ombrages

Vous pouvez ajouter des ombres à vos formes pour leur donner de la profondeur.

  • ctx.shadowBlur = level: Niveau de flou de l'ombre (en pixels).
  • ctx.shadowColor = color: Couleur de l'ombre.
  • ctx.shadowOffsetX = x: Décalage horizontal de l'ombre par rapport à la forme.
  • ctx.shadowOffsetY = y: Décalage vertical de l'ombre.

Exemple de Code 1 : Dessin de Formes Simples

Intégrons tout cela dans notre script JavaScript.

// ... (code HTML et initialisation du canvas/ctx) ...
<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // 1. Dessiner un rectangle plein
    ctx.fillStyle = 'red'; // Définir la couleur de remplissage
    ctx.fillRect(50, 50, 100, 75); // (x, y, width, height)

    // 2. Dessiner le contour d'un rectangle avec une couleur et épaisseur de trait
    ctx.strokeStyle = 'blue'; // Couleur du trait
    ctx.lineWidth = 5;       // Épaisseur du trait
    ctx.strokeRect(200, 50, 100, 75);

    // 3. Dessiner un cercle
    ctx.beginPath(); // Commencer un nouveau chemin
    ctx.arc(400, 100, 50, 0, Math.PI * 2, false); // (x, y, rayon, angle_départ, angle_fin, anti-horaire)
    ctx.fillStyle = 'green';
    ctx.fill(); // Remplir le cercle
    ctx.strokeStyle = 'darkgreen';
    ctx.stroke(); // Dessiner le contour du cercle

    // 4. Dessiner une ligne brisée (triangle)
    ctx.beginPath();
    ctx.moveTo(550, 50); // Point de départ
    ctx.lineTo(650, 50); // Ligne vers la droite
    ctx.lineTo(600, 150); // Ligne vers le bas et le centre
    ctx.closePath(); // Ferme le chemin pour former un triangle
    ctx.strokeStyle = 'orange';
    ctx.lineWidth = 3;
    ctx.stroke(); // Dessiner le contour

    // 5. Rectangle avec ombre
    ctx.fillStyle = 'purple';
    ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // Couleur de l'ombre (semi-transparente)
    ctx.shadowBlur = 10;                     // Flou de l'ombre
    ctx.shadowOffsetX = 5;                   // Décalage X
    ctx.shadowOffsetY = 5;                   // Décalage Y
    ctx.fillRect(50, 200, 100, 100);

    // Réinitialiser les ombres (très important pour ne pas affecter les dessins suivants)
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;

    // 6. Exemple de lineCap et lineJoin
    ctx.lineWidth = 15;
    ctx.strokeStyle = 'white';
    ctx.lineCap = 'round'; // Extrémités arrondies
    ctx.lineJoin = 'round'; // Angles arrondis
    ctx.beginPath();
    ctx.moveTo(250, 250);
    ctx.lineTo(350, 250);
    ctx.lineTo(300, 350);
    ctx.stroke();

</script>

Ce premier bloc de code vous permet de visualiser immédiatement l'effet de ces méthodes sur votre canevas. Exécutez le code et observez les différentes formes apparaître.

3. Dessiner des Images et du Texte

Au-delà des formes géométriques, Canvas excelle dans l'affichage d'éléments plus complexes comme les images (sprites de jeu) et le texte (scores, messages).

3.1 Dessiner des Images avec drawImage()

La méthode drawImage() est cruciale pour afficher des éléments graphiques. Elle est polyvalente et permet de dessiner une image entière ou une partie spécifique (pour les spritesheets).

  • Charger une image : Avant de dessiner, l'image doit être chargée dans un objet Image de JavaScript. C'est une opération asynchrone, donc le dessin doit se faire après que l'image soit chargée.

    const img = new Image(); // Crée un nouvel objet Image
    img.src = 'chemin/vers/votre/image.png'; // Définit la source de l'image
    img.onload = () => {
        // Le code de dessin de l'image va ici, après que l'image soit chargée
        ctx.drawImage(img, x, y); // Dessine l'image une fois chargée
    };
    img.onerror = () => {
        console.error("Erreur lors du chargement de l'image !");
    };
    
  • Syntaxe de drawImage() :

    • ctx.drawImage(image, x, y): Dessine l'image à la position (x, y) avec sa taille originale.
    • ctx.drawImage(image, x, y, width, height): Dessine l'image redimensionnée aux dimensions width et height à la position (x, y).
    • ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight): La version la plus complète pour les spritesheets.
      • (sx, sy, sWidth, sHeight): Définit la source (la partie de l'image d'origine à découper).
      • (dx, dy, dWidth, dHeight): Définit la destination (où et comment cette partie sera dessinée sur le canvas).

3.2 Dessiner du Texte

Le texte est essentiel pour les informations de jeu comme les scores, les messages de statut ou les dialogues.

  • ctx.font = "size family": Définit la police et la taille du texte (ex: "30px Arial").
  • ctx.textAlign = alignment: Alignement horizontal (start, end, left, right, center).
  • ctx.textBaseline = alignment: Alignement vertical (alphabetic, top, hanging, middle, ideographic, bottom).
  • ctx.fillText(text, x, y): Dessine le texte rempli.
  • ctx.strokeText(text, x, y): Dessine le contour du texte.

Exemple de Code 2 : Image et Texte

Pour cet exemple, assurez-vous d'avoir une image nommée player.png (ou remplacez par l'URL d'une image en ligne) dans le même répertoire que votre fichier HTML, ou adaptez le chemin.

// ... (code HTML et initialisation du canvas/ctx) ...
<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // 1. Dessiner une image
    const playerImg = new Image();
    playerImg.src = 'https://mdn.github.io/dom-examples/canvas/starfield/assets/galaxy.png'; // Exemple d'URL d'image
    // Ou si vous avez une image locale : playerImg.src = 'player.png';

    playerImg.onload = () => {
        // Dessiner l'image à la position (50, 50) avec sa taille originale
        ctx.drawImage(playerImg, 50, 350);

        // Dessiner l'image redimensionnée à (200, 200) à la position (200, 350)
        ctx.drawImage(playerImg, 200, 350, 100, 100);

        // Pour les spritesheets, imaginez que playerImg contient plusieurs sprites.
        // Ici, nous simulons en découpant une partie de l'image source
        // et en la dessinant à une autre taille. (sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
        // Cet exemple est plus conceptuel pour une image simple, mais très utile pour les assets de jeu.
        ctx.drawImage(playerImg, 0, 0, 50, 50, 350, 350, 75, 75);
    };

    playerImg.onerror = () => {
        console.error("Erreur lors du chargement de l'image du joueur.");
    };


    // 2. Dessiner du texte
    ctx.font = '48px Impact'; // Taille et famille de police
    ctx.fillStyle = '#FFD700'; // Couleur dorée
    ctx.textAlign = 'center'; // Centrer le texte horizontalement
    ctx.textBaseline = 'middle'; // Centrer le texte verticalement par rapport au point y

    // Dessiner le texte "Score: 0" au centre haut du canvas
    ctx.fillText('Score: 0', canvas.width / 2, 50);

    ctx.strokeStyle = 'black'; // Contour noir
    ctx.lineWidth = 2;
    ctx.strokeText('Score: 0', canvas.width / 2, 50);

    // Autre texte avec un style différent
    ctx.font = '24px Arial';
    ctx.fillStyle = 'white';
    ctx.textAlign = 'left';
    ctx.fillText('Vies: 3', 50, 450); // En bas à gauche
</script>

4. Transformer et Manipuler le Contexte

Les transformations vous permettent de déplacer, faire pivoter et redimensionner le système de coordonnées du canvas. Cela affecte tous les dessins effectués après la transformation.

  • ctx.translate(x, y): Déplace l'origine (0,0) du canvas aux nouvelles coordonnées (x, y). Utile pour dessiner des objets par rapport à leur propre centre.
  • ctx.rotate(angle): Fait pivoter le canevas d'un certain angle (en radians) autour de son origine actuelle.
  • ctx.scale(x, y): Redimensionne les unités du canevas. scale(2, 2) double la taille de tout ce qui est dessiné.

save() et restore() : La Clé des Transformations

Les transformations sont cumulatives et affectent tous les dessins subséquents. Pour éviter que les transformations d'un objet n'affectent les autres, utilisez ctx.save() et ctx.restore().

  • ctx.save(): Enregistre l'état actuel du contexte (transformations, styles, etc.) sur une pile.
  • ctx.restore(): Restaure le dernier état enregistré depuis la pile.

Ceci est fondamental pour appliquer des transformations temporaires à des éléments spécifiques sans impacter le reste du dessin.

Exemple de Code 3 : Transformations

// ... (code HTML et initialisation du canvas/ctx) ...
<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // Carré sans transformation
    ctx.fillStyle = 'cyan';
    ctx.fillRect(50, 50, 80, 80);

    // Carré déplacé (translaté)
    ctx.save(); // Enregistrer l'état actuel (sans translation)
    ctx.translate(200, 50); // Déplacer l'origine à (200, 50)
    ctx.fillStyle = 'magenta';
    ctx.fillRect(0, 0, 80, 80); // Dessiné à (0,0) par rapport à la nouvelle origine
    ctx.restore(); // Restaurer l'état précédent (reviens à l'origine normale)

    // Carré pivoté
    ctx.save();
    ctx.translate(400, 100); // Déplacer l'origine au centre du carré pour une rotation autour de son centre
    ctx.rotate(45 * Math.PI / 180); // Pivoter de 45 degrés (en radians)
    ctx.fillStyle = 'yellow';
    ctx.fillRect(-40, -40, 80, 80); // Dessiner le carré centré sur la nouvelle origine (400, 100)
    ctx.restore();

    // Carré redimensionné
    ctx.save();
    ctx.translate(550, 50); // Déplacer pour éviter les chevauchements
    ctx.scale(1.5, 0.75); // Agrandir de 1.5x en X, 0.75x en Y
    ctx.fillStyle = 'lime';
    ctx.fillRect(0, 0, 80, 80);
    ctx.restore();

    // Carré avec plusieurs transformations (translate, rotate, scale)
    ctx.save();
    ctx.translate(canvas.width / 2, canvas.height / 2); // Déplacer au centre du canvas
    ctx.rotate(30 * Math.PI / 180); // Rotation
    ctx.scale(1.2, 1.2); // Échelle
    ctx.fillStyle = 'orange';
    ctx.fillRect(-50, -50, 100, 100); // Dessiner un carré de 100x100 centré sur la nouvelle origine
    ctx.restore();
</script>

Ce concept de save/restore combiné aux transformations est essentiel pour gérer la position et l'orientation de chaque objet dans un jeu sans que leurs transformations n'interfèrent les unes avec les autres.

5. Les Bases de l'Animation avec Canvas

L'animation consiste à créer l'illusion de mouvement en affichant une séquence rapide d'images statiques légèrement différentes. Avec Canvas, cela se traduit par un cycle continu de :

  1. Effacer l'ancien dessin.
  2. Mettre à jour les positions/états des objets du jeu.
  3. Dessiner les objets à leurs nouvelles positions.

5.1 Le Principe de l'Animation (Boucle de Jeu)

Une boucle de jeu typique ressemblerait à ceci :

function gameLoop() {
    // 1. Effacer le canvas
    // 2. Mettre à jour les positions/états des objets (personnages, projectiles, etc.)
    // 3. Dessiner tous les objets à leurs nouvelles positions

    // Appeler la boucle à nouveau pour la prochaine frame
    requestAnimationFrame(gameLoop);
}
// Démarrer la boucle
requestAnimationFrame(gameLoop);

5.2 requestAnimationFrame vs setInterval/setTimeout

Pour les animations, requestAnimationFrame() est la méthode préférée et recommandée par rapport à setInterval() ou setTimeout().

  • requestAnimationFrame(callback):

    • Le navigateur l'exécute juste avant le prochain rafraîchissement de l'écran (environ 60 fois par seconde, si possible).
    • Il est mis en pause lorsque l'onglet n'est pas actif, économisant les ressources du CPU/batterie.
    • Il offre un meilleur synchronisme avec le rafraîchissement du moniteur, résultant en des animations plus fluides et sans déchirements visuels.
    • Il passe un argument à votre fonction de callback : le temps écoulé depuis le chargement de la page, qui peut être utilisé pour des animations basées sur le temps.
  • setInterval()/setTimeout():

    • Exécutent le code à intervalles fixes, indépendamment du taux de rafraîchissement du navigateur. Peut entraîner des saccades ou une consommation excessive de ressources.

5.3 Créer une Simple Animation : Un Carré Qui Bouge

Nous allons animer un carré qui se déplace horizontalement sur le canvas.

// ... (code HTML et initialisation du canvas/ctx) ...
<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // Variables pour notre carré en mouvement
    let squareX = 0; // Position X de départ
    let squareY = 100; // Position Y fixe
    const squareSize = 50; // Taille du carré
    let speed = 2; // Vitesse de déplacement

    // Fonction de mise à jour et de dessin (la boucle de jeu)
    function gameLoop() {
        // 1. Effacer tout le canvas (ou juste la zone affectée)
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 2. Mettre à jour la position du carré
        squareX += speed;

        // Inverser la direction si le carré atteint les bords
        if (squareX + squareSize > canvas.width || squareX < 0) {
            speed *= -1; // Inverse la vitesse
            // Assurez-vous que le carré est bien dans les limites après le changement de direction
            if (squareX + squareSize > canvas.width) {
                squareX = canvas.width - squareSize;
            }
            if (squareX < 0) {
                squareX = 0;
            }
        }

        // 3. Dessiner le carré à sa nouvelle position
        ctx.fillStyle = 'deepskyblue';
        ctx.fillRect(squareX, squareY, squareSize, squareSize);

        // Demander au navigateur d'appeler gameLoop() à la prochaine frame
        requestAnimationFrame(gameLoop);
    }

    // Démarrer l'animation
    requestAnimationFrame(gameLoop);
</script>

Dans cet exemple, la fonction gameLoop est notre cœur de jeu. Elle est appelée en continu par requestAnimationFrame. À chaque appel, elle efface l'écran, met à jour la position du carré, puis redessine le carré à cette nouvelle position, créant ainsi l'illusion de mouvement.

Conclusion et Prochaines Étapes

Félicitations ! Vous avez fait vos premiers pas dans le monde passionnant du HTML5 Canvas pour le développement de jeux web. Vous savez maintenant :

  • Comment configurer un environnement Canvas.
  • Dessiner des formes de base, des images et du texte.
  • Appliquer des styles et des transformations pour positionner et styliser vos éléments.
  • Maîtriser les bases de l'animation à l'aide de requestAnimationFrame.

Le Canvas est un outil puissant, et ces fondamentaux sont la pierre angulaire de toute expérience visuelle interactive que vous souhaiterez créer.

Pour aller plus loin :

  • Gestion des interactions utilisateur : Apprenez à écouter les événements clavier et souris pour faire interagir vos éléments de jeu.
  • Structuration du code : Organisez votre code en objets (personnages, projectiles, niveaux) pour faciliter la gestion de projets complexes.
  • Spritesheets avancées : Exploitez pleinement drawImage pour animer des personnages avec plusieurs images.
  • Collision Detection : Implémentez des algorithmes pour détecter quand deux objets se touchent.
  • Optimisation : Gérez la performance pour des jeux fluides même avec de nombreux objets.

Le chemin est long, mais chaque ligne de code que vous écrivez vous rapproche de la création de vos propres univers ludiques. Continuez à expérimenter, à coder et à vous amuser !