Einführung
Im vergangenen Frühjahr (2010) habe ich mich für die schnell wachsende Unterstützung von HTML5 und ähnlichen Technologien interessiert. Damals haben mein Freund und ich uns in zweiwöchigen Game-Development-Wettbewerben herausgefordert, um unsere Programmier- und Entwicklungskompetenzen zu verbessern und die Spielideen zu verwirklichen, die wir uns ständig gegenseitig vorstellten. Also begann ich, HTML5-Elemente in meine Wettbewerbsbeiträge einzubauen, um die Funktionsweise besser zu verstehen und Dinge zu tun, die mit früheren HTML-Spezifikationen fast unmöglich waren.
Von den vielen neuen Funktionen in HTML5 bot mir die zunehmende Unterstützung für das Canvas-Tag eine spannende Möglichkeit, interaktive Kunst mit JavaScript zu implementieren. Das führte dazu, dass ich ein Puzzlespiel implementierte, das jetzt Verstrickung heißt. Ich hatte bereits einen Prototyp mit der Rückseite der Siedler von Catan-Spielsteine erstellt. Als Art Blaupause gibt es drei wesentliche Teile, um die sechseckige Kachel auf dem HTML5-Canvas für das Webspiel zu gestalten: das Sechseck zeichnen, die Pfade zeichnen und die Kachel drehen. Im Folgenden wird ausführlich beschrieben, wie ich diese in ihrer aktuellen Form umgesetzt habe.
Sechseck zeichnen
In der ursprünglichen Version von „Entanglement“ habe ich mehrere Canvas-Zeichenmethoden verwendet, um das Sechseck zu zeichnen. In der aktuellen Version des Spiels werden mit drawImage()
Texturen gezeichnet, die aus einem Sprite Sheet zugeschnitten wurden.

Ich habe die Bilder in einer einzigen Datei kombiniert, sodass nur eine Anfrage an den Server gesendet wird, anstatt in diesem Fall zehn. Um ein ausgewähltes Sechseck auf dem Canvas zu zeichnen, müssen wir zuerst unsere Tools zusammenstellen: Canvas, Kontext und Bild.
Um ein Canvas zu erstellen, benötigen wir nur das Canvas-Tag in unserem HTML-Dokument:
<canvas id="myCanvas"></canvas>
Ich gebe ihm eine ID, damit wir es in unser Script einfügen können:
var cvs = document.getElementById('myCanvas');
Zweitens müssen wir den 2D-Kontext für den Canvas abrufen, damit wir mit dem Zeichnen beginnen können:
var ctx = cvs.getContext('2d');
Als Letztes benötigen wir das Bild. Wenn sie im selben Ordner wie unsere Webseite „tiles.png“ heißt, können wir sie so abrufen:
var img = new Image();
img.src = 'tiles.png';
Nachdem wir die drei Komponenten haben, können wir mit ctx.drawImage() das gewünschte einzelne Sechseck aus dem Sprite-Sheet auf dem Canvas zeichnen:
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
In diesem Fall verwenden wir das vierte Sechseck von links in der obersten Zeile. Außerdem zeichnen wir sie oben links auf dem Canvas und behalten dabei die gleiche Größe wie das Original bei. Angenommen, die Sechsecke sind 400 Pixel breit und 346 Pixel hoch, sieht das Ganze in etwa so aus:
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);
Wir haben einen Teil des Bilds in den Canvas kopiert. Das Ergebnis sieht so aus:

Pfade zeichnen
Nachdem wir das Sechseck auf dem Canvas gezeichnet haben, möchten wir einige Linien darauf zeichnen. Sehen wir uns zuerst die Geometrie der Sechseckkachel an. Wir möchten zwei Linienenden pro Seite, die jeweils 1/4 vom Ende entlang der Kante und 1/2 der Kante voneinander entfernt sind, so:

Wir möchten auch eine schöne Kurve haben. Nach ein wenig Ausprobieren habe ich festgestellt, dass ich, wenn ich von jedem Endpunkt aus eine senkrechte Linie zeichne, die Schnittpunkte der einzelnen Endpunktpaare um einen bestimmten Winkel des Sechsecks einen schönen Bezier-Kontrollpunkt für die jeweiligen Endpunkte ergeben:

Jetzt ordnen wir sowohl die Endpunkte als auch die Kontrollpunkte einer kartesischen Ebene zu, die unserem Canvas-Bild entspricht. Jetzt können wir wieder zum Code zurückkehren. Der Einfachheit halber beginnen wir mit einer Zeile. Zeichnen Sie zuerst einen Pfad vom oberen linken Endpunkt zum unteren rechten Endpunkt. Bei unserem früheren Sechseckbild mit 400 × 346 Pixeln hat der obere Endpunkt eine Breite von 150 Pixeln und eine Höhe von 0 Pixeln, also (150, 0). Der Kontrollpunkt ist (150, 86). Der Endpunkt der unteren Kante ist (250, 346) mit einem Steuerpunkt von (250, 260):

Nachdem wir die Koordinaten haben, können wir mit dem Zeichnen beginnen. Wir beginnen mit ctx.beginPath() und gehen dann mit folgendem Code zum ersten Endpunkt:
ctx.moveTo(pointX1,pointY1);
Die Linie selbst können wir dann mit ctx.bezierCurveTo() so zeichnen:
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
Da wir der Linie einen schönen Rahmen geben möchten, zeichnen wir diesen Pfad zweimal, wobei wir jedes Mal eine andere Breite und Farbe verwenden. Die Farbe wird mit der Eigenschaft „ctx.strokeStyle“ und die Breite mit „ctx.lineWidth“ festgelegt. Insgesamt sieht das Zeichnen der ersten Linie so aus:
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();
Wir haben jetzt eine sechseckige Kachel mit der ersten Linie, die sich durch sie schlängelt:

Wenn wir die Koordinaten für die anderen 10 Endpunkte sowie die entsprechenden Bezierkurven-Kontrollpunkte eingeben, können wir die obigen Schritte wiederholen und eine Kachel erstellen, die in etwa so aussieht:

Canvas drehen
Sobald wir unsere Kachel haben, möchten wir sie drehen können, damit im Spiel verschiedene Pfade möglich sind. Dazu verwenden wir ctx.translate()
und ctx.rotate()
. Wir möchten, dass sich die Kachel um ihre Mitte dreht. Deshalb verschieben wir als ersten Schritt den Canvas-Referenzpunkt in die Mitte der sechseckigen Kachel.
Dazu verwenden wir:
ctx.translate(originX, originY);
Dabei ist „originX“ die Hälfte der Breite der sechseckigen Kachel und „originY“ die Hälfte der Höhe. Das ergibt:
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
Jetzt können wir die Kachel mit dem neuen Mittelpunkt drehen. Da ein Sechseck sechs Seiten hat, sollten wir es um ein Vielfaches von Math.PI geteilt durch 3 drehen. Wir halten es einfach und führen eine einzige Drehung im Uhrzeigersinn aus:
ctx.rotate(Math.PI / 3);
Da unser Sechseck und unsere Linien jedoch die alten Koordinaten (0,0) als Ursprung verwenden, müssen wir nach der Drehung wieder zurückverschieben, bevor wir zeichnen. Insgesamt haben wir jetzt:
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
Wenn wir die obigen Verschiebungen und Drehungen vor unseren Rendering-Code setzen, wird jetzt die gedrehte Kachel gerendert:

Zusammenfassung
Oben habe ich einige der Funktionen hervorgehoben, die HTML5 mit dem Canvas-Tag bietet, darunter das Rendern von Bildern, das Zeichnen von Bézierkurven und das Drehen des Canvas. Die Verwendung des HTML5-Canvas-Tags und der JavaScript-Zeichentools für Entanglement hat sich als angenehm erwiesen. Ich freue mich auf die vielen neuen Anwendungen und Spiele, die andere mit dieser offenen und neuen Technologie entwickeln werden.
Codereferenz
Alle oben genannten Codebeispiele sind unten als Referenz aufgeführt:
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();