CHAPTER 04 · 全23項目
Canvas の座標と描画
内部座標と表示サイズの違い、文字描画、重ねる順序を確認します。
主な参照先:W / H / canvasX / dotScale / drawTextOn / draw
内部は480 × 640、見た目の大きさはCSS
const W = 480, H = 640;
const cvs = document.getElementById("game");
const ctx = cvs.getContext("2d", { alpha: false }); // 毎フレーム全面を塗るので不透明にし、ページとの合成コストを下げるCanvas の内部座標は左上が (0, 0)、右へ進むと x、下へ進むと y が増えます。CSS で画面を縮めても内部の480 × 640という寸法は変わりません。ゲーム中の速度や当たり判定は、この内部座標で計算します。
ポインターの座標を変換する
function canvasX(e) {
const r = cvs.getBoundingClientRect();
return ((e.clientX - r.left) * W) / r.width;
}イベントの clientX から Canvas の表示位置を引き、表示幅に対する比率を求めて内部幅 W を掛けます。例えば表示幅が240 pxなら、左端から120 pxの位置は内部座標240になります。ポインターの移動とクリックのどちらでもこの関数で自機の目標位置 mouseX を求めます。ゲームで使うのは横方向だけなので、y方向の変換はありません。
文字も図形として描く
// ドット 1 個を何 px で描くか。size 14 以上で 2 倍、24 以上で 3 倍…と整数倍にして、点の大きさをそろえる。
function dotScale(size) { return Math.max(1, Math.floor((size + 6) / 10)); }
// ドット文字にない文字(日本語など)を含む文は、行の中で大きさがそろうよう全体をシステムのフォントで描く。
// 英字の字形が整った等幅フォントを OS ごとに選ぶ(CSS の --mono と同じ並び)。
const SYSTEM_FONT = 'ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Mono", "JetBrains Mono", "DejaVu Sans Mono", monospace';
// 文字を描く。英数字と記号だけの文はドット文字で描く。o.spacing は文字の間に足す間隔(px)。
function drawTextOn(g, s, x, y, o = {}) {
s = String(s);
const size = o.size || 13, align = o.align || "center";
g.fillStyle = o.color || "#cfd8ff";
let n = 0;
for (const ch of s) {
if (!GLYPHS.has(ch)) {
g.font = `${o.weight || 700} ${size}px ${SYSTEM_FONT}`;
g.textAlign = align;
g.textBaseline = "alphabetic";
g.fillText(s, x, y);
return;
}
n++;
}
// 揃え方から左端を決める。最後の文字の右の空き 1 列は幅に含めない。
// 座標は整数にそろえ、点がにじまないようにする。
const k = dotScale(size), gap = o.spacing || 0, step = GLYPH_ADVANCE * k + gap;
const width = n * step - k - gap;
let cx = Math.round(align === "left" ? x : align === "right" ? x - width : x - width / 2);
const by = Math.round(y);
// 長方形を 1 本のパスにまとめて一度に塗る(光彩も文全体に 1 回だけかかる)。
g.beginPath();
for (const ch of s) {
for (const [gx, gy, gw] of GLYPHS.get(ch)) g.rect(cx + gx * k, by + (gy - GLYPH_H) * k, gw * k, k);
cx += step;
}
g.fill();
}Canvas に描いた文字は DOM の文章とは別です。このゲームでは英数字と記号を、文字表から作ったドット文字(「05 文字列からドット絵を作る」)の長方形として描きます。文字の大きさ size から dotScale() でドット1個の大きさを整数倍で決め、1文字6ドットぶんずつ右へ進めます。中央揃えや右揃えのために先に文全体の幅を計算し、左端と基準線の座標を整数にそろえるので、点がにじみません。長方形はすべて1本のパスに加えてから fill() で一度に塗ります。タイトルロゴのように光彩 (shadowBlur) を付けるときも、光彩が文全体に1回だけかかります。
ドット文字にない文字、つまり日本語を含む文は、全体をシステムのフォント SYSTEM_FONT で fillText() により描きます。英字だけドット文字にすると、日本語より小さく見えて行の大きさがそろわないためです。SYSTEM_FONT は CSS と同じく、英字の字形が整った等幅フォントを OS ごとに選ぶ並びです。drawText() はこの関数へ本画面の ctx を渡す薄い窓口になっています。
後で描くものが手前になる
draw() は背景、ゲーム内の物体、HUD、画面中央の案内の順に重ねます。いちばん下の星雲の画像は不透明で画面全体を覆うので、前のフレームの絵はこれで塗り隠されます。画面を揺らすときだけは画像がずれて端に隙間ができるため、先に下地の色で全面を塗ります。ctx.save() と ctx.restore() は座標変換などの描画設定を保存・復元するためのもので、ゲームの状態や画像そのものを保存する命令ではありません。