mcpField
P
pixijs-2d

pixijs-2d

github.com/freshtechbro開発1.2k2.6k 回インストール49 週間前に更新
リポジトリ

pixijs-2d について

pixijs-2dは、PixiJSでインタラクティブな2DグラフィックスやCanvasアプリを作る開発者向けのスキルガイドです。スプライト、パーティクル、フィルター、描画、WebGL/WebGPUレンダリングの最適化を扱います。

主な機能

  1. 01スプライトとテクスチャの管理
  2. 02プログラムによるベクター描画
  3. 03ParticleContainerによる大量スプライト描画
  4. 04シェーダーベースの視覚効果
  5. 05スプライトシートアニメーション

活用シーン

  1. 012DゲームやインタラクティブなCanvasアプリの制作
  2. 02多数のスプライトを使うパーティクル効果の作成
  3. 033Dシーンへの2D UIオーバーレイ追加
  4. 04データビジュアライゼーションの描画

インストール

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

$npx skills add https://github.com/freshtechbro/claudedesignskills --skill pixijs-2d

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/examples/
README.md
assets/starter_pixijs/
index.html
main.js
README.md
styles.css
references/
api_reference.md
filters_effects.md
performance_guide.md
scripts/
particle_builder.py
sprite_generator.py

詳細

PixiJS 2D レンダリングスキル

WebGL/WebGPU を使用して、インタラクティブなグラフィックス、パーティクルエフェクト、キャンバスベースのアプリケーションを作成するための、高速で軽量な 2D レンダリングエンジンです。


このスキルを使う場面

次のような要件に遭遇したとき、このスキルを使用します。

  • 「2D パーティクルエフェクトを作成する」または「アニメーションするパーティクル」
  • 「2D スプライトアニメーション」または「スプライトシートの処理」
  • 「インタラクティブなキャンバスグラフィックス」または「2D ゲーム」
  • 「3D シーン上の UI オーバーレイ」または「HUD レイヤー」
  • 「プログラムで図形を描画する」または「ベクターグラフィックス API」
  • 「レンダリングパフォーマンスを最適化する」または「数千個のスプライト」
  • 「ビジュアルフィルターを適用する」または「ぼかし/変位エフェクト」
  • 「軽量な 2D エンジン」または「Canvas2D の代替」

PixiJS の用途:高パフォーマンスの 2D レンダリング(最大 100,000 個以上のスプライト)、パーティクルシステム、インタラクティブ UI、2D ゲーム、WebGL アクセラレーションによるデータ可視化。

用途外:3D グラフィックス(Three.js/R3F を使用)、シンプルなアニメーション(Motion/GSAP を使用)、基本的な DOM 操作。


コアコンセプト

1. アプリケーションとレンダラー

PixiJS アプリケーションのエントリーポイントです。

import { Application } from 'pixi.js';

const app = new Application();

await app.init({
  width: 800,
  height: 600,
  backgroundColor: 0x1099bb,
  antialias: true,  // Smooth edges
  resolution: window.devicePixelRatio || 1
});

document.body.appendChild(app.canvas);

主なプロパティ:

  • app.stage:すべての表示オブジェクトを格納するルートコンテナー
  • app.renderer:WebGL/WebGPU レンダラーのインスタンス
  • app.ticker:アニメーションの更新ループ
  • app.screen:キャンバスのサイズ

2. スプライトとテクスチャ

画像から読み込む、基本的な視覚要素です。

import { Assets, Sprite } from 'pixi.js';

// Load texture
const texture = await Assets.load('path/to/image.png');

// Create sprite
const sprite = new Sprite(texture);
sprite.anchor.set(0.5);  // Center pivot
sprite.position.set(400, 300);
sprite.scale.set(2);  // 2x scale
sprite.rotation = Math.PI / 4;  // 45 degrees
sprite.alpha = 0.8;  // 80% opacity
sprite.tint = 0xff0000;  // Red tint

app.stage.addChild(sprite);

簡単な作成方法:

const sprite = Sprite.from('path/to/image.png');

3. グラフィックス API

プログラムでベクター図形を描画します。

import { Graphics } from 'pixi.js';

const graphics = new Graphics();

// Rectangle
graphics.rect(50, 50, 100, 100).fill('blue');

// Circle with stroke
graphics.circle(200, 100, 50).fill('red').stroke({ width: 2, color: 'white' });

// Complex path
graphics
  .moveTo(300, 100)
  .lineTo(350, 150)
  .lineTo(250, 150)
  .closePath()
  .fill({ color: 0x00ff00, alpha: 0.5 });

app.stage.addChild(graphics);

SVG のサポート:

graphics.svg('<svg><path d="M 100 350 q 150 -300 300 0" /></svg>');

4. ParticleContainer

数千個のスプライトのレンダリングに最適化されたコンテナーです。

import { ParticleContainer, Particle, Texture } from 'pixi.js';

const texture = Texture.from('particle.png');

const container = new ParticleContainer({
  dynamicProperties: {
    position: true,   // Allow position updates
    scale: false,     // Static scale
    rotation: false,  // Static rotation
    color: false      // Static color
  }
});

// Add 10,000 particles
for (let i = 0; i < 10000; i++) {
  const particle = new Particle({
    texture,
    x: Math.random() * 800,
    y: Math.random() * 600
  });

  container.addParticle(particle);
}

app.stage.addChild(container);

パフォーマンス:プロパティが静的な場合、通常の Container より最大 10 倍高速です。


5. フィルター

WebGL シェーダーを使用して、ピクセル単位のエフェクトを適用します。

import { BlurFilter, DisplacementFilter, ColorMatrixFilter } from 'pixi.js';

// Blur
const blurFilter = new BlurFilter({ strength: 8, quality: 4 });
sprite.filters = [blurFilter];

// Multiple filters
sprite.filters = [
  new BlurFilter({ strength: 4 }),
  new ColorMatrixFilter()  // Color transforms
];

// Custom filter area for performance
sprite.filterArea = new Rectangle(0, 0, 200, 100);

利用可能なフィルター:

  • BlurFilter:ガウスぼかし
  • ColorMatrixFilter:色の変換(セピア、グレースケールなど)
  • DisplacementFilter:ピクセルのワープ/歪み
  • AlphaFilter:子要素全体のアルファ値を統一
  • NoiseFilter:ランダムな粒子のエフェクト
  • FXAAFilter:アンチエイリアス

6. テキストのレンダリング

スタイルを設定してテキストを表示します。

import { Text, BitmapText, TextStyle } from 'pixi.js';

// Standard Text
const style = new TextStyle({
  fontFamily: 'Arial',
  fontSize: 36,
  fill: '#ffffff',
  stroke: { color: '#000000', width: 4 },
  filters: [new BlurFilter()]  // Bake filter into texture
});

const text = new Text({ text: 'Hello PixiJS!', style });
text.position.set(100, 100);

// BitmapText (faster for dynamic text)
const bitmapText = new BitmapText({
  text: 'Score: 0',
  style: { fontFamily: 'MyBitmapFont', fontSize: 24 }
});

パフォーマンスのヒント:頻繁に変わるテキスト(スコア、カウンター)には BitmapText を使用します。


よくあるパターン

パターン 1:基本的なインタラクティブスプライト
import { Application, Assets, Sprite } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);

const texture = await Assets.load('bunny.png');
const bunny = new Sprite(texture);

bunny.anchor.set(0.5);
bunny.position.set(400, 300);
bunny.eventMode = 'static';  // Enable interactivity
bunny.cursor = 'pointer';

// Events
bunny.on('pointerdown', () => {
  bunny.scale.set(1.2);
});

bunny.on('pointerup', () => {
  bunny.scale.set(1.0);
});

bunny.on('pointerover', () => {
  bunny.tint = 0xff0000;  // Red on hover
});

bunny.on('pointerout', () => {
  bunny.tint = 0xffffff;  // Reset
});

app.stage.addChild(bunny);

// Animation loop
app.ticker.add((ticker) => {
  bunny.rotation += 0.01 * ticker.deltaTime;
});

パターン 2:Graphics による描画
import { Graphics, Application } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);

const graphics = new Graphics();

// Rectangle with gradient
graphics.rect(50, 50, 200, 100).fill({
  color: 0x3399ff,
  alpha: 0.8
});

// Circle with stroke
graphics.circle(400, 300, 80)
  .fill('yellow')
  .stroke({ width: 4, color: 'orange' });

// Star shape
graphics.star(600, 300, 5, 50, 0).fill({ color: 0xffdf00, alpha: 0.9 });

// Custom path
graphics
  .moveTo(100, 400)
  .bezierCurveTo(150, 300, 250, 300, 300, 400)
  .stroke({ width: 3, color: 'white' });

// Holes
graphics
  .rect(450, 400, 150, 100).fill('red')
  .beginHole()
  .circle(525, 450, 30)
  .endHole();

app.stage.addChild(graphics);

// Dynamic drawing (animation)
app.ticker.add(() => {
  graphics.clear();

  const time = Date.now() * 0.001;
  const x = 400 + Math.cos(time) * 100;
  const y = 300 + Math.sin(time) * 100;

  graphics.circle(x, y, 20).fill('cyan');
});

パターン 3:ParticleContainer を使用したパーティクルシステム
import { Application, ParticleContainer, Particle, Texture } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600, backgroundColor: 0x000000 });
document.body.appendChild(app.canvas);

const texture = Texture.from('spark.png');

const particles = new ParticleContainer({
  dynamicProperties: {
    position: true,  // Update positions every frame
    scale: true,     // Fade out by scaling
    rotation: true,  // Rotate particles
    color: false     // Static color
  }
});

const particleData = [];

// Create particles
for (let i = 0; i < 5000; i++) {
  const particle = new Particle({
    texture,
    x: 400,
    y: 300,
    scaleX: 0.5,
    scaleY: 0.5
  });

  particles.addParticle(particle);

  particleData.push({
    particle,
    vx: (Math.random() - 0.5) * 5,
    vy: (Math.random() - 0.5) * 5 - 2,  // Slight upward bias
    life: 1.0
  });
}

app.stage.addChild(particles);

// Update loop
app.ticker.add((ticker) => {
  particleData.forEach(data => {
    // Physics
    data.particle.x += data.vx * ticker.deltaTime;
    data.particle.y += data.vy * ticker.deltaTime;
    data.vy += 0.1 * ticker.deltaTime;  // Gravity

    // Fade out
    data.life -= 0.01 * ticker.deltaTime;
    data.particle.scaleX = data.life * 0.5;
    data.particle.scaleY = data.life * 0.5;

    // Reset particle
    if (data.life <= 0) {
      data.particle.x = 400;
      data.particle.y = 300;
      data.vx = (Math.random() - 0.5) * 5;
      data.vy = (Math.random() - 0.5) * 5 - 2;
      data.life = 1.0;
    }
  });
});

パターン 4:フィルターの適用
import { Application, Sprite, Assets, BlurFilter, DisplacementFilter } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);

const texture = await Assets.load('photo.jpg');
const photo = new Sprite(texture);
photo.position.set(100, 100);

// Blur filter
const blurFilter = new BlurFilter({ strength: 5, quality: 4 });

// Displacement filter (wavy effect)
const displacementTexture = await Assets.load('displacement.jpg');
const displacementSprite = Sprite.from(displacementTexture);
const displacementFilter = new DisplacementFilter({
  sprite: displacementSprite,
  scale: 50
});

// Apply multiple filters
photo.filters = [blurFilter, displacementFilter];

// Optimize with filterArea
photo.filterArea = new Rectangle(0, 0, photo.width, photo.height);

app.stage.addChild(photo);

// Animate displacement
app.ticker.add((ticker) => {
  displacementSprite.x += 1 * ticker.deltaTime;
  displacementSprite.y += 0.5 * ticker.deltaTime;
});

パターン 5:シェーダーを使用したカスタムフィルター
import { Filter, GlProgram } from 'pixi.js';

const vertex = `
  in vec2 aPosition;
  out vec2 vTextureCoord;

  uniform vec4 uInputSize;
  uniform vec4 uOutputFrame;
  uniform vec4 uOutputTexture;

  vec4 filterVertexPosition() {
    vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
    position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
    position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
    return vec4(position, 0.0, 1.0);
  }

  vec2 filterTextureCoord() {
    return aPosition * (uOutputFrame.zw * uInputSize.zw);
  }

  void main() {
    gl_Position = filterVertexPosition();
    vTextureCoord = filterTextureCoord();
  }
`;

const fragment = `
  in vec2 vTextureCoord;
  uniform sampler2D uTexture;
  uniform float uTime;

  void main() {
    vec2 uv = vTextureCoord;

    // Wave distortion
    float wave = sin(uv.y * 10.0 + uTime) * 0.05;
    vec4 color = texture(uTexture, vec2(uv.x + wave, uv.y));

    gl_FragColor = color;
  }
`;

const customFilter = new Filter({
  glProgram: new GlProgram({ fragment, vertex }),
  resources: {
    timeUniforms: {
      uTime: { value: 0.0, type: 'f32' }
    }
  }
});

sprite.filters = [customFilter];

// Update uniform
app.ticker.add((ticker) => {
  customFilter.resources.timeUniforms.uniforms.uTime += 0.04 * ticker.deltaTime;
});

パターン 6:スプライトシートアニメーション
import { Application, Assets, AnimatedSprite } from 'pixi.js';

const app = new Application();
await app.init({ width: 800, height: 600 });
document.body.appendChild(app.canvas);

// Load sprite sheet
await Assets.load('spritesheet.json');

// Create animation from frames
const frames = [];
for (let i = 0; i < 10; i++) {
  frames.push(Texture.from(`frame_${i}.png`));
}

const animation = new AnimatedSprite(frames);
animation.anchor.set(0.5);
animation.position.set(400, 300);
animation.animationSpeed = 0.16;  // ~10 FPS
animation.play();

app.stage.addChild(animation);

// Control playback
animation.stop();
animation.gotoAndPlay(0);
animation.onComplete = () => {
  console.log('Animation completed!');
};

パターン 7:パフォーマンス向上のためのオブジェクトプーリング
class SpritePool {
  constructor(texture, initialSize = 100) {
    this.texture = texture;
    this.available = [];
    this.active = [];

    // Pre-create sprites
    for (let i = 0; i < initialSize; i++) {
      this.createSprite();
    }
  }

  createSprite() {
    const sprite = new Sprite(this.texture);
    sprite.visible = false;
    this.available.push(sprite);
    return sprite;
  }

  spawn(x, y) {
    let sprite = this.available.pop();

    if (!sprite) {
      sprite = this.createSprite();
    }

    sprite.position.set(x, y);
    sprite.visible = true;
    this.active.push(sprite);

    return sprite;
  }

  despawn(sprite) {
    sprite.visible = false;
    const index = this.active.indexOf(sprite);

    if (index > -1) {
      this.active.splice(index, 1);
      this.available.push(sprite);
    }
  }

  reset() {
    this.active.forEach(sprite => {
      sprite.visible = false;
      this.available.push(sprite);
    });
    this.active = [];
  }
}

// Usage
const bulletTexture = Texture.from('bullet.png');
const bulletPool = new SpritePool(bulletTexture, 50);

// Spawn bullet
const bullet = bulletPool.spawn(100, 200);
app.stage.addChild(bullet);

// Despawn after 2 seconds
setTimeout(() => {
  bulletPool.despawn(bullet);
}, 2000);

統合パターン

React との統合
import { useEffect, useRef } from 'react';
import { Application } from 'pixi.js';

function PixiCanvas() {
  const canvasRef = useRef(null);
  const appRef = useRef(null);

  useEffect(() => {
    const init = async () => {
      const app = new Application();

      await app.init({
        width: 800,
        height: 600,
        backgroundColor: 0x1099bb
      });

      canvasRef.current.appendChild(app.canvas);
      appRef.current = app;

      // Setup scene
      // ... add sprites, graphics, etc.
    };

    init();

    return () => {
      if (appRef.current) {
        appRef.current.destroy(true, { children: true });
      }
    };
  }, []);

  return <div ref={canvasRef} />;
}

Three.js オーバーレイ(3D 上の 2D UI)
import * as THREE from 'three';
import { Application, Sprite, Text } from 'pixi.js';

// Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

// PixiJS overlay
const pixiApp = new Application();
await pixiApp.init({
  width: window.innerWidth,
  height: window.innerHeight,
  backgroundAlpha: 0  // Transparent background
});

pixiApp.canvas.style.position = 'absolute';
pixiApp.canvas.style.top = '0';
pixiApp.canvas.style.left = '0';
pixiApp.canvas.style.pointerEvents = 'none';  // Click through
document.body.appendChild(pixiApp.canvas);

// Add UI elements
const scoreText = new Text({ text: 'Score: 0', style: { fontSize: 24, fill: 'white' } });
scoreText.position.set(20, 20);
pixiApp.stage.addChild(scoreText);

// Render loop
function animate() {
  requestAnimationFrame(animate);

  renderer.render(scene, camera);  // 3D scene
  pixiApp.renderer.render(pixiApp.stage);  // 2D overlay
}

animate();

パフォーマンスのベストプラクティス

1. 大量のスプライトには ParticleContainer を使用する
// DON'T: Regular Container (slow for 1000+ sprites)
const container = new Container();
for (let i = 0; i < 10000; i++) {
  container.addChild(new Sprite(texture));
}

// DO: ParticleContainer (10x faster)
const particles = new ParticleContainer({
  dynamicProperties: { position: true }
});
for (let i = 0; i < 10000; i++) {
  particles.addParticle(new Particle({ texture }));
}

2. フィルターの使用を最適化する
// Set filterArea to avoid runtime measurement
sprite.filterArea = new Rectangle(0, 0, 200, 100);

// Release filters when not needed
sprite.filters = null;

// Bake filters into Text at creation
const style = new TextStyle({
  filters: [new BlurFilter()]  // Applied once at texture creation
});

3. テクスチャメモリを管理する
// Destroy textures when done
texture.destroy();

// Batch destruction with delays to prevent frame drops
textures.forEach((tex, i) => {
  setTimeout(() => tex.destroy(), Math.random() * 100);
});

4. 画面外のオブジェクトではカリングを有効にする
sprite.cullable = true;  // Skip rendering if outside viewport

// Use CullerPlugin
import { CullerPlugin } from 'pixi.js';

5. 静的なグラフィックスをビットマップとしてキャッシュする
// Convert complex graphics to texture for faster rendering
const complexShape = new Graphics();
// ... draw many shapes

complexShape.cacheAsBitmap = true;  // Renders to texture once

6. レンダラーの設定を最適化する
const app = new Application();
await app.init({
  antialias: false,  // Disable on mobile for performance
  resolution: 1,     // Lower resolution on low-end devices
  autoDensity: true
});

7. 動的なテキストには BitmapText を使用する
// DON'T: Standard Text (expensive updates)
const text = new Text({ text: `Score: ${score}` });
app.ticker.add(() => {
  text.text = `Score: ${++score}`;  // Re-renders texture each frame
});

// DO: BitmapText (much faster)
const bitmapText = new BitmapText({ text: `Score: ${score}` });
app.ticker.add(() => {
  bitmapText.text = `Score: ${++score}`;
});

よくある落とし穴

落とし穴 1:オブジェクトを破棄しない

問題:GPU リソースが解放されず、メモリリークが発生します。

解決策:

// Always destroy sprites and textures
sprite.destroy({ children: true, texture: true, baseTexture: true });

// Destroy filters
sprite.filters = null;

// Destroy graphics
graphics.destroy();

落とし穴 2:静的な ParticleContainer のプロパティを更新する

問題:dynamicProperties.scale = false のときに scale を変更しても、効果はありません。

解決策:

const container = new ParticleContainer({
  dynamicProperties: {
    position: true,
    scale: true,  // Enable if you need to update
    rotation: true,
    color: true
  }
});

// If properties are static but you change them, call update:
container.update();

落とし穴 3:フィルターを過剰に使用する

問題:フィルターは処理負荷が高く、多用するとパフォーマンスの問題が発生します。

解決策:

// Limit filter usage
sprite.filters = [blurFilter];  // 1-2 filters max

// Use filterArea to constrain processing
sprite.filterArea = new Rectangle(0, 0, sprite.width, sprite.height);

// Bake filters into textures when possible
const filteredTexture = renderer.filters.generateFilteredTexture({
  texture,
  filters: [blurFilter]
});

落とし穴 4:テキストを頻繁に更新する

問題:Text を更新するたびにテクスチャが再生成されます。

解決策:

// Use BitmapText for frequently changing text
const bitmapText = new BitmapText({ text: 'Score: 0' });

// Reduce resolution for less memory
text.resolution = 1;  // Lower than device pixel ratio

落とし穴 5:再描画せずに Graphics の clear() を呼び出す

問題:clear() を呼び出すとすべてのジオメトリが削除されますが、自動的に再描画されることはありません。

解決策:

graphics.clear();  // Remove all shapes

// Redraw new shapes
graphics.rect(0, 0, 100, 100).fill('blue');

落とし穴 6:アセットの読み込みを使用しない

問題:URL からスプライトを作成すると、非同期処理の問題が発生します。

解決策:

// DON'T:
const sprite = Sprite.from('image.png');  // May load asynchronously

// DO:
const texture = await Assets.load('image.png');
const sprite = new Sprite(texture);

リソース


関連スキル

  • threejs-webgl:3D グラフィックス向けです。PixiJS は 2D UI オーバーレイを提供できます
  • gsap-scrolltrigger:スクロールに合わせて PixiJS のプロパティをアニメーション化する場合に使用します
  • motion-framer:PixiJS のキャンバスと併用して React コンポーネントをアニメーション化する場合に使用します
  • react-three-fiber:同様の React 統合パターンを提供します

まとめ

PixiJS は、WebGL アクセラレーションによる高性能な 2D レンダリングに優れています。主な強みは次のとおりです。

  1. パフォーマンス:100,000 個以上のスプライトを 60 FPS で描画できます
  2. ParticleContainer:静的なプロパティでは 10 倍高速です
  3. フィルター:WebGL を活用した視覚効果を提供します
  4. Graphics API:直感的なベクター描画が可能です
  5. アセット管理:テクスチャとスプライトシートを堅牢に扱えます

パーティクルシステム、2D ゲーム、データ可視化、そしてパフォーマンスが重要なインタラクティブキャンバスアプリケーションに使用してください。

コメント