mcpField
P
pixijs-rendering

pixijs-rendering

github.com/gamedev-skills開発5.4k6.0k 回インストール1 日前に更新
リポジトリ

pixijs-rendering について

PixiJS 8.21で開発する人向けのレンダリングスキルです。非同期のアプリ初期化、アセット読み込み、シーングラフ、tickerによるアニメーション、ポインター入力、レンダーグループを扱います。

主な機能

  1. 01Applicationの非同期初期化
  2. 02Assetsによるアセット読み込み
  3. 03Containerによるシーングラフ構成
  4. 04tickerによるアニメーション
  5. 05ポインターイベントとレンダーグループの設定

活用シーン

  1. 01PixiJS v8アプリの起動とキャンバス表示
  2. 02テクスチャの読み込みとシーン構築
  3. 03フレームレートに依存しないアニメーション
  4. 04マウス・タッチ・ドラッグ操作の処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill pixijs-rendering

インストール後、Claude Code で/pixijs-renderingを入力して読み込まれたか確認します。

同梱ファイルreferences/assets-and-display.md

詳細

PixiJS 8.21 のレンダリング

PixiJS 8.21 アプリケーションをセットアップして構成します。非同期の Application、Assets によるアセットの読み込み、Container/Sprite のシーングラフ、ticker ループ、ポインターイベント、レンダーグループを扱います。8.21 の API(非同期の init、統合された Assets、eventMode)に固定しています。

使用する場面

  • PixiJS v8 プロジェクトを開始するとき、空白のキャンバスを修正するとき、ディスプレイリストを構成するとき、テクスチャを読み込むとき、ticker でアニメーションするとき、またはポインター入力を処理するときに使用します。
  • package.json が pixi.js(v8)に依存し、コードで import { Application } from 'pixi.js' を行う場合に使用します。

使用しない場面: Phaser のシーン/ローダーモデル → phaser-core。3D シーン → threejs-scene-setup。PixiJS v7 以前のコード(同期的な new Application({...})、Loader、beginFill/endFill)では、まず v8 への移行が必要です。このスキルは v8 のみを対象としています。(interactive = true は v8 でも eventMode = 'static' のエイリアスとして引き続き動作しますが、明示的な eventMode を推奨します。)

基本的なワークフロー

  1. Application を作成して await します。 v8 では new Application() は空の状態です。設定は await app.init({...}) で行います。app.view ではなく app.canvas を DOM に追加します。バンドラーを使う場合は、トップレベルの await を非同期関数で囲みます。
  2. Assets でアセットを読み込みます。 await Assets.load(url) は Texture を返します。多数のアセットを扱う場合は、マニフェスト/バンドルを登録して名前で読み込みます。v7 の Loader はありません。
  3. シーングラフを構築します。 すべてのオブジェクトは(Container である)app.stage から派生します。関連するオブジェクトを Container にまとめます。子のトランスフォームは親を基準とします。描画順は挿入順です(後に追加したものが上になります)。
  4. ticker でアニメーションします。 app.ticker.add((ticker) => {...}) を使います。動きをフレームレートに依存させないよう、ticker.deltaTime(フレーム数、60 fps で約 1)または ticker.deltaMS(ミリ秒)でスケーリングします。
  5. オブジェクトごとにイベントを有効にします。 eventMode = 'static'(または 'dynamic')を設定してから、obj.on('pointerdown', ...) を使います。フェデレーテッドポインターイベントはマウス/タッチ/ペンに対応します。
  6. 大きな静的サブツリーをレンダーグループに昇格します。 isRenderGroup: true を設定すると、GPU がトランスフォームをキャッシュします。変更前後でプロファイルを取得し、画面上のピクセルを確認します。

パターン

1. 非同期の Application 起動(v8 のエントリーポイント)
import { Application, Assets, Sprite } from 'pixi.js';

(async () => {
  // v8: construct empty, then await init(). Config does NOT go in the constructor.
  const app = new Application();
  await app.init({
    background: '#1099bb',
    resizeTo: window,        // track the window size
    antialias: true,
    // preference: 'webgpu',  // hint only; default order tries 'webgl' first.
    //                        // Pixi falls back if the backend is unavailable —
    //                        // branch on app.renderer.name, don't assume it took.
  });

  document.body.appendChild(app.canvas); // v8 uses app.canvas, not app.view

  const texture = await Assets.load('https://pixijs.com/assets/bunny.png');
  const bunny = new Sprite(texture);
  bunny.anchor.set(0.5);
  bunny.position.set(app.screen.width / 2, app.screen.height / 2);
  app.stage.addChild(bunny);
})();
2. 相対トランスフォームのシーングラフに Container を使用する
import { Container, Sprite } from 'pixi.js';

const world = new Container();
app.stage.addChild(world);

// Children are positioned relative to `world`; move/scale/rotate the whole group
// by transforming the parent.
for (let i = 0; i < 10; i++) {
  const coin = new Sprite(coinTexture);
  coin.x = i * 40;
  world.addChild(coin);
}
world.position.set(100, 100);
world.scale.set(2);            // every coin scales with the container
3. ticker ループ(フレームレートに依存しない)
let elapsed = 0;
app.ticker.add((ticker) => {
  // deltaTime ≈ 1 at 60fps; deltaMS is milliseconds since last frame.
  elapsed += ticker.deltaMS;
  bunny.rotation += 0.05 * ticker.deltaTime;          // smooth at any frame rate
  bunny.y = app.screen.height / 2 + Math.sin(elapsed / 500) * 50;
});
4. ポインターイベント(フェデレーテッド)
bunny.eventMode = 'static';   // 'static' = interactive, doesn't move on its own
bunny.cursor = 'pointer';     // hover cursor; `buttonMode` was removed in v8
bunny.on('pointerdown', (event) => {
  bunny.tint = 0xff0000;
  // event.global is the pointer position in stage space.
});
bunny.on('pointerover', () => bunny.scale.set(1.1));
bunny.on('pointerout',  () => bunny.scale.set(1.0));

ドラッグには pointermove ではなく globalpointermove が必要です。v8 では、pointermove はポインターがオブジェクト上にある間だけ発生するため、オブジェクトの端を越えてカーソルを追うドラッグでは更新が止まります。globalpointermove は移動のたびに発生します。

let dragging = false;
bunny.on('pointerdown', () => { dragging = true; });
bunny.on('pointerup', () => { dragging = false; });
bunny.on('pointerupoutside', () => { dragging = false; }); // release off the object
bunny.on('globalpointermove', (event) => {
  if (dragging) bunny.position.copyFrom(event.global);
});
5. 複数のアセットを名前で読み込む(バンドル)
import { Assets } from 'pixi.js';

await Assets.init({
  manifest: {
    bundles: [{
      name: 'level-1',
      assets: [
        { alias: 'hero',  src: 'assets/hero.png' },
        { alias: 'tiles', src: 'assets/tiles.png' },
      ],
    }],
  },
});

const bundle = await Assets.loadBundle('level-1'); // { hero: Texture, tiles: Texture }
const hero = new Sprite(bundle.hero);
6. 大きな静的レイヤーにレンダーグループを使用する
// A big, rarely-changing background subtree: let the GPU cache its transforms.
const background = new Container({ isRenderGroup: true });
app.stage.addChild(background);
// Add hundreds of static tiles to `background`. Moving `background` itself stays
// cheap; constantly re-adding/removing children negates the benefit.

よくある問題

  • キャンバスが空白/「app.stage is undefined」 → await app.init() を呼び出していないか、コンストラクターを設定しています。v8 のコンストラクターは空です。すべてのオプションは init() に渡します。init() の Promise が解決するまで、app.renderer/app.canvas/app.screen は undefined です。
  • app.view が undefined → v8 では app.canvas に名称が変更されました。
  • v7 のコードを移行する場合 → Loader/loader.add は Assets.load に置き換えます(Loader クラスは廃止されています)。同期的な new Application({...}) は、空のコンストラクターと非同期の init() に置き換えます(コンストラクターに渡したオプションは、エラーではなく非推奨の警告とともに無視されます)。beginFill()/endFill() は、シェイプを先に指定する .rect(...).fill(...) に置き換えます。interactive = true と app.view は非推奨のエイリアスとして引き続き動作します。
  • Ticker のコールバック引数は delta の数値ではなく Ticker です → app.ticker.add((dt) => { obj.rotation += dt; }) はコンパイルできますが、dt は Ticker オブジェクト全体なので、計算結果は NaN となり、アニメーションは動作しません。引数から ticker.deltaTime を読み取ってください。
  • トップレベルの await でビルドエラーが発生する(Vite ≤6.0.6) → 起動処理を (async () => { ... })() でラップします。
  • フレームレートによって速度が変わる → 移動量に ticker.deltaTime(60fps では約 1)を掛けるか、ticker.deltaMS でスケーリングします。60fps を前提にしないでください。deltaTime はミリ秒ではなく、単位のない乗数です。
  • クリックしても何も起きない → オブジェクトの eventMode が 'passive' のままです(v8 のデフォルトでは、自身はインタラクティブではありませんが、子要素はインタラクティブです)。'static' に設定してください(静止したカーソルの下で動くオブジェクトには 'dynamic' を設定します)。
  • オブジェクトの端でドラッグが止まる → v8 では、ポインターがオブジェクト上にある間だけ pointermove が発火します。ドラッグやグローバルな追跡には globalpointermove を使用してください。
  • Texture.from(url) が空または undefined のテクスチャを返す → v8 では、これは Assets のキャッシュだけを読み取ります。先に await Assets.load(url) を実行し、返された Texture を使用してください。
  • WebGPU の機能でエラーが発生する → preference はヒントです。バックエンドが利用できない場合、Pixi はフォールバックします(WebGL、次に Canvas)。バックエンド固有のコードを使用する前に、app.renderer.name で分岐してください。
  • ピクセルアートのテクスチャがぼやける → texture.source.scaleMode = 'nearest' を設定します(または読み込み時に渡します)。
  • メモリ使用量が増え続ける → removeChild では GPU メモリは解放されません。不要になったアセットには sprite.destroy() と Assets.unload(url) を呼び出してください。
  • 同じタブでアプリを破棄して再作成した後にちらつきや破損が起きる → app.destroy({ releaseGlobalResources: true }) で破棄してください。そうしないと、以前のアプリのバッチやテクスチャがプールに残り、新しいアプリに漏れ込みます。

参考資料

  • テクスチャ/アセットのパイプライン(スプライトシート/アトラス、Assets.add、バックグラウンドでの読み込み、アンロード)と、Graphics/Text/TilingSprite/ParticleContainer およびフィルターについては、references/assets-and-display.md を参照してください。

関連スキル

  • phaser-core — シーン、物理演算、入力機能を備えた 2D フレームワークです。
  • threejs-scene-setup — three.js を使ってブラウザーで 3D を扱います。
  • prototype-fast — プレイ可能なプロトタイプをすばやく作成します(PixiJS がよく取り上げられています)。

コメント