はじめに
昨年(2010 年)の春、HTML5 と関連テクノロジーのサポートが急速に拡大していることに興味を持ちました。当時、友人と私は 2 週間のゲーム開発コンペティションで互いに競い合い、プログラミングと開発スキルを磨き、お互いに提案し合ったゲームのアイデアを実現していました。そのため、私は自然に HTML5 要素をコンペティションのエントリに組み込み始め、その仕組みをより深く理解し、以前の HTML 仕様ではほぼ不可能だったことを実現できるようになりました。
HTML5 の多くの新機能の中で、キャンバス タグのサポートが強化されたことで、JavaScript を使用してインタラクティブなアートを実装する機会が生まれました。そこで、パズルゲーム(現在は Entanglement という名前)の実装に挑戦しました。すでに Settlers of Catan タイルの裏面を使用してプロトタイプを作成していたため、これを一種の設計図として使用しました。ウェブプレイ用に HTML5 キャンバスに六角形タイルを作成するには、六角形の描画、パスの描画、タイルの回転の 3 つの重要な部分があります。以下では、これらの各機能を現在の形に仕上げるために行った作業について詳しく説明します。
六角形の描画
元のバージョンの Entanglement では、いくつかのキャンバス描画メソッドを使用して六角形を描画していましたが、現在のゲームでは drawImage()
を使用して、スプライトシートからクリップされたテクスチャを描画しています。

画像を 1 つのファイルにまとめたので、この例では 10 回ではなく 1 回だけサーバーにリクエストを送信します。選択した六角形をキャンバスに描画するには、まずキャンバス、コンテキスト、画像のツールを用意する必要があります。
キャンバスを作成するには、HTML ドキュメントにキャンバス タグを追加するだけです。
<canvas id="myCanvas"></canvas>
スクリプトに読み込めるように ID を指定します。
var cvs = document.getElementById('myCanvas');
次に、描画を開始できるようにキャンバスの 2D コンテキストを取得する必要があります。
var ctx = cvs.getContext('2d');
最後に、画像が必要です。ウェブページと同じフォルダにある「tiles.png」という名前の画像を取得するには、次の操作を行います。
var img = new Image();
img.src = 'tiles.png';
3 つのコンポーネントが揃ったので、ctx.drawImage() を使用して、必要な単一の六角形をスプライトシートからキャンバスに描画できます。
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
この例では、最上段の左から 4 番目の六角形を使用しています。また、元のサイズのまま、左上のキャンバスに描画します。六角形の幅が 400 ピクセル、高さが 346 ピクセルの場合、全体は次のようになります。
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);
画像の一部がキャンバスにコピーされ、次のような結果になりました。

描画パス
キャンバスに六角形を描画したので、その上に線をいくつか描画します。まず、六角形タイルに関するジオメトリについて説明します。各辺に 2 つの線端があり、各線端が各辺の端から 1/4 の位置にあり、辺の 1/2 の位置で互いに離れているようにします。

きれいな曲線も必要なので、試行錯誤の結果、各エンドポイントの端から垂直線を引くと、正六角形の特定の角度の周りの各エンドポイントのペアの交点が、そのエンドポイントのベジェル コントロール ポイントとして適切であることがわかりました。

次に、エンドポイントとコントロール ポイントの両方を、キャンバス画像に対応するデカルト平面にマッピングします。これで、コードに戻る準備が整いました。シンプルにするため、まずは 1 行から始めます。まず、左上の端点から右下の端点にパスを描画します。先ほどの六角形の画像が 400x346 ピクセルの場合、上端のエンドポイントは幅 150 ピクセル、下端は 0 ピクセルになります。略記は(150, 0)です。コントロール ポイントは(150, 86)になります。下端のエンドポイントは(250、346)で、コントロール ポイントは(250、260)です。

座標が手に入ったので、描画を開始できます。最初は ctx.beginPath() から始めて、次のように最初のエンドポイントに移動します。
ctx.moveTo(pointX1,pointY1);
次に、ctx.bezierCurveTo() を使用して線自体を描画します。
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
線にきれいな境界線を付けるため、このパスを 2 回ストロークします。ストロークするたびに幅と色を変えます。色は ctx.strokeStyle プロパティを使用して設定し、幅は ctx.lineWidth を使用して設定します。最初の線を描画するコードは次のようになります。
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();
これで、最初の行が曲がりくねった六角形のタイルになりました。

他の 10 個のエンドポイントの座標と、対応するベジェ曲線の制御点を入力し、上記の手順を繰り返すと、次のようなタイルを作成できます。

キャンバスの回転
タイルを作成したら、ゲームでさまざまなパスを進めるできるようにタイルを回転できるようにします。キャンバスを使用してこれを実現するには、ctx.translate()
と ctx.rotate()
を使用します。タイルを中心で回転させるため、まずキャンバスの参照点を六角形タイルの中心に移動します。これには、以下を使用します。
ctx.translate(originX, originY);
ここで、originX は六角形タイル幅の半分、originY は高さの半分になります。
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
これで、新しい中心点を使用してタイルを回転できるようになりました。六角形には 6 つの辺があるため、Math.PI を 3 で除算した値の倍数で回転する必要があります。簡単な方法として、次のように時計回りに 1 回回します。
ctx.rotate(Math.PI / 3);
ただし、六角形と線は元の(0,0)座標を原点として使用しているため、回転が完了したら、描画する前に元の位置に戻す必要があります。合計すると、次のような式になります。
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
レンダリング コードの前に上記の移動と回転を配置すると、回転したタイルがレンダリングされます。

概要
上記では、画像のレンダリング、ベジェ曲線の描画、キャンバスの回転など、HTML5 がキャンバスタグを使用して提供する機能の一部について説明しました。HTML5 キャンバス タグとその JavaScript 描画ツールを Entanglement で使用するのは楽しい体験でした。このオープンで新しいテクノロジーを使って、他のユーザーが作成する新しいアプリケーションやゲームが数多く登場することを楽しみにしています。
コード リファレンス
上記のすべてのコードサンプルを、参考として以下にまとめます。
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();