事例紹介 - HTML5 Canvas を使用して Entangled を利用する

はじめに

2010 年の春、私は HTML5 と関連技術のサポートが急増していることに関心を寄せました。当時、友人と私は 2 週間にわたるゲーム開発コンテストで、プログラミングと開発のスキルに磨きをかけ、常に投げかけられていたゲームのアイデアを具現化するために互いに挑戦していました。そのため、当然のことながら、コンテストのエントリに HTML5 要素を組み入れるようになりました。その仕組みを理解して、以前の HTML 仕様ではほとんど不可能だったことができるようにしました。

HTML5 には数多くの新機能が加わりましたが、canvas タグのサポートが拡大したことは、JavaScript を使用してインタラクティブ アートを実装する絶好の機会となり、今では Entanglement と呼ばれるパズルゲームを実装してみることになりました。私はすでにカタンタイルの開拓者の裏面を使ってプロトタイプを作成しているので、これを一種の設計図として使用し、ウェブプレイ用の HTML5 キャンバス上で六角形タイルを作成するには、六角形の描画、パスの描画、タイルの回転という 3 つの重要な部分があります。以下では、現在の形式でそれぞれの手順を詳しく説明します。

六角形の描画

Entanglement のオリジナル バージョンでは、いくつかのキャンバス描画方法を使用して六角形を描画していましたが、現在のゲーム形式では drawImage() を使用して、スプライト シートからクリップしたテクスチャを描画しています。

タイルのスプライト シート
タイルのスプライト シート

画像を 1 つのファイルにまとめることで、サーバーへのリクエストは 10 件だけになりました。この場合は 10 件です。選択した六角形をキャンバスに描画するには、まずキャンバス、コンテキスト、画像などのツールを収集する必要があります。

キャンバスを作成するために必要なのは、次のような html ドキュメント内の canvas タグだけです。

<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 つのコンポーネントが揃いました。次に、resourcemanager.drawImage() を使用して、スプライト シートからキャンバスに描画する 1 つの六角形を描画します。

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)です。

第 1 ベジェ曲線の座標
第 1 ベジェ曲線の座標

座標が手に入ったので、描画を開始する準備が整いました。まずは resourcemanager.beginPath() で開始し、次を使用して最初のエンドポイントに移動します。

ctx.moveTo(pointX1,pointY1);

次に、次のように resourcemanager.bezierCurveTo() を使用して線自体を描画します。

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

線に適切な境界線を引くため、このパスに毎回異なる幅と色を使用して 2 度ストロークします。色は ="_blankStyle プロパティを使用して設定され、幅は resourcemanager.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();

これで、1 行目が曲がりくねった六角形のタイルができました。

六角タイル上の単線
六角形タイル上の単線

他の 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 の canvas タグと Entanglement 用の JavaScript 描画ツールは楽しいものでした。このオープンかつ新興のテクノロジーを活用して、さまざまな新しいアプリやゲームが生まれることを期待しています。

コード リファレンス

参考までに、上記のすべてのコードサンプルを以下に示します。

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();