Claude-skill-registry dev-phaser-particles
Particle emitters for visual effects like fire, smoke, explosions, and magic
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dev-phaser-particles" ~/.claude/skills/majiayu000-claude-skill-registry-dev-phaser-particles && rm -rf "$T"
manifest:
skills/data/dev-phaser-particles/SKILL.mdsource content
Phaser Particles
"Create stunning visual effects with particle systems."
Before/After: Manual Particle System vs Phaser Particles
❌ Before: Manual Particle Management
// Manual particle system without Phaser class Particle { x: number; y: number; vx: number; vy: number; life: number; maxLife: number; size: number; alpha: number; color: string; constructor(x: number, y: number) { this.x = x; this.y = y; const angle = Math.random() * Math.PI * 2; const speed = 50 + Math.random() * 200; this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; this.life = 0; this.maxLife = 500 + Math.random() * 500; this.size = 2 + Math.random() * 4; this.alpha = 1; this.color = `hsl(${Math.random() * 60 + 10}, 100%, 50%)`; } update(dt: number): boolean { this.x += this.vx * dt / 1000; this.y += this.vy * dt / 1000; this.vy += 200 * dt / 1000; // Gravity this.life += dt; this.alpha = 1 - (this.life / this.maxLife); return this.life < this.maxLife; } render(ctx: CanvasRenderingContext2D) { ctx.globalAlpha = this.alpha; ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; } } class ParticleSystem { private particles: Particle[] = []; explode(x: number, y: number, count: number) { for (let i = 0; i < count; i++) { this.particles.push(new Particle(x, y)); } } update(dt: number) { // Filter out dead particles - creates garbage! this.particles = this.particles.filter(p => p.update(dt)); } render(ctx: CanvasRenderingContext2D) { for (const p of this.particles) { p.render(ctx); } } // No built-in emit zones // No death zones // No onEmit callbacks // No texture/animated particles // No blend modes (ADD, MULTIPLY) } // Problems: // - Manual array filtering creates GC pressure // - No built-in particle lifecycle management // - Manual render loop for each particle // - No texture support (only circles/rects) // - No blend modes (ADD for fire/magic) // - No emit zones (circle, rectangle, edge) // - No death zones // - No onEmit/onDeath callbacks // - Performance degrades with many particles
✅ After: Phaser Particle System
// Phaser handles particles automatically export class GameScene extends Phaser.Scene { create() { // Explosion emitter - ONE config! const explosion = this.add.particles(0, 0, 'explosion', { speed: { min: 100, max: 400 }, angle: { min: 0, max: 360 }, scale: { start: 0.5, end: 2 }, alpha: { start: 1, end: 0 }, lifespan: 600, quantity: 30, emitting: false, // Don't auto-emit blendMode: 'ADD' // Built-in blend modes! }); // Trigger explosion anywhere - ONE line! explosion.explode(30, 400, 300); // Fire effect - continuous emitter const fire = this.add.particles(400, 500, 'fire', { speed: { min: 50, max: 100 }, angle: { min: 240, max: 300 }, scale: { start: 0.5, end: 0 }, alpha: { start: 0.8, end: 0 }, blendMode: 'ADD', lifespan: 800, frequency: 50, // Emit every 50ms quantity: 3 }); // Trail following player - ONE line! const trail = this.add.particles(0, 0, 'trail', { speed: 0, scale: { start: 0.8, end: 0 }, alpha: { start: 0.5, end: 0 }, lifespan: 300, quantity: 1, frequency: 50, emitZone: { type: 'edge', // Circle edge emission! source: new Phaser.Geom.Circle(0, 0, 20), quantity: 3 } }); trail.startFollow(this.player); // Auto-follow! // Custom per-particle behavior const sparkles = this.add.particles(400, 300, 'sparkle', { speed: { min: 50, max: 150 }, scale: { start: 0, end: 0.5, ease: 'Back.easeOut' }, alpha: { start: 1, end: 0 }, lifespan: { min: 500, max: 1000 }, onEmit: (particle: any) => { // Customize each particle! particle.tint = Phaser.Utils.Array.GetRandom([ 0xff0000, 0x00ff00, 0x0000ff ]); } }); } } // Benefits: // - Automatic object pooling (no GC!) // - Built-in lifecycle management // - GPU-accelerated rendering // - Texture support + animation // - Blend modes (ADD, MULTIPLY, SCREEN) // - Emit zones (random, edge) // - Death zones // - Per-particle callbacks // - Follow any game object
When to Use This Skill
Use when:
- Creating explosion effects
- Adding fire/smoke effects
- Building magic spells
- Implementing weather effects
- Adding visual polish to actions
Quick Start
create() { const particles = this.add.particles(0, 0, 'flare', { speed: 100, scale: { start: 1, end: 0 }, blendMode: 'ADD' }); particles.startFollow(this.player); }
Decision Framework
| Need | Use |
|---|---|
| Trail effect | + emitting |
| Explosion | One-time burst emitter |
| Continuous effect | Always-on emitter |
| Zone-based emission | Emitter zone |
| Texture animation | Animated particle frames |
Progressive Guide
Level 1: Basic Particle Emitter
export class GameScene extends Phaser.Scene { create() { // Simple particle emitter const particles = this.add.particles(400, 300, "flare", { speed: 100, scale: { start: 1, end: 0 }, blendMode: "ADD", lifespan: 1000, quantity: 1, }); // Particle at mouse position this.input.on("pointermove", (pointer: Phaser.Input.Pointer) => { particles.emitParticleAt(pointer.x, pointer.y); }); } }
Level 2: Common Effects
create() { // Fire effect const fire = this.add.particles(400, 500, 'fire', { speed: { min: 50, max: 100 }, angle: { min: 240, max: 300 }, scale: { start: 0.5, end: 0 }, blendMode: 'ADD', lifespan: 800, frequency: 50, quantity: 3 }); // Smoke effect const smoke = this.add.particles(400, 480, 'smoke', { speed: 30, angle: { min: 250, max: 290 }, scale: { start: 0.3, end: 1 }, alpha: { start: 0.5, end: 0 }, lifespan: 2000, frequency: 100 }); // Explosion const createExplosion = (x: number, y: number) => { const explosion = this.add.particles(x, y, 'explosion', { speed: { min: 100, max: 300 }, angle: { min: 0, max: 360 }, scale: { start: 0.5, end: 2 }, alpha: { start: 1, end: 0 }, lifespan: 500, quantity: 30, emitting: false }); explosion.explode(30, x, y); }; // Trigger explosion on click this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { createExplosion(pointer.x, pointer.y); }); }
Level 3: Following Emitters
export class GameScene extends Phaser.Scene { private trailEmitter!: Phaser.GameObjects.Particles.ParticleEmitter; create() { // Create player this.player = this.add.image(400, 300, "player"); // Trail emitter this.trailEmitter = this.add.particles(0, 0, "trail", { speed: 0, scale: { start: 0.8, end: 0 }, alpha: { start: 0.5, end: 0 }, lifespan: 300, quantity: 1, frequency: 50, emitZone: { type: "edge", source: new Phaser.Geom.Circle(0, 0, 20), quantity: 3, }, }); // Follow player this.trailEmitter.startFollow(this.player); // Engine exhaust effect const exhaust = this.add.particles(0, 20, "exhaust", { speedX: { min: -20, max: 20 }, speedY: { min: 50, max: 100 }, scale: { start: 0.3, end: 0 }, alpha: { start: 0.8, end: 0 }, lifespan: 400, frequency: 30, }); exhaust.startFollow(this.player); } }
Level 4: Advanced Particle Configurations
create() { // Magic sparkles const sparkles = this.add.particles(400, 300, 'sparkle', { speed: { min: 50, max: 150 }, angle: { min: 0, max: 360 }, scale: { start: 0, end: 0.5, ease: 'Back.easeOut' }, alpha: { start: 1, end: 0, ease: 'Linear' }, lifespan: { min: 500, max: 1000 }, quantity: 2, frequency: 100, blendMode: 'ADD', rotate: { start: 0, end: 180 }, emitting: true }); // Rain effect const rainZone = new Phaser.Geom.Rectangle(0, 0, this.scale.width, 20); const rain = this.add.particles(0, 0, 'rain', { x: { min: 0, max: this.scale.width }, y: -20, speedY: 400, speedX: 0, scale: { start: 0.3, end: 0.3 }, alpha: { start: 0.5, end: 0.8 }, lifespan: 2000, quantity: 5, frequency: 20, emitZone: { source: rainZone } }); // Radial burst const burstEmitter = this.add.particles(400, 300, 'particle', { speed: 200, scale: { start: 1, end: 0 }, alpha: { start: 1, end: 0 }, lifespan: 1000, quantity: 50, emitting: false, onEmit: (particle: any) => { // Set custom color per particle particle.tint = Phaser.Utils.Array.GetRandom([0xff0000, 0x00ff00, 0x0000ff]); } }); this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { burstEmitter.explode(50, pointer.x, pointer.y); }); }
Level 5: Particle System Manager
class ParticleManager { private emitters = new Map< string, Phaser.GameObjects.Particles.ParticleEmitter >(); constructor(private scene: Phaser.Scene) { this.createPresets(); } private createPresets() { // Fire preset this.createEmitter("fire", { key: "fire", config: { speed: { min: 50, max: 100 }, angle: { min: 240, max: 300 }, scale: { start: 0.5, end: 0 }, alpha: { start: 0.8, end: 0 }, blendMode: "ADD", lifespan: 800, frequency: 50, }, }); // Explosion preset this.createEmitter("explosion", { key: "explosion", config: { speed: { min: 100, max: 400 }, angle: { min: 0, max: 360 }, scale: { start: 0.3, end: 1 }, alpha: { start: 1, end: 0 }, lifespan: 600, quantity: 30, emitting: false, }, }); // Trail preset this.createEmitter("trail", { key: "trail", config: { speed: 50, scale: { start: 0.5, end: 0 }, alpha: { start: 0.6, end: 0 }, lifespan: 400, frequency: 50, }, }); } createEmitter(name: string, { key, config }: any) { const emitter = this.scene.add.particles(0, 0, key, config); this.emitters.set(name, emitter); return emitter; } emit(name: string, x: number, y: number) { const emitter = this.emitters.get(name); if (emitter) { emitter.explode(emitter.config.quantity || 10, x, y); } } follow(name: string, target: Phaser.GameObjects.GameObject) { const emitter = this.emitters.get(name); if (emitter) { emitter.startFollow(target); } } stop(name: string) { const emitter = this.emitters.get(name); if (emitter) { emitter.stop(); } } start(name: string) { const emitter = this.emitters.get(name); if (emitter) { emitter.start(); } } destroy(name: string) { const emitter = this.emitters.get(name); if (emitter) { emitter.destroy(); this.emitters.delete(name); } } } // Usage in scene export class GameScene extends Phaser.Scene { private particles!: ParticleManager; create() { this.particles = new ParticleManager(this); // Use presets this.particles.follow("trail", this.player); // Trigger explosion this.input.on("pointerdown", (pointer: Phaser.Input.Pointer) => { this.particles.emit("explosion", pointer.x, pointer.y); }); } }
Anti-Patterns
❌ DON'T:
- Create too many particles per frame
- Use large particle textures for small effects
- Forget to stop emitters when done
- Overuse blend modes (can kill performance)
- Create particles in update() loop
- Ignore particle lifespan
✅ DO:
- Limit particle count for mobile
- Use appropriate particle sizes
- Stop/remove unused emitters
- Use blend modes sparingly
- Create emitters once in create()
- Tune lifespan for desired effect
Code Patterns
Emit Zones
// Rectangle zone const rectZone = new Phaser.Geom.Rectangle(x, y, width, height); particles.setEmitZone({ type: "random", source: rectZone, }); // Edge zone (particles on perimeter) const edgeZone = new Phaser.Geom.Circle(x, y, radius); particles.setEmitZone({ type: "edge", source: edgeZone, quantity: 20, }); // Random point zone const pointZone = new Phaser.Geom.Circle(x, y, radius); particles.setEmitZone({ type: "random", source: pointZone, });
Death Zones
// Particles die when entering zone const deathZone = new Phaser.Geom.Rectangle(300, 200, 200, 200); particles.setDeathZone({ type: "onEnter", source: deathZone, });
Particle Callbacks
const particles = this.add.particles(400, 300, "spark", { speed: 100, lifespan: 1000, onEmit: (particle: any) => { // Customize each particle particle.tint = 0xff0000; particle.velocity.x *= Math.random(); }, onParticleEmit: (emitter, particle) => { // Called when particle emits }, onParticleDeath: (emitter, particle) => { // Called when particle dies }, });
Emitter Configuration Reference
| Property | Type | Description |
|---|---|---|
| number/min/max | Particle velocity |
| number/min/max | Emission angle |
| start/end | Size over life |
| start/end | Opacity over life |
| number/min/max | Particle duration (ms) |
| number | Particles per emission |
| number | Time between emissions (ms) |
| string | Rendering blend mode |
| start/end | Rotation over life |
| object | Zone for emission |
| object | Zone for death |
Common Blend Modes
| Mode | Description | Use For |
|---|---|---|
| Default | Most effects |
| Additive | Fire, sparks, magic |
| Multiply | Shadows, smoke |
| Screen | Glowing effects |
Checklist
- Particle textures loaded
- Emitter frequency tuned
- Particle lifespan appropriate
- Speed and angle configured
- Blend mode set correctly
- Emitters stopped/removed when done
- Particle count limited for performance
Reference
- Particle Emitter — Emitter API
- Particle Manager — Manager API