ソースコード解説・目次

CHAPTER 04 · 全23項目

Canvas の座標と描画

内部座標と表示サイズの違い、文字描画、重ねる順序を確認します。

主な参照先:W / H / canvasX / dotScale / drawTextOn / draw

内部は480 × 640、見た目の大きさはCSS

game.html · L87–89ソースを見る ↗
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という寸法は変わりません。ゲーム中の速度や当たり判定は、この内部座標で計算します。

ポインターの座標を変換する

game.html · L1287–1290ソースを見る ↗
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方向の変換はありません。

文字も図形として描く

game.html · L288–324ソースを見る ↗
// ドット 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() は座標変換などの描画設定を保存・復元するためのもので、ゲームの状態や画像そのものを保存する命令ではありません。