← Documentation Home

7) Particle Effects (Pro)

Back to Tutorials

Goal: build a responsive spark emitter, tune its behavior, and understand the performance choices that matter at high particle counts.

License: this tutorial requires the Pro GamePhysics feature.


Step 1: Reserve Capacity Once

Dim particles As Integer
particles = CreateParticleSystem(20000)

If particles = 0 Then
    DebugPrint("Could not create particle system")
    Terminate()
End If

Capacity is the maximum number of live particles, not the number created immediately. Storage is fixed up front so emission and simulation do not allocate per particle during gameplay.

Step 2: Describe the Emitter

SetParticleLife(particles, 0.45, 1.1)
SetParticleSpeed(particles, 90.0, 240.0)
SetParticleDirection(particles, -120.0, -60.0)
SetParticleGravity(particles, 0.0, 360.0)
SetParticleSize(particles, 0.6, 1.4)

Each emitted particle randomly chooses values inside these ranges. Negative 90 degrees points upward in screen coordinates; positive Y gravity pulls sparks back down.

Step 3: Emit from an Event

If MouseButton(0) Then
    EmitParticles(particles, 18, MouseX(), MouseY())
End If

If MouseHit(1) Then
    EmitParticles(particles, 500, MouseX(), MouseY())
End If

Continuous input produces a trail; a hit produces a one-shot burst. Emission stops at capacity, so the system remains bounded.

Step 4: Update and Draw Exactly Once Per Frame

UpdateParticles(particles, FrameDelta())

Cls()
SetDrawColor(255, 190, 55)
DrawParticles(particles)
Flip()

Simulation and rendering are separate. Calling each once keeps timing understandable and avoids accidentally doubling work.

Step 5: Add Bouncing Bounds

SetParticleBounds( _
    particles, _
    20, 80, _
    ScreenWidth() - 40, _
    ScreenHeight() - 100, _
    0.65 _
)

The last value controls bounce: 0 removes rebound energy and 1 preserves it.

Step 6: Add an Optional Particle Image

Dim sparkImage As Integer
sparkImage = LoadImage("assets/spark.png")

If sparkImage <> 0 Then
    SetImageHotspot( _
        sparkImage, _
        ImageWidth(sparkImage) / 2, _
        ImageHeight(sparkImage) / 2 _
    )
    SetParticleImage(particles, sparkImage)
End If

Passing image handle 0 uses the fast native primitive renderer. Image particles are richer but carry more rendering work, so use them when the visual difference matters.

Step 7: Use Attraction Deliberately

SetParticleAttraction(particles, 700.0)   ' Attraction
SetParticleAttraction(particles, -700.0)  ' Repulsion
SetParticleAttraction(particles, 0.0)     ' Disabled

Nonzero attraction introduces particle-to-particle forces. The runtime automatically switches between direct and adaptive multipole solving, but zero is still the cheapest choice for ordinary smoke, rain, and sparks.

Checkpoint: Interactive Spark Lab

Graphics(960, 600, False)
SetClsColor(8, 12, 22)

Dim fontRef As Integer
Dim particles As Integer
Dim attractionEnabled As Boolean
Dim running As Boolean

fontRef = LoadFont("", 18)
particles = CreateParticleSystem(20000)
running = True

If particles = 0 Then
    Terminate()
End If

SetParticleLife(particles, 0.45, 1.1)
SetParticleSpeed(particles, 90.0, 240.0)
SetParticleDirection(particles, -120.0, -60.0)
SetParticleGravity(particles, 0.0, 360.0)
SetParticleSize(particles, 0.6, 1.4)
SetParticleBounds(particles, 20, 70, 920, 510, 0.65)

Do
    If MouseButton(0) Then
        EmitParticles(particles, 18, MouseX(), MouseY())
    End If

    If KeyHit(KEY_SPACE) Then
        EmitParticles(particles, 600, MouseX(), MouseY())
    End If

    If KeyHit(KEY_A) Then
        attractionEnabled = Not attractionEnabled
        If attractionEnabled Then
            SetParticleAttraction(particles, 700.0)
        Else
            SetParticleAttraction(particles, 0.0)
        End If
    End If

    If KeyHit(KEY_C) Then
        ClearParticles(particles)
    End If

    UpdateParticles(particles, FrameDelta())

    Cls()
    SetDrawColor(255, 185, 55)
    DrawParticles(particles)
    SetTextColor(235, 240, 255)
    DrawText(fontRef, 18, 16, _
        "Hold mouse: emit | Space: burst | A: attraction | C: clear")
    DrawText(fontRef, 18, 42, _
        "Live particles: " + Str(ParticleCount(particles)))
    Flip()

    If KeyHit(KEY_ESC) Then
        running = False
    End If
Loop Until Not running

FreeParticleSystem(particles)
FreeFont(fontRef)

Performance Checklist

  • Create systems during level setup, not in the frame loop.
  • Choose a realistic capacity and control the emission rate.
  • Prefer primitive particles for dense effects.
  • Leave attraction at zero unless the effect requires long-range interaction.
  • Use ParticleCount while profiling so the current workload is visible.
  • Clear or free systems when changing scenes instead of leaving invisible simulations alive.

Try It Yourself

Create three presets: upward fire, downward rain, and a zero-gravity explosion. Change only configuration values, not the update loop.

Functions Introduced

CreateParticleSystem, EmitParticles, UpdateParticles, DrawParticles, SetParticleBounds, and SetParticleAttraction.


Navigation: Previous | Course outline | Next: Build a Physics Game