Skip to content

Particles and Effects

The particle system allows you to create visual effects like fire, explosions, smoke, and sparks. This guide covers ParticleEmitter, ParticleConfig, and the included presets.

ParticleEmitter Basics

A ParticleEmitter is an Entity that manages a pool of particles to create visual effects.

Creating a Particle Emitter

#include <graphics/particles/ParticleEmitter.h>
#include <graphics/particles/ParticleConfig.h>

// Create particle configuration
pixelroot32::graphics::particles::ParticleConfig config;
config.startColor = pixelroot32::graphics::Color::Red;
config.endColor = pixelroot32::graphics::Color::Yellow;
config.lifetime = 1.0f; // 1 second
config.speed = 50.0f;
config.gravity = -100.0f; // Upward (negative = up)

// Create emitter
pixelroot32::graphics::particles::ParticleEmitter* emitter = 
    new pixelroot32::graphics::particles::ParticleEmitter(100, 100, config);

// Add to scene
addEntity(emitter);

Emitting Particles

// Emit a burst of particles
emitter->burst(100, 100, 10); // x, y, particle count

// Particles will automatically update and draw
// No additional code needed!

ParticleConfig

ParticleConfig defines how particles behave:

#include <graphics/particles/ParticleConfig.h>

pixelroot32::graphics::particles::ParticleConfig config;

// Colors
config.startColor = pixelroot32::graphics::Color::Red;   // Color at spawn
config.endColor = pixelroot32::graphics::Color::Yellow;  // Color at death

// Lifetime
config.lifetime = 0.5f; // Duration in seconds

// Velocity
config.speed = 100.0f;           // Base speed
config.speedVariation = 20.0f;   // Random variation
config.direction = 90.0f;        // Direction in degrees (0 = right, 90 = up)
config.directionVariation = 45.0f; // Random direction spread

// Physics
config.gravity = 200.0f;  // Gravity force (positive = down)
config.friction = 0.95f;   // Friction (0.0 to 1.0, 1.0 = no friction)

// Size
config.startSize = 2;     // Size at spawn (pixels)
config.endSize = 1;       // Size at death

Complete Config Example

pixelroot32::graphics::particles::ParticleConfig fireConfig;

// Fire colors (red to yellow)
fireConfig.startColor = pixelroot32::graphics::Color::Red;
fireConfig.endColor = pixelroot32::graphics::Color::Yellow;

// Short lifetime
fireConfig.lifetime = 0.3f;

// Upward movement with variation
fireConfig.speed = 80.0f;
fireConfig.speedVariation = 30.0f;
fireConfig.direction = 90.0f; // Up
fireConfig.directionVariation = 30.0f; // Spread

// Upward gravity (negative)
fireConfig.gravity = -50.0f;

// Slight friction
fireConfig.friction = 0.98f;

// Size
fireConfig.startSize = 3;
fireConfig.endSize = 1;

Built-in Presets

PixelRoot32 includes several particle presets for common effects:

Fire

#include <graphics/particles/ParticlePresets.h>

// Create fire emitter
pixelroot32::graphics::particles::ParticleEmitter* fire = 
    new pixelroot32::graphics::particles::ParticleEmitter(
        100, 100,
        pixelroot32::graphics::particles::ParticlePresets::Fire()
    );

// Emit continuous fire
void update(unsigned long deltaTime) override {
    fire->burst(100, 100, 2); // Emit 2 particles per frame
    Scene::update(deltaTime);
}

Explosion

// Create explosion emitter
pixelroot32::graphics::particles::ParticleEmitter* explosion = 
    new pixelroot32::graphics::particles::ParticleEmitter(
        100, 100,
        pixelroot32::graphics::particles::ParticlePresets::Explosion()
    );

// Emit explosion burst
explosion->burst(100, 100, 20); // 20 particles at once

Sparks

// Create sparks emitter
pixelroot32::graphics::particles::ParticleEmitter* sparks = 
    new pixelroot32::graphics::particles::ParticleEmitter(
        100, 100,
        pixelroot32::graphics::particles::ParticlePresets::Sparks()
    );

// Emit sparks
sparks->burst(100, 100, 10);

Smoke

// Create smoke emitter
pixelroot32::graphics::particles::ParticleEmitter* smoke = 
    new pixelroot32::graphics::particles::ParticleEmitter(
        100, 100,
        pixelroot32::graphics::particles::ParticlePresets::Smoke()
    );

// Emit smoke
smoke->burst(100, 100, 3);

Dust

// Create dust emitter
pixelroot32::graphics::particles::ParticleEmitter* dust = 
    new pixelroot32::graphics::particles::ParticleEmitter(
        100, 100,
        pixelroot32::graphics::particles::ParticlePresets::Dust()
    );

// Emit dust
dust->burst(100, 100, 5);

Complete Example: Explosion Effect

#include <core/Scene.h>
#include <graphics/particles/ParticleEmitter.h>
#include <graphics/particles/ParticlePresets.h>

class ExplosionEffect : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::particles::ParticleEmitter* explosion;
    bool active = false;

public:
    ExplosionEffect()
        : Entity(0, 0, 1, 1, pixelroot32::core::EntityType::GENERIC) {
        setRenderLayer(1);

        // Create explosion emitter
        explosion = new pixelroot32::graphics::particles::ParticleEmitter(
            0, 0,
            pixelroot32::graphics::particles::ParticlePresets::Explosion()
        );
    }

    void trigger(float x, float y) {
        active = true;
        this->x = x;
        this->y = y;

        // Emit explosion burst
        explosion->burst(x, y, 25);
    }

    void update(unsigned long deltaTime) override {
        explosion->update(deltaTime);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        explosion->draw(renderer);
    }
};

// Usage in scene
void MyScene::init() override {
    explosionEffect = new ExplosionEffect();
    addEntity(explosionEffect);
}

void MyScene::update(unsigned long deltaTime) override {
    auto& input = engine.getInputManager();

    // Trigger explosion on button press
    if (input.isButtonPressed(4)) { // Button A
        explosionEffect->trigger(player->x, player->y);
    }

    Scene::update(deltaTime);
}

Continuous Effects

For continuous effects like fire or smoke:

class FireEffect : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::particles::ParticleEmitter* fire;
    unsigned long emitTimer = 0;
    const unsigned long EMIT_INTERVAL_MS = 50; // Emit every 50ms

public:
    FireEffect(float x, float y)
        : Entity(x, y, 1, 1, pixelroot32::core::EntityType::GENERIC) {
        setRenderLayer(1);

        fire = new pixelroot32::graphics::particles::ParticleEmitter(
            x, y,
            pixelroot32::graphics::particles::ParticlePresets::Fire()
        );
    }

    void update(unsigned long deltaTime) override {
        // Emit particles continuously
        emitTimer += deltaTime;
        if (emitTimer >= EMIT_INTERVAL_MS) {
            emitTimer -= EMIT_INTERVAL_MS;
            fire->burst(x, y, 2); // 2 particles per interval
        }

        fire->update(deltaTime);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        fire->draw(renderer);
    }
};

Custom Particle Effects

Create your own particle effects by customizing ParticleConfig:

Magic Spell Effect

pixelroot32::graphics::particles::ParticleConfig magicConfig;

// Magical colors (purple to cyan)
magicConfig.startColor = pixelroot32::graphics::Color::Purple;
magicConfig.endColor = pixelroot32::graphics::Color::Cyan;

// Medium lifetime
magicConfig.lifetime = 0.8f;

// Outward spread
magicConfig.speed = 60.0f;
magicConfig.speedVariation = 20.0f;
magicConfig.direction = 0.0f; // Right
magicConfig.directionVariation = 360.0f; // Full circle

// Slight upward float
magicConfig.gravity = -30.0f;

// Low friction (floaty)
magicConfig.friction = 0.92f;

// Size
magicConfig.startSize = 2;
magicConfig.endSize = 1;

Rain Effect

pixelroot32::graphics::particles::ParticleConfig rainConfig;

// Rain color (light blue)
rainConfig.startColor = pixelroot32::graphics::Color::LightBlue;
rainConfig.endColor = pixelroot32::graphics::Color::LightBlue;

// Long lifetime
rainConfig.lifetime = 2.0f;

// Downward movement
rainConfig.speed = 150.0f;
rainConfig.speedVariation = 20.0f;
rainConfig.direction = 270.0f; // Down
rainConfig.directionVariation = 5.0f; // Slight angle variation

// Downward gravity
rainConfig.gravity = 200.0f;

// No friction
rainConfig.friction = 1.0f;

// Small size
rainConfig.startSize = 1;
rainConfig.endSize = 1;

Best Practices

Performance

  • Limit particle count: Each emitter has MAX_PARTICLES_PER_EMITTER (50)
  • Reuse emitters: Don't create new emitters every frame
  • Disable when not visible: Set isVisible = false when off-screen
  • Limit active emitters: Too many emitters can impact performance

Visual Design

  • Match game style: Particle effects should fit your game's aesthetic
  • Use appropriate colors: Match particle colors to game palette
  • Test on hardware: ESP32 may render particles differently
  • Keep it simple: Simple effects often look better than complex ones

Timing

  • Burst timing: Space out bursts for better visual effect
  • Continuous effects: Use timers to control emission rate
  • Lifetime: Adjust lifetime to match effect duration
  • Cleanup: Particles automatically clean up when lifetime expires

Common Patterns

One-Shot Effect

class OneShotEffect : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::particles::ParticleEmitter* emitter;
    bool hasEmitted = false;

public:
    void trigger(float x, float y) {
        if (!hasEmitted) {
            emitter->burst(x, y, 20);
            hasEmitted = true;
        }
    }

    void reset() {
        hasEmitted = false;
    }
};

Attached Effect

class AttachedParticleEffect : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::particles::ParticleEmitter* emitter;
    pixelroot32::core::Actor* target;

public:
    void update(unsigned long deltaTime) override {
        // Update emitter position to follow target
        emitter->x = target->x;
        emitter->y = target->y;

        // Emit particles
        emitter->burst(target->x, target->y, 1);

        emitter->update(deltaTime);
    }
};

Troubleshooting

Particles Not Appearing

  • Verify emitter is added to scene
  • Check particle config is valid
  • Ensure burst() is being called
  • Verify emitter position is on-screen

Performance Issues

  • Reduce particle count per burst
  • Limit number of active emitters
  • Use simpler particle configs
  • Disable emitters when not visible

Particles Not Moving

  • Check gravity value (positive = down, negative = up)
  • Verify speed is not 0
  • Check friction isn't too high (1.0 = no movement)
  • Ensure direction is correct (degrees: 0=right, 90=up, 180=left, 270=down)

Next Steps

Now that you understand particles, you've completed the advanced graphics section. Continue with: - Performance Optimization - Optimize your game - Memory Management - Manage memory efficiently - API Reference - Complete API documentation


See also: - API Reference - ParticleEmitter - API Reference - ParticleConfig - API Reference - ParticlePresets - Manual - Basic Rendering