Skip to content
Back to skills

Dev Phaser Particles

ASecurity

Particle emitters for visual effects like fire, smoke, explosions, and magic

  • 666 stars
  • 0 votes
  • 1 copy
  • 4 views
  • Added February 8, 2026
designtypescriptgitapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill dev-phaser-particles --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dev Phaser Particles?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dev Phaser Particles
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-dev-phaser-particles/badge)](https://www.skillsdirectory.com/skills/majiayu000-dev-phaser-particles)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dev-phaser-particles
description: Particle emitters for visual effects like fire, smoke, explosions, and magic
---

# Phaser Particles

> "Create stunning visual effects with particle systems."

## Before/After: Manual Particle System vs Phaser Particles

### ❌ Before: Manual Particle Management

```typescript
// 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

```typescript
// 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

```typescript
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        | `startFollow()` + 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
// Particles die when entering zone
const deathZone = new Phaser.Geom.Rectangle(300, 200, 200, 200);
particles.setDeathZone({
  type: "onEnter",
  source: deathZone,
});
```

### Particle Callbacks

```typescript
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                 |
| ----------- | -------------- | --------------------------- |
| `speed`     | number/min/max | Particle velocity           |
| `angle`     | number/min/max | Emission angle              |
| `scale`     | start/end      | Size over life              |
| `alpha`     | start/end      | Opacity over life           |
| `lifespan`  | number/min/max | Particle duration (ms)      |
| `quantity`  | number         | Particles per emission      |
| `frequency` | number         | Time between emissions (ms) |
| `blendMode` | string         | Rendering blend mode        |
| `rotate`    | start/end      | Rotation over life          |
| `emitZone`  | object         | Zone for emission           |
| `deathZone` | object         | Zone for death              |

## Common Blend Modes

| Mode       | Description | Use For             |
| ---------- | ----------- | ------------------- |
| `NORMAL`   | Default     | Most effects        |
| `ADD`      | Additive    | Fire, sparks, magic |
| `MULTIPLY` | Multiply    | Shadows, smoke      |
| `SCREEN`   | 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](https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Particles.ParticleEmitter.html) — Emitter API
- [Particle Manager](https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Particles.ParticleManager.html) — Manager API

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.