Canvas 2D API でピクセルアートタイルを「手書き」する実装テクニック

Canvas 2D API でピクセルアートタイルを「手書き」する実装テクニック 1. 概要 Web ゲーム開発、特にローグライクやタクティカル RPG を開発する際、避けて通れないのが「マップの描画」だ。通常、これらは「タイルセット」と呼ばれる画像ファイルを読み込んで描画するが、開発の初期段階や、あえて外部アセットに頼りたくない場合、Canvas 2D API を使ってプログラムで直接タイルを描画する「手書き(プログラマティック描画)」の手法が非常に強力な武器になる。 本記事では、HTML5 Canvas の fillRect や beginPath などの基本命令のみを使い、擬似的なピクセルアート風のタイルを描画するテクニックを解説する。画像を用意する手間を省きつつ、動的に色や形状を変更できる柔軟な描画システムを構築しよう。 2. タイル描画の基本構造 まずは、どのようなタイルでも共通して利用できる描画の入り口を作る。ピクセル座標ではなく、タイル座標(x, y)とタイルサイズ(tileSize)を受け取る設計にすることで、グリッドベースのシステムと統合しやすくなる。 /** * タイルを描画するメイン関数 * @param {CanvasRenderingContext2D} ctx - Canvasコンテキスト * @param {string} tileType - タイルの種類 ('wall', 'floor', 'grass', etc.) * @param {number} x - タイルのX座標(グリッド単位) * @param {number} y - タイルのY座標(グリッド単位) * @param {number} tileSize - 1タイルのピクセルサイズ * @param {string} fieldType - フィールドの種類 ('meadow', 'forest', 'mountain') */ function drawTile(ctx, tileType, x, y, tileSize, fieldType = 'meadow') { const px = x * tileSize; const py = y * tileSize; ctx.save(); ctx.translate(px, py); switch (tileType) { case 'floor': drawFloor(ctx, tileSize, fieldType); break; case 'wall': drawWall(ctx, tileSize, fieldType); break; case 'object_grass': drawFloor(ctx, tileSize, fieldType); drawGrass(ctx, tileSize); break; case 'object_tree': drawFloor(ctx, tileSize, fieldType); drawTree(ctx, tileSize); break; case 'stairs_down': drawFloor(ctx, tileSize, fieldType); drawStairs(ctx, tileSize, false); break; default: ctx.fillStyle = '#333'; ctx.fillRect(0, 0, tileSize, tileSize); } ctx.restore(); } この設計のポイントは、ctx.translate を使ってタイルの左上を原点 (0, 0) に固定することだ。これにより、各タイルの描画ロジック内で座標計算を簡略化できる。 ...

March 31, 2026 · 4 min

React + Canvas 2D API で作るターン制ローグライク:論理と描画を切り離す設計

React + Canvas 2D API で作るターン制ローグライク:論理と描画を切り離す設計 React でゲームを作る際、多くの開発者が最初に直面するのが「DOM で描画するか、Canvas で描画するか」という選択です。特に数千枚のタイルや多数のユニットが登場するローグライクゲームでは、DOM 要素の管理はすぐにパフォーマンスの限界に達します。 本稿では、React の強力な状態管理(useReducer)と、Canvas 2D API の命令的な描画を組み合わせ、滑らかなアニメーションを実現しつつ堅牢なゲームロジックを維持する設計手法について解説します。 1. 概要:なぜ React と Canvas を組み合わせるのか React は「宣言的」な UI 構築に長けていますが、毎秒 60 回の頻度で数千の DOM 要素を更新するような動的な描画には向いていません。一方で、Canvas は「命令的」であり、ピクセル単位での高速な描画が可能ですが、状態と描画の同期を自分で行う必要があります。 この二つの「いいとこ取り」をするのが、「ロジックは React(useReducer)で、描画は Canvas で」 という役割分担です。 本記事で構築するアーキテクチャ Core Logic (useReducer): ゲームの「真実の状態(State of Truth)」を管理。ターン単位で離散的に変化する座標などを扱う。 Render Loop (requestAnimationFrame): Canvas 上で毎フレーム実行される描画処理。 Interpolation (補完): 離散的な論理座標を、滑らかな描画座標へと変換するローカル状態管理。 2. 設計判断:論理座標と描画座標の分離 ローグライクゲームは基本的に「ターン制」です。プレイヤーが右に移動したとき、内部データ(Core State)では x: 10 から x: 11 へと一瞬で書き換わります。しかし、これをそのまま描画すると、キャラクターがワープしたように見えてしまいます。 滑らかな移動(アニメーション)を実現するためには、以下の二種類の状態を明確に分ける必要があります。 なぜ分離が必要か 種類 管理場所 特徴 役割 論理座標 (Logical Position) useReducer (Global) 整数(タイル単位)。 当たり判定、AI、クエスト進行など。 描画座標 (Visual Position) Canvas 内の Actor クラス (Local) 小数点を含むピクセル単位。 滑らかな移動、揺れ、エフェクト。 判断理由: Core State にアニメーションの「途中経過(x: 10.2 など)」を持たせてしまうと、ゲームロジックが描画の都合に汚染されます。例えば、「まだ移動アニメーション中だから攻撃はできない」といった判定をロジック層で書く必要が出てき、コードが複雑化します。ロジック層は常に「今は(論理的に)どこにいるか」だけを知っていれば良いのです。 ...

March 28, 2026 · 4 min