本文へスキップ

ドット絵 ​

ドット絵では、スムージングをオフにし、小さな固定の論理解像度で描いて、整数倍で拡大します。spryte は回転していないスプライトを整数ピクセルにスナップするので、動いても輪郭がちらつきません。

ts
import { createRenderer } from "spryte";

const W = 320;
const H = 180;
const canvas = document.querySelector("canvas")!;
const draw = createRenderer(canvas, { smooth: false });

function fit(): number {
  const dpr = devicePixelRatio || 1;
  const fitScale = Math.min((innerWidth * dpr) / W, (innerHeight * dpr) / H);
  const scale = Math.max(1, Math.floor(fitScale)); // ゲームの1ピクセルあたりの整数バッキングピクセル数
  if (canvas.width !== W * scale || canvas.height !== H * scale) {
    canvas.width = W * scale;
    canvas.height = H * scale;
    canvas.style.width = `${(W * scale) / dpr}px`;
    canvas.style.height = `${(H * scale) / dpr}px`;
    draw.resize(canvas.width, canvas.height);
  }
  return scale;
}

function frame() {
  const scale = fit();
  draw.begin("#1a1c2c", W, H, scale);
  draw.sprite(hero, player.x, player.y);
  draw.end();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

smooth: false ​

createRenderer(canvas, { smooth: false }) で最近傍フィルタリングがデフォルトになり、拡大してもテクセルは輪郭のはっきりしたブロックのままです。デフォルトは後から draw.smooth で変えられ、スプライトごとに上書きもできます。

ts
draw.sprite(hero, x, y);                                     // デフォルトに従ってくっきり
draw.sprite(glowTarget, 0, 0, { origin: [0, 0], smooth: true }); // わざと柔らかく

柔らかく拡大したグローやぼかした背景レイヤーを、くっきりしたスプライトと並べるのはよくある組み合わせです。

論理サイズとバッキングサイズ ​

サイズは2種類あります。

  • 論理サイズ:描画に使う単位で、begin() に渡します。ここでは 320×180 です。位置、スプライトのサイズ、テキストはすべてこの単位を使います。
  • バッキングサイズ:canvas.width と canvas.height、つまり実際のピクセルです。自分で設定し、draw.resize() で spryte に伝えます。

begin() の第4引数は、論理単位あたりのバッキングピクセル数 canvas.width / W です。整数倍率なら、ゲームのどのピクセルも同じ大きさの画面ピクセルの正方形を占めます。小数倍率だと、ゲームのピクセルの一部がほかより画面1ピクセル分広くなり、スクロールするドット絵では目に見えてしまいます。

上の例では倍率に devicePixelRatio を含め、canvas の CSS サイズはそれで割り戻して指定しています。こうすると整数倍率がデバイスピクセル、つまり実際に画面に出るピクセルの単位になります。残りの領域は CSS で canvas をレターボックス表示(暗い背景の中央に配置)して埋めてください。

ピクセルスナップ ​

スムージングがオフのとき、draw.sprite() は回転していないスプライトを整数ピクセル上に配置します。そうしないと、小数の位置で最近傍サンプリングをしたとき、フレームごとに輪郭が1ピクセル増えたり減ったりして、ちらつきに見えます。物理演算を滑らかにするために位置は浮動小数のまま持ち、丸めは描画時に spryte に任せられます。

  • スナップは、スプライトの実際の smooth が false で、スプライトが回転していない(自身の rotation でも現在のトランスフォームでも)ときに適用されます。
  • draw.sprites() も回転していない各コピーを同じようにスナップするので、一括描画のパーティクルは sprite() のループとピクセル単位で一致します。
  • ゲームのピクセル単位でしか動かしたくないなら、描く前に自分で位置を丸めてください。

くっきりしたテキスト ​

CSS のテキストは begin() に渡した scale でラスタライズされるので、320×180 から引き伸ばしてぼやけるのではなく、バッキング解像度でシャープに表示されます。ドット絵の上に載せる UI テキストに向いています。

アートと揃えたテキストには、グリフシートから作った BitmapFont を使います。グリフはスムージングなしで整数ピクセル上に描かれるので、どの整数倍率でもくっきりしたままです。

ts
import { BitmapFont } from "spryte";

const font = new BitmapFont({ frames: fontFrames, chars: BitmapFont.ASCII });
draw.font = font; // text() と measure() のデフォルト
draw.text("LIVES 3", 4, 4, { color: "#f4f4f4" });

グリッドから fontFrames を作る方法と BitmapFont の全オプションは、図形とテキストにあります。

回転と拡大縮小 ​

回転したスプライトはスナップされず、回転したスプライトに最近傍フィルタリングを使うと輪郭がギザギザになります。低解像度で回転するドット絵としては、それが想定どおりの見た目です。特定のスプライトだけ滑らかにしたいなら、そのスプライトに smooth: true を渡してください。テクセルの大きさを揃えるには、スプライトを整数倍(scaleX: 2)で拡大します。

次へ:PixiJS からの移行