Scripting

How to Build a Dynamic Particle System in Rive Using Scripting & AI Agent

Dec 23, 2025

|

10

min read

Full Video Tutorial

Over the past 2.5 years working with Rive, I loved every moment of it.
Almost.

Every now and then, I needed something like confetti, sparks, or any animation made of lots of small moving parts - and I kept running into the same frustration.

Because Rive is lightweight and intentionally simple, there’s no built-in particle system or plugins to rely on.

That meant duplicating timelines, animating everything manually, and endlessly tweaking details that never quite felt right.

When Rive introduced scripting and the AI Agent, the mindset finally shifted.

Instead of animating particles one by one, I could build a smart, dynamic system to do the work for me.

This article breaks down how that system was built from scratch — using scripting and the AI Agent, not presets or plugins.

The Mental Model

Before writing any code, it’s important to reframe how particles work in Rive.

Artboard = Particle
Script = Particle System

Each particle is represented by a small Artboard — a visual prefab.
The script doesn’t draw shapes.

It creates, positions, moves, and recycles Artboard instances.

This separation gives us:

  • Visual control in Design Mode

  • Behavioral control in Scripting

  • A system that’s easy to extend later

Once this mental model clicks, everything else becomes much simpler.

Creating the Particle Artboard

We start by creating a tiny Artboard that represents a single snowflake.

  • Size: 24×24

  • Simple white ellipse

  • Origin centered

  • Defined as a Component

This Artboard has no logic, no animation, no awareness of the system - that’s intentional.

Using an artboard for the particles let's us make the particle dynamic so we can later replace it with: a different snowflake, a raindrop, a star, or any other visual without touching the script at all.

Introducing the Script

We then create a Node Script, which becomes the brain of the system.

The script is responsible for:

  • spawning particles

  • updating their position every frame

  • applying speed, wind, scale, and parallax

  • recycling particles when they leave the screen

The particle Artboard is passed into the script as an Artboard input.

This is a key design choice:

the script doesn’t care what the particle looks like — only how it behaves.

// Define the script's data and inputs.
type MyNode = {
  flake: Input<Artboard>,
  }

Working with the Rive AI Agent

One of the most important parts of this process wasn’t just scripting - it was how the scripting was built.

Instead of writing the entire system upfront, I used the Rive AI Agent to iterate on the logic step by step.

The Agent was especially useful for:

  • adding one behavior at a time (falling, looping, wind, scale, parallax)

  • modifying existing logic without breaking the system

  • refining math and edge cases quickly

  • keeping the code readable and intentional

Rather than “generate everything”, the Agent acted like a pair programmer.

I described what the system should do next, tested it visually, and layered complexity gradually — exactly how we normally design motion.

The Agent doesn’t replace understanding.

It accelerates iteration.

Drawing and Updating a Single Particle

One of the most important parts of this process wasn’t just scripting - it was how the scripting was built.

Instead of writing the entire system upfront, I used the Rive AI Agent to iterate on the logic step by step.

The Agent was especially useful for:

  • adding one behavior at a time (falling, looping, wind, scale, parallax)

  • modifying existing logic without breaking the system

  • refining math and edge cases quickly

  • keeping the code readable and intentional

Rather than “generate everything”, the Agent acted like a pair programmer.

I described what the system should do next, tested it visually, and layered complexity gradually — exactly how we normally design motion.

The Agent doesn’t replace understanding.

It accelerates iteration.

Making the System Screen-Aware

To make the system reusable, the script needs to know the screen size.

We expose these inputs:

  • screenWidth

  • screenHeight

This allows the system to:

  • spawn particles correctly

  • loop them outside screen bounds

  • adapt to different layouts

Later, these values can be bound to a ViewModel instead of being hardcoded.

// Make sure the system is working by centering a single particle.
Modify the existing SnowParticles factory script.

Center the Flake artboard in the middle of the screen.

Use the existing variables:
- screenWidth
- screenHeight

Set the Flake position so it is centered on both X and Y based on these values

Falling Motion and Seamless Looping

Now we add the core behavior:

particles fall from above the screen to below it, then loop.

Key rules:

  • particles start outside the visible area

  • they move downward over time

  • once they fully exit the bottom, they respawn at the top

This prevents visible popping and creates a seamless, infinite loop.

// Create the base falling single particle animation
Add a looping falling animation to the Flake artboard.

Requirements:
- The animation should loop continuously.
- Duration: 3 seconds.
- Start the Flake above the top edge of the screen (outside bounds).
- End the Flake below the bottom edge of the screen (outside bounds).
- Use Ease In for the motion.
- When the animation loops, the Flake should jump back to the starting position above the screen, without visible popping

Scaling Up with Particle Count

A single falling particle isn’t very interesting.

We introduce particlesCount and let the script:

  • spawn multiple particles

  • randomize their starting X and Y positions

  • stagger their timing

Each particle behaves independently, but follows the same rules.
This is the moment where we move from an animation to a particle system.

// Turn one snowflake into a particle system.
Modify the existing SnowParticles script to spawn multiple flakes based on `particlesCount`.

Requirements:
- Create `particlesCount` instances of the Flake artboard.
- Each particle uses the existing 3-second looping falling animation.
- Randomize X position across screen width.
- Randomize Y offset so particles are staggered and not synchronized.

Loop behavior:
- When a particle exits the bottom of the screen:
  - Reset it to the top (outside bounds)
  - Re-randomize its X position

Speed as a System Control

Next, we add speedFactor.

It behaves exactly like scaling a timeline:

  • 1 → normal speed

  • 2 → twice as fast

  • 0.5 → half speed

Speed affects the entire system uniformly, not individual particles.

// Control animation speed dynamically using an external input
Use `speedFactor` as a multiplier for the falling animation timing.

Rules:
- speedFactor = 1 normal speed
- speedFactor = 2 2x faster
- speedFactor = 0.5 half speed

Clamp the value:
- minimum = 0.1
- maximum = 3

Apply this uniformly to all particles.
Do not modify any other behavior

Adding Wind

To make the motion feel natural, we add wind.

Instead of using raw angles, wind is normalized between -1 and 1:

  • -1 → wind to the left

  • 0 → no wind

  • 1 → wind to the right

This value is mapped to a small angle (±15°) and used for:

  • slight rotation

  • horizontal drift

Normalization keeps the system predictable and easy to bind to data.

// Add sideways motion for wind effect and tilt using a single normalized input.
Use the existing `wind` input, normalized between -1 and 1.

Interpret the value as:
- wind = -1 blowing left
- wind = 0 no wind
- wind = 1 blowing right

Map the value to a maximum rotation of ±15 degrees.

Apply:
- Rotation based on wind direction
- Horizontal drift using the sine of the angle

Keep the falling animation logic unchanged

Expanding the Spawn Range

Once wind is applied, particles can drift sideways.

Instead of correcting positions with offsets, we simply expand the horizontal spawn range to about 120% of the screen width.

Particles can start slightly outside the screen and naturally drift into view.

This keeps the logic simple and avoids artificial corrections.

// Avoid empty gaps when wind pushes particles sideways.
Expand the horizontal spawn range so particles can enter naturally with wind.

Requirements:
- Start X at: -0.1 * screenWidth
- End X at:  1.1 * screenWidth

Allow particles to spawn outside the visible screen.
Do not change falling speed, rotation logic, or other behavior

Size Variation with Scale Levels

Uniform particles feel artificial.

We introduce scaleLevels — a discrete control (1–5) that defines how many size variants exist.

Examples:

  • 1 → all particles same size

  • 3 → small / medium / large

  • 5 → full depth range

Each particle randomly picks from predefined size sets.
This creates controlled variation without chaos.

// Create natural variation in particle sizes.

Add a new input called `scaleLevels` (1–5) to control size variety.

Rules:
- scaleLevels = 1  [1]
- scaleLevels = 2  [1, 0.5]
- scaleLevels = 3  [0.5, 0.75, 1]
- scaleLevels = 4  [0.4, 0.6, 0.8, 1]
- scaleLevels = 5  [0.2, 0.4, 0.6, 0.8, 1]

When spawning or respawning a particle:
- Randomly select a scale from the list
- Store it per particle
- Apply it during rendering

Keep all other behavior unchanged

Depth with Parallax

With a boolean useParallax, we add depth.

  • larger particles move slightly faster

  • smaller particles move slightly slower

This creates a convincing sense of depth: near elements feel closer, far elements feel distant.

Parallax is optional — the system works without it.

// Add depth: near particles move faster, far ones move slower.
Add a depth effect using the existing boolean input `useParallax`.

Behavior:
- If useParallax = true:
  - Larger particles move faster
  - Smaller particles move slower
  - Keep the effect subtle
- If useParallax = false:
  - All particles move at the same speed

Parallax should be proportional to particle scale.
Do not modify any other logic

One Script, Multiple Particle Systems

This is where the system really shines.

Because:

  • the particle visual is an Artboard input

  • all behavior is driven by parameters

  • the logic is completely generic

the same script can be reused multiple times.

In practice, this means:

  • one instance can drive background snow

  • another can drive heavier foreground flakes

  • another can control falling stars or decorative particles

Each instance:

  • uses a different particle Artboard

  • has different counts, speeds, and scale levels

  • and runs simultaneously on the same screen


You’re not building an effect. You’re building a system.

Why This Matters in Rive

The snow effect shown here is intentionally simple.
Once the foundation exists, extending it becomes trivial:

  • particles can move upward instead of downward

  • new parameters can be added to control behavior

  • special effects and interactions can be layered on top

  • each particle can become a complex animated element — not just an icon or an image

Because particles are Artboards, each one can include:

  • its own animation

  • internal state machines

  • interaction logic

  • or even nested components

This is the real strength of Rive.

You’re not limited to preset effects.

You’re building vector-based, dynamic, interactive systems — designed specifically for your product and your use case.

Final Thoughts

This particle system isn’t about realism.
It’s about control, flexibility, and reuse.

By combining Rive’s scripting feature with the AI Agent, complex behavior can be built incrementally - without losing clarity or visual control.

One script. Many effects.

A reusable foundation.

What's Next?

If you want to learn Rive step by step and see how these systems are built in real projects, you can explore the full course here:

🔗 Rive Masterclass for Designers