Mid-Flight Projectile Dispersion in Matter.js

This article explains how to simulate a dispersing projectile mechanic in the 2D physics engine Matter.js. By launching a primary body and tracking either its flight time, position, or an event trigger, developers can remove the parent entity mid-air and spawn multiple child projectiles with radial velocity vectors. This technique is commonly used in video games for fireworks, multi-stage rockets, and split-shot weapons.

1. Setting Up the Main Projectile

To create a projectile that travels through a physics world, you instantiate a dynamic circular or rectangular body and apply an initial velocity or impulse.

const { Engine, Render, Runner, World, Bodies, Body, Vector, Events } = Matter;

// Create parent projectile
const parentProjectile = Bodies.circle(100, 500, 15, {
    density: 0.004,
    frictionAir: 0.01,
    label: 'parentMunition'
});

// Launch the projectile upward and forward
Body.setVelocity(parentProjectile, { x: 12, y: -18 });
World.add(engine.world, parentProjectile);

2. Triggering Mid-Flight Dispersion

The dispersion event can be triggered via a timed delay (using setTimeout), an altitude check, or when the vertical velocity changes sign (reaching the trajectory apex). A timer offers predictable mid-flight detonation:

const flightDuration = 1200; // Milliseconds before separation

setTimeout(() => {
    disperseChildProjectiles(parentProjectile, 8, 7);
}, flightDuration);

3. Spawning and Dispersing Child Entities

The dispersion function handles removing the original entity from the simulation and creating child particles at the parent's final coordinates. To disperse them outward, calculate radial velocity offsets distributed evenly around a circle (360 degrees) or biased forward along the parent's flight vector.

function disperseChildProjectiles(parent, childCount, dispersionForce) {
    // Only disperse if the parent projectile is still in the world
    if (!parent || !parent.position) return;

    const { x, y } = parent.position;
    const parentVelocity = parent.velocity;

    // Remove the original body from the simulation
    World.remove(engine.world, parent);

    const childProjectiles = [];

    for (let i = 0; i < childCount; i++) {
        // Calculate radial angle for even distribution
        const angle = (i / childCount) * (Math.PI * 2);

        const child = Bodies.circle(x, y, 5, {
            density: 0.002,
            frictionAir: 0.02,
            label: 'childProjectile'
        });

        // Combine parent momentum with outward radial force
        const radialX = Math.cos(angle) * dispersionForce;
        const radialY = Math.sin(angle) * dispersionForce;

        Body.setVelocity(child, {
            x: parentVelocity.x * 0.5 + radialX,
            y: parentVelocity.y * 0.5 + radialY
        });

        childProjectiles.push(child);
    }

    // Add all child bodies to the physics engine
    World.add(engine.world, childProjectiles);
}

4. Cleanup and Performance Considerations

Child projectiles rapidly increase the body count within the engine. To maintain optimal performance, listen to the engine's beforeUpdate event or set secondary timers to remove child bodies after a fixed duration or once they exit the playable screen bounds.