本文へスキップ

はじめる ​

spryte は、どんな canvas にもイミディエイトモードで 2D グラフィックスを描きます。毎フレーム begin() を呼び、描きたい順に描画して、end() を呼ぶだけです。WebGL2 を使い、必要なら自動で Canvas 2D にフォールバックします。このページでは、単体で使うためのセットアップをすべて説明します。

インストール ​

bash
npm install spryte

レンダラーを作成する ​

ts
import { createRenderer } from "spryte";

const canvas = document.querySelector("canvas")!;
const draw = createRenderer(canvas, { smooth: false }); // ドット絵なら smooth: false
// WebGL2 なら即座に完了。それ以外はフォールバックを待つ
await draw.ready;
console.log(draw.kind); // "webgl2"、フォールバック時は "2d"

createRenderer(canvas, options?) のオプションは3つです。

オプションデフォルト
backend"auto""auto" は WebGL2 を使い、Canvas 2D にフォールバックします。"webgl2" は WebGL2 が使えないと例外を投げます。"2d" は常に Canvas 2D を使います。
smoothtrueデフォルトの画像フィルタリング。ドット絵では false にします。スプライトごとに上書きできます。
transparentfalse背景が透ける canvas。"#0000" のようなアルファ付きの色でクリアすると、ページや別の canvas の上に重ねて描けます。不透明のほうがコンポジタの負荷は少しだけ軽くなります。

draw.ready とフォールバック ​

Canvas 2D バックエンドは別チャンクなので、WebGL2 対応ブラウザがダウンロードすることはありません。フォールバック時は、そのチャンクが届くまで(数ミリ秒)フレームには何も描かれません。await draw.ready でその到着を待てます。WebGL2 では即座に解決し、フォールバックの読み込みに失敗すると reject されます。

canvas のサイズを合わせる ​

描画は自分で決めた論理単位で行い、canvas の実サイズ(バッキングサイズ)は自分で管理します。canvas.width や canvas.height を変えたら、draw.resize() を呼んでください。

ts
function resize(): number {
  const dpr = devicePixelRatio || 1;
  const w = Math.round(canvas.clientWidth * dpr);
  const h = Math.round(canvas.clientHeight * dpr);
  if (canvas.width !== w || canvas.height !== h) {
    canvas.width = w;
    canvas.height = h;
    draw.resize(w, h);
  }
  return dpr;
}

フレームループ ​

ts
function frame(time: number) {
  const dpr = resize();
  const w = canvas.clientWidth;
  const h = canvas.clientHeight;
  draw.begin("#111", w, h, dpr); // クリア色、論理サイズの幅と高さ、1単位あたりのバッキングピクセル数
  draw.rect(10, 10, 40, 20, "#cffb2d");
  draw.sprite(hero, w / 2, h / 2, { rotation: time / 1000 });
  draw.text(`${draw.kind}: ${draw.drawCalls} draw calls`, 8, 40, { font: "14px sans-serif" });
  draw.end();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

begin(clear, width, height, scale) は canvas をクリアし、論理サイズを設定します。scale は論理単位あたりのバッキングピクセル数で、たいていは canvas.width / width(CSS の拡大率 × devicePixelRatio)です。テキストをラスタライズする解像度もこれで決まります。begin() はトランスフォームスタック、クリップ、グローバルアルファ、ブレンドモード、シェーダーをリセットするので、毎フレームまっさらな状態から始まります。end() でフレームを送信します。

固定解像度にしたいなら、論理単位 320×180 で描き、scale = canvas.width / 320 とします。その設定はドット絵で説明しています。

描く前に画像を読み込む ​

spryte が描けるのは HTMLImageElement、HTMLCanvasElement、OffscreenCanvas、ImageBitmap、そして spryte 自身のレンダーターゲットです。どれも最初に描いたときに GPU へアップロードされ、オブジェクトごとにキャッシュされます。先にピクセルの読み込みを済ませておいてください。

ts
async function load(url: string): Promise<HTMLImageElement> {
  const img = new Image();
  img.src = url;
  await img.decode();
  return img;
}

const hero = await load("/hero.png");
// または: const hero = await createImageBitmap(await (await fetch("/hero.png")).blob());

描画元の canvas のピクセルを変えたら、draw.invalidate(thatCanvas) を呼んで spryte に再アップロードさせます。spryte が画像を所有することはありません。テクスチャは invalidate() か dispose() で破棄されます。

コンテキスト消失 ​

スマートフォンでは、バックグラウンドのタブからブラウザが GPU を取り上げることがあります。spryte はこれに対応しています。draw.contextLost が true の間は描画をスキップします。コンテキストが復元されると GPU オブジェクトが作り直され、画像は次に使ったときに再アップロードされるので、何もする必要はありません。戻ってこないのはレンダーターゲットの中身だけです。毎フレーム描き直すか、contextLost が false に戻ったときに描き直してください。

後片付け ​

ts
draw.dispose(); // すべての GPU オブジェクトとリスナーを解放

dispose 済みの Draw を使うと例外を投げます。2回 dispose しても何も起きません。

診断 ​

  • draw.kind:"webgl2" または "2d"。
  • draw.quads:このフレームで送信したクアッドの数。
  • draw.drawCalls:このフレームの GPU ドローコール数(WebGL2)。Canvas 2D ではクアッドごとに1回です。
  • draw.raw:{ kind: "webgl2", gl } または { kind: "2d", ctx }。生の呼び出しと draw の呼び出しを混ぜるなら、変更した状態は元に戻してください。

次へ:スプライト