Étude de cas : S'enliver avec le canevas HTML5

Derek Detweiler
Derek Detweiler

Présentation

Au printemps 2010, je me suis intéressé à l'augmentation rapide de la prise en charge du format HTML5 et des technologies associées. À l'époque, nous nous mettions au défi lors de compétitions de développement de jeux de deux semaines pour perfectionner nos compétences en programmation et en développement, et donner vie aux idées de jeux que nous nous proposions constamment. J'ai donc naturellement commencé à intégrer des éléments HTML5 dans les vidéos de mon concours afin de mieux comprendre leur fonctionnement et de pouvoir faire des choses qui étaient presque impossibles à réaliser avec les spécifications HTML antérieures.

Parmi les nombreuses nouvelles fonctionnalités disponibles en HTML5, la prise en charge croissante de la balise de canevas m'a permis d'implémenter des œuvres interactives à l'aide de JavaScript. J'ai donc essayé d'implémenter un jeu de réflexion baptisé Entanglement. J'avais déjà créé un prototype à l'aide de l'arrière de tuiles Catan. Si je l'utilise comme plan, il y a trois parties essentielles pour créer la tuile hexagonale sur le canevas HTML5 pour le Web Play: dessiner l'hexagone, tracer les trajets et faire pivoter la tuile. Ce qui suit décrit en détail comment j'ai accompli chacun d'entre eux dans leur forme actuelle.

Dessiner l'hexagone

Dans la version d'origine d'Entanglement, j'utilisais plusieurs méthodes de dessin de canevas pour dessiner l'hexagone, mais la forme actuelle du jeu utilise drawImage() pour dessiner des textures rognées à partir d'une feuille de sprites.

Feuille de sprites pour les cartes
Feuille de sprites pour les cartes

J'ai combiné les images dans un seul fichier, ce qui en fait une seule requête au serveur au lieu de, dans ce cas, 10. Pour dessiner un hexagone choisi sur le canevas, nous devons d'abord rassembler nos outils: canevas, contexte et image.

Pour créer un canevas, nous n'avons besoin que du tag de canevas de notre document html, comme ceci:

<canvas id="myCanvas"></canvas>

Je lui donne un identifiant pour que nous puissions l'intégrer à notre script:

var cvs = document.getElementById('myCanvas');

Ensuite, nous devons saisir le contexte 2D du canevas afin de pouvoir commencer à dessiner:

var ctx = cvs.getContext('2d');

Enfin, nous avons besoin de l'image. S'il s'appelle "tiles.png" et se trouve dans le même dossier que notre page Web, nous pouvons l'obtenir comme suit:

var img = new Image();
img.src = 'tiles.png';

Maintenant que nous disposons des trois composants, nous pouvons utiliser la fonction ctx.drawImage() pour dessiner l'hexagone unique de notre choix depuis la feuille de sprites vers le canevas:

ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

Dans ce cas, nous utilisons le quatrième hexagone à partir de la gauche sur la ligne supérieure. Nous allons également le dessiner sur le canevas situé dans l'angle supérieur gauche, en conservant la même taille que l'original. En supposant que les hexagones aient une largeur de 400 pixels et une hauteur de 346 pixels, l'ensemble ressemblera à ceci:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

Une partie de l'image a été copiée sur le canevas avec le résultat suivant:

Tuile hexagonale
Tuile hexagonale

Dessiner des trajets

Maintenant que notre hexagone est dessiné sur le canevas, nous allons y tracer quelques lignes. Tout d'abord, nous allons nous pencher sur certaines géométries concernant la tuile hexagonale. Nous voulons que deux extrémités de ligne soient séparées par un côté, chacune se terminant à 1/4 des extrémités le long de chaque arête et à un tiers des extrémités, comme ceci:

Extrémités de ligne sur une tuile hexagonale
Extrémité de ligne sur un bloc hexagonal

Nous voulons également une bonne courbe. Par conséquent, en faisant quelques essais et erreurs, j'ai découvert que si je crée une ligne perpendiculaire à partir de l'arête à chaque extrémité, l'intersection de chaque paire d'extrémités autour d'un angle donné de l'hexagone crée un point de contrôle plus beau pour les extrémités données:

Points de contrôle sur une tuile hexagonale
Points de contrôle sur une tuile hexagonale

Mappez maintenant les points de terminaison et les points de contrôle sur un plan cartésien correspondant à notre image de canevas, puis nous sommes prêts à revenir au code. Pour faire simple, commençons par une ligne. Nous allons commencer par tracer un tracé entre l'extrémité supérieure gauche et l'extrémité inférieure droite. Avec notre précédente image hexagonale de 400 x 346, notre point de terminaison supérieur sera de 150 pixels de large et 0 pixel vers le bas, soit 150, 0. Son point de contrôle sera (150, 86). Le point de terminaison du bord inférieur est (250, 346) avec un point de contrôle de (250, 260):

Coordonnées de la première courbe de Bézier
Coordonnées de la première courbe de Bézier

Maintenant que nous avons les coordonnées, nous pouvons commencer à dessiner. Nous allons commencer à zéro avec ctx.beginPath(), puis passer au premier point de terminaison à l'aide de la commande suivante:

ctx.moveTo(pointX1,pointY1);

Nous pouvons ensuite tracer la ligne elle-même à l'aide de aussit.bezierCurveTo(), comme suit:

ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);

Comme nous voulons que la ligne ait une belle bordure, nous allons rayer ce tracé deux fois en utilisant une largeur et une couleur différentes à chaque fois. La couleur sera définie à l'aide de la propriété ctx.strokeStyle et la largeur sera définie à l'aide de somm.lineWidth. Au total, le dessin de la première ligne se présentera comme suit:

var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

Nous avons maintenant une tuile hexagonale sur laquelle la première ligne traverse:

Ligne solitaire sur une tuile hexagonale
Ligne isolée sur une tuile hexagonale

En saisissant les coordonnées des 10 autres extrémités ainsi que les points de contrôle correspondants de la courbe de Bézier, nous pouvons répéter les étapes ci-dessus et créer une carte semblable à celle-ci:

Tuile hexagonale terminée.
Tuile hexagonale terminée

Faire pivoter le canevas

Une fois que nous avons notre carte, nous voulons être en mesure de la transformer pour qu'elle puisse emprunter différentes voies dans le jeu. Pour ce faire à l'aide du canevas, nous utilisons ctx.translate() et ctx.rotate(). Nous voulons que la tuile pivote sur son centre. La première étape consiste donc à déplacer le point de référence du canevas vers le centre de la tuile hexagonale. Pour ce faire, nous utilisons:

ctx.translate(originX, originY);

Où originX sera égal à la moitié de la largeur de la tuile hexagonale et originY à la moitié de la hauteur, ce qui nous donne:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);

Nous pouvons maintenant faire pivoter la tuile avec notre nouveau point central. Étant donné qu'un hexagone a six côtés, nous allons le faire pivoter en fonction d'un multiple de Math.PI divisé par 3. Nous allons faire simple et effectuer un seul tour dans le sens des aiguilles d'une montre en utilisant:

ctx.rotate(Math.PI / 3);

Toutefois, comme notre hexagone et nos lignes utilisent les anciennes coordonnées (0,0) comme point de départ, une fois la rotation terminée, nous devons effectuer une translation précédente avant de dessiner. Au total, nous obtenons donc:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

Si vous placez la translation et la rotation ci-dessus avant le code de rendu, la carte pivotée s'affiche désormais:

Tuile hexagonale pivotée
Tuile hexagonale pivotée

Résumé

Ci-dessus, j'ai mis en évidence quelques-unes des fonctionnalités offertes par HTML5 lors de l'utilisation de la balise de canevas, y compris le rendu d'images, le dessin de courbes de Bézier et la rotation du canevas. L'utilisation de la balise de canevas HTML5 et des outils de dessin JavaScript pour Entanglement s'est révélée très agréable. J'ai hâte de voir les nombreux nouveaux jeux et applications créés par d'autres utilisateurs avec cette technologie ouverte et émergente.

Documentation de référence sur le code

Tous les exemples de code fournis ci-dessus sont combinés ci-dessous à titre de référence:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();