pixijs-rendering について
PixiJS 8.21で開発する人向けのレンダリングスキルです。非同期のアプリ初期化、アセット読み込み、シーングラフ、tickerによるアニメーション、ポインター入力、レンダーグループを扱います。
主な機能
- 01Applicationの非同期初期化
- 02Assetsによるアセット読み込み
- 03Containerによるシーングラフ構成
- 04tickerによるアニメーション
- 05ポインターイベントとレンダーグループの設定
活用シーン
- 01PixiJS v8アプリの起動とキャンバス表示
- 02テクスチャの読み込みとシーン構築
- 03フレームレートに依存しないアニメーション
- 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 を推奨します。)
基本的なワークフロー
- Application を作成して
awaitします。 v8 ではnew Application()は空の状態です。設定はawait app.init({...})で行います。app.viewではなくapp.canvasを DOM に追加します。バンドラーを使う場合は、トップレベルのawaitを非同期関数で囲みます。 Assetsでアセットを読み込みます。await Assets.load(url)はTextureを返します。多数のアセットを扱う場合は、マニフェスト/バンドルを登録して名前で読み込みます。v7 のLoaderはありません。- シーングラフを構築します。 すべてのオブジェクトは(
Containerである)app.stageから派生します。関連するオブジェクトをContainerにまとめます。子のトランスフォームは親を基準とします。描画順は挿入順です(後に追加したものが上になります)。 - ticker でアニメーションします。
app.ticker.add((ticker) => {...})を使います。動きをフレームレートに依存させないよう、ticker.deltaTime(フレーム数、60 fps で約 1)またはticker.deltaMS(ミリ秒)でスケーリングします。 - オブジェクトごとにイベントを有効にします。
eventMode = 'static'(または'dynamic')を設定してから、obj.on('pointerdown', ...)を使います。フェデレーテッドポインターイベントはマウス/タッチ/ペンに対応します。 - 大きな静的サブツリーをレンダーグループに昇格します。
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 がよく取り上げられています)。