Skip to content

Particle Systems

A particle system, much like the UI, is a Boo script. The script provides certain settings, and is run on each particle in a system.

There is a world-view particle limit, which defaults to 500.

API Docs


There are three different key terms in terms in the particle system:

  • Domain: The entire world's set of particles
  • System: A system of particles, contained in the world, driven by a script
  • Particle: An individual particle

Creating in plugins

To create a system, you inject the IParticleDomain as a Dependency, and use its methods. Primarily the CreateSystem method with a script file resolved.

The returned object is a ref, and if the ref is lost, the particle system will be destroyed. If that is undesirable, you can set Pinned = true.



Empeld has a built-in particle system display. Similar to how you test UI's, simply write

empeld.exe --dev-psys path/to/my/particlesystem.psys

You can then use F5 to refresh, and WSQA to move around the system.


The particle itself is a set of fields, all of which are modifiable by the script.

Variable Type Description
Pos Vector3d The real-world position of the particle
Vel Vector3d The current velocity of the particle
Acc Vector3d The current acceleration of the particle
TexU Vector2 The U coordinate of the given texture. Defaults to (0,0)
TexV Vector2 The V coordinate of the texture. Defaults to (1,1)
Col Rgba The color of the particle. Defaults to white (1,1,1,1)
Size float The size of the particle. Defaults to 1
Life float Current life of the particle. This is set automatically, but can be overriden. Ranges from 0 to 1
Lifespan float The life of a particle, in seconds
Child int The index of this particle as a child. eg. if we were the result of an exploding particle, our child would be 1. Defaults to 0
BlendMode ParticleBlendMode The mode with which the particle is blended (See below)


  * Undefined=0,
  * MaskAdd
  * MaskAlpha
  * Additive
  * Screen
  * Multiply


Unlike the UI scripts, which are duck-typed, the particle scripts have to use explicit types. This is because they need to be high-performance. You can read about explicit typing on the wiki, which you can find a link to on Boo


The script has various variables that can be assigned to it, some of which are optional.

Name Type Required Description
Texture string Yes Relative path to an image of the particle
AudioSpawn SoundDescriptor Sound played when a particle is spawned
AudioChildSpawn SoundDescriptor Sound that's played when a child spawns
AudioDie SoundDescriptor Audio that is played when a particle dies
ParticleLife float Life of a particle in seconds. Default: 5
SystemLife float Life of a system in seconds. Default: Unlimited
SpawnRate float The delay, in seconds, until a new particle is spawned. Default 0.1
PerParticleBillboard bool If false, system computes the billboard angle (fast); if true, billboard is computed for each particle (slow)
Collision bool Whether or not we check for particle collision
DieOnCollision bool If true, particles will die on collision, otherwise, it calls the collide method or defaults to bouncing. Default true
AutoGravity bool If true, gravity is automatically applied to Acc. Default false
Wind float Coefficient of wind affect. Default 1.0


The script also has a set of methods. Each of them have a default implementation, but you can provide them explicitly to customize behavior.

Name Arguments Description
constructor This is just the boo class constructor
Simulate Particle, float Simulate the particle, for an elapsed time of d
Spawn Particle Called when a particle is spawned. Can initialize it here
Die Particle, ret: int Called when a particle dies. Returns the number of children that get spawned as an int
SpawnChild Particle Called when a particle child is spawned
NextSpawn ret: float Return a float to override the delay to the next child spawn
Collide Particle Called when a particle collides with something


class ParticleController:
    Texture = "hardsphere.png"
    ParticleLife = 3f
    SpawnRate = 0.05f
    PerParticleBillboard = true
    AutoGravity = true
    Collision = true
    DieOnCollide = false

    static _rSeed = 1
    r as Random = null

    def constructor():
        r = Random(_rSeed)
        _rSeed += 1

    def _random() as double:
        return r.NextDouble() * 2.0 - 1.0

    def simulate(p as Particle, d as single):
        p.Vel += p.Acc * d
        p.Pos += p.Vel * d
        p.Col = Rgba(1f, 0.8f, 0.8f, 1f - p.Life)

    def collide(p as Particle):
        p.Vel = Vector3d(p.Vel.X, p.Vel.Y, p.Vel.Z * -0.8f)

    def spawn(p as Particle):
        p.Vel = Vector3d(_random()*2f, _random()*2f, 10f+_random()*2f )
        p.Size = 0.05f
        p.Pos = Vector3d(0f, 0f, 0.55f)
class ParticleController:
    Texture = "hardsphere.png"
    #Audio: filename, gain, pitch, variance, min_dist, max_dist
    AudioSpawn = SoundDescriptor("firework_launch.wav", 0.5f, 1f, 0.3f, 2f, 30f)
    AudioChildSpawn = SoundDescriptor("firework_exp.wav", 2f, 1f, 0.3f, 10f, 50f)
    ParticleLife = 2f
    PerParticleBillboard = true

    static _rSeed = 1
    r as Random = null

    def constructor():
        r = Random(_rSeed)
        _rSeed += 1

    def simulate(p as Particle, d as single):
        p.Vel += p.Acc * d
        p.Pos += p.Vel * d

        if p.Child > 0:
            p.Col = Rgba(p.Col.R, p.Col.G, p.Col.B, 1f - p.Life)

    def _random() as double:
        return r.NextDouble() * 2.0 - 1.0

    def _getColor() as Rgba:
        return Rgba(r.NextDouble()*0.6f+0.2f, r.NextDouble()*0.6f+0.2f, r.NextDouble()*0.6f+0.2f, 1f)

    def spawn(p as Particle):
        p.Vel = Vector3d(_random() * 0.4f, _random() * 0.4f, r.NextDouble() * 10f + 25f)
        p.Acc = Vector3d(0f, 0f, -5f)
        p.Col = _getColor()
        p.Size = 0.5f

    def die(p as Particle):
        if p.Child == 0:
            return 30
        return 0

    def spawnChild(p as Particle):
        vInit = 5f
        p.Vel = Vector3d(_random()*vInit, _random()*vInit, _random()*vInit)
        p.Acc = Vector3d(0f, 0f, -2f)
        p.Size = 0.3f
        p.Lifespan = 2f

    def nextSpawn() as single:
        return r.NextDouble() * 3.5f