Skip to main content

Overview

The ParticleEngine class manages all particle effects in the game, including rendering, updating, and managing different types of particles. It supports multiple texture layers and handles particle lifecycle management. Header: Minecraft.Client/ParticleEngine.h

Class Definition

class ParticleEngine

Constants

Particle Limits

static const int MAX_PARTICLES_PER_LAYER = 200;
static const int MAX_DRAGON_BREATH_PARTICLES = 1000;
Maximum number of particles allowed per texture layer. Dragon breath particles have a higher limit.

Texture Types

static const int MISC_TEXTURE = 0;                // General particle texture
static const int TERRAIN_TEXTURE = 1;             // Block/terrain particles
static const int ITEM_TEXTURE = 2;                // Item particles
static const int ENTITY_PARTICLE_TEXTURE = 3;     // Entity-related particles
static const int DRAGON_BREATH_TEXTURE = 4;       // Dragon breath effect

static const int TEXTURE_COUNT = 5;               // Total texture types
Particles are organized by texture type for efficient batch rendering.

Constructor and Destructor

ParticleEngine(Level *level, Textures *textures);
~ParticleEngine();
Parameters:
  • level - The level/world the particles exist in
  • textures - Texture manager for particle textures
Example:
ParticleEngine* particles = new ParticleEngine(level, textures);

Core Methods

add

void add(shared_ptr<Particle> p);
Adds a particle to the engine. Automatically manages particle limits per layer. Parameters:
  • p - Shared pointer to the particle to add
Example:
// Create and add a particle
shared_ptr<Particle> particle = make_shared<ExplodeParticle>(
    level, x, y, z, xVel, yVel, zVel
);
particleEngine->add(particle);
Behavior:
  • If layer is full, removes oldest particle first (FIFO)
  • Supports higher limit for dragon breath particles
  • Automatically determines texture layer from particle type

tick

void tick();
Updates all active particles. Called once per game tick (20 times per second). Operations:
  • Calls tick() on each particle
  • Removes particles marked as removed
  • Handles all three dimension layers (Overworld, Nether, End)
Example:
// In game loop (20 ticks per second)
if (shouldTick) {
    particleEngine->tick();
}

render

void render(shared_ptr<Entity> player, float a);
Renders all particles from the player’s perspective. Parameters:
  • player - Player entity (for camera positioning)
  • a - Interpolation alpha for smooth rendering (0.0-1.0)
Example:
particleEngine->render(player, partialTick);
Rendering Details:
  • Binds appropriate texture for each particle layer
  • Uses Tesselator for efficient batch rendering
  • Calculates camera-relative positions for billboarding
  • Handles different textures: particles, terrain, items

renderLit

void renderLit(shared_ptr<Entity> player, float a);
Renders particles that are affected by world lighting. Parameters:
  • player - Player entity
  • a - Interpolation alpha

Level Management

setLevel

void setLevel(Level *level);
Changes the level/world for the particle engine. Parameters:
  • level - New level to associate with
Example:
// When changing dimensions or loading new world
particleEngine->setLevel(newLevel);

Block-Specific Particles

destroy

void destroy(int x, int y, int z, int tid, int data);
Creates block destruction particles when a block is broken. Parameters:
  • x, y, z - Block position
  • tid - Tile/block ID
  • data - Block data/metadata value
Example:
// When player breaks a block
particleEngine->destroy(blockX, blockY, blockZ, Tile::stone->id, 0);

crack

void crack(int x, int y, int z, int face);
Creates crack particles when hitting a block. Parameters:
  • x, y, z - Block position
  • face - Face of block that was hit (0-5)
Example:
// When player hits a block
particleEngine->crack(blockX, blockY, blockZ, hitFace);

Statistics

countParticles

wstring countParticles();
Returns a string with particle count statistics for debugging. Returns: Wide string with format like “P: 45/600” (current/max) Example:
wstring stats = particleEngine->countParticles();
// stats = "P: 45/600"

Implementation Details

Internal Storage

private:
    // 3 dimension layers × 5 texture types
    deque<shared_ptr<Particle>> particles[3][TEXTURE_COUNT];
Particles are stored in a 2D array:
  • First dimension: Level/dimension (0=Overworld, 1=Nether, 2=End)
  • Second dimension: Texture type (MISC, TERRAIN, ITEM, etc.)
This organization allows efficient rendering by texture batching.

Rendering Implementation

// Simplified rendering logic from ParticleEngine.cpp
void ParticleEngine::render(shared_ptr<Entity> player, float a) {
    // Get camera vectors for billboarding
    float xa = Camera::xa;
    float za = Camera::za;
    float xa2 = Camera::xa2;
    float za2 = Camera::za2;
    float ya = Camera::ya;
    
    // Calculate particle offset for camera-relative rendering
    Particle::xOff = player->xOld + (player->x - player->xOld) * a;
    Particle::yOff = player->yOld + (player->y - player->yOld) * a;
    Particle::zOff = player->zOld + (player->z - player->zOld) * a;
    
    // Get correct dimension layer
    int layer = level->dimension->id == 0 ? 0 : 
                (level->dimension->id == -1 ? 1 : 2);
    
    // Render each texture type
    for (int textureType = 0; textureType < TEXTURE_COUNT; textureType++) {
        if (particles[layer][textureType].empty()) continue;
        
        // Bind appropriate texture
        switch (textureType) {
            case MISC_TEXTURE:
            case DRAGON_BREATH_TEXTURE:
                textures->bindTexture(TN_PARTICLES);
                break;
            case TERRAIN_TEXTURE:
                textures->bindTexture(TN_TERRAIN);
                break;
            case ITEM_TEXTURE:
                textures->bindTexture(TN_GUI_ITEMS);
                break;
        }
        
        // Batch render all particles with this texture
        Tesselator *t = Tesselator::getInstance();
        t->begin();
        
        for (auto& particle : particles[layer][textureType]) {
            particle->render(t, a, xa, ya, za, xa2, za2);
        }
        
        t->end();
    }
}

Common Particle Types

While ParticleEngine manages all particles, individual particle types include:
  • ExplodeParticle - Explosion effects
  • FlameParticle - Fire and flame
  • SmokeParticle - Smoke effects
  • WaterDropParticle - Water dripping
  • TerrainParticle - Block break/hit particles
  • ItemParticle - Item pickup/use particles
  • HeartParticle - Animal breeding hearts
  • CritParticle - Critical hit stars

Usage Example

// Initialize particle engine
ParticleEngine* particles = new ParticleEngine(level, textures);

// Game loop
void update() {
    // Update particles (every tick)
    particles->tick();
}

void render(float partialTick) {
    // Render particles
    particles->render(player, partialTick);
}

// Create particles
void onBlockBreak(int x, int y, int z) {
    // Create destruction particles
    particles->destroy(x, y, z, blockType, blockData);
}

void onPlayerHit(int x, int y, int z, int face) {
    // Create crack particles
    particles->crack(x, y, z, face);
}

// Add custom particle
void spawnCustomParticle(double x, double y, double z) {
    auto particle = make_shared<ExplodeParticle>(
        level, x, y, z,
        0.0, 0.1, 0.0  // upward velocity
    );
    particles->add(particle);
}

// Change dimension
void onDimensionChange(Level* newLevel) {
    particles->setLevel(newLevel);
}

// Debug information
void renderDebug() {
    wstring count = particles->countParticles();
    // Display particle count on screen
}

Performance Considerations

Particle Limits: The particle system automatically manages limits per layer (200 standard, 1000 for dragon breath). Oldest particles are removed when limits are exceeded, ensuring consistent performance.
Batch Rendering: Particles are batched by texture type for optimal performance. Avoid creating custom particles that frequently switch textures.

Optimization Tips

  1. Texture Batching: The engine groups particles by texture for efficient rendering
  2. Dimension Separation: Each dimension has separate particle storage
  3. FIFO Removal: Oldest particles are removed first when limits are hit
  4. Deque Storage: Uses std::deque for efficient add/remove operations

Multi-Dimension Support

// Particles are separated by dimension:
// - Layer 0: Overworld (dimension ID 0)
// - Layer 1: Nether (dimension ID -1)
// - Layer 2: End (dimension ID 1+)

int layer = level->dimension->id == 0 ? 0 : 
            (level->dimension->id == -1 ? 1 : 2);
This separation ensures particles in one dimension don’t affect another.
  • LevelRenderer - Main level rendering system
  • GameRenderer - Rendering coordinator
  • Particle - Base particle class
  • Tesselator - Vertex batching for rendering
  • Camera - Camera orientation for billboarding

Build docs developers (and LLMs) love