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. 03使用 ParticleContainer 渲染大量精灵
  4. 04基于着色器的视觉滤镜
  5. 05精灵表动画

适用场景

  1. 01制作 2D 游戏和交互式 Canvas 应用
  2. 02构建含大量精灵的粒子效果
  3. 03为 3D 场景添加 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 渲染技能

快速、轻量的 2D 渲染引擎,可使用 WebGL/WebGPU 创建交互式图形、粒子效果和基于画布的应用。


何时使用此技能

遇到以下情况时触发此技能:

  • “创建 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. Graphics 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:统一子项的 Alpha 值
  • 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. 性能:以 60 FPS 渲染 100,000+ 个精灵
  2. ParticleContainer:静态属性的处理速度提升 10 倍
  3. 滤镜:由 WebGL 驱动的视觉效果
  4. 图形 API:直观的矢量绘制
  5. 资源管理:可靠的纹理和精灵图集处理

适用于粒子系统、2D 游戏、数据可视化和交互式画布应用,尤其是对性能要求较高的场景。

评论

支持 Markdown