All articles

Building a Fully Dynamic Particle System in Rive with Scripting & AI Agent

|12 min read

Building a Fully Dynamic Particle System in Rive with Scripting & AI Agent

Rive gives designers a high level of control over interactive animation, especially through state machines, inputs, and data binding. However, it doesn't include a built-in particle system — and that's intentional.

Because Rive is intentionally lightweight and built around dynamic, real-time systems, it doesn't rely on preset effects. As a result, effects like particle systems for confetti, snow, or rain require a custom approach.

Until recently, achieving this kind of behavior required a fair amount of manual setup and offered limited flexibility. With the introduction of scripting and the AI Agent, this approach becomes practical, scalable, and accessible.

This article walks through how a fully dynamic, reusable particle system can be built from scratch in Rive — using scripting to drive behavior and the AI Agent to accelerate development.

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, and no awareness of the system — that's intentional.

Using an Artboard for each particle lets us swap the visual later — snowflakes, raindrops, stars, or any custom shape — without touching the script.

Introducing the Script

We then create a Node Script, which acts as the system's control layer.

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 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.

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. Rather than writing everything by hand, we used the Rive AI Agent as a collaborator.

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.

The system was built by describing each next step, testing it visually, and layering complexity gradually. The Agent doesn't replace understanding. It accelerates iteration.

Drawing and Updating a Single Particle

Before creating a full system, we start by drawing one particle. We:

  • Advance the animation using the advance() function
  • Draw the particle Artboard in the draw() function, update it every frame

At this stage, nothing moves yet. This confirms:

  • The script is connected
  • The Artboard input works
  • The render loop is correct

Building incrementally avoids debugging complex systems too early.

Making the System Screen-Aware

To keep the system reusable, the script needs to be aware of 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.

Prompt — Verify the system works by centering a single particle:

Modify the existing SnowParticles factory script. Center the Flake artboard in the middle of the screen. Use the existing screenWidth and screenHeight. Set the Flake position centered on both X and Y based on these values.

Falling Motion and Seamless Looping

Now we introduce the core behavior: particles fall from above the screen to below it, then loop seamlessly.

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.

Prompt — Create the base falling single particle animation:

Add a looping falling animation to the Flake artboard. Requirements: The animation should loop continuously. Duration: 4 seconds. Start the Flake above the top edge of the screen (outside). End the Flake below the bottom edge of the screen (outside). Use Ease 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 doesn't yet form a system. 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 system rules. This is the moment where we move from a single element to a particle system.

Prompt — 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 looping falling animation. Randomize X position across screen width. Randomize time offset so particles are staggered and not synchronized. Loop: when a particle exits the bottom, reset it to the top (outside) and 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.

Prompt — Control animation speed dynamically:

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 minimum to 0.1. Apply this uniformly to all particles. Do not modify any other behavior.

Adding Wind

To make the motion feel more natural, we introduce 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 and horizontal drift.

Prompt — Add sideways motion for wind effect:

Use the existing wind input, normalized between -1 and 1. Interpret the value as: -1 = blowing left, 0 = no wind, 1 = blowing right. Map the value to a maximum rotation of ±15 degrees. Apply Rotation based on wind direction. Add 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 expand the horizontal spawn range to 120% of the screen width.

Particles can start slightly outside the screen and naturally drift into view. This keeps the logic clean and the result seamless.

Prompt — Expand spawn range for wind coverage:

Expand the horizontal spawn range so particles can enter naturally with wind. Requirements: Start at -0.1 × screenWidth. End at 1.1 × screenWidth. Allow particles to spawn outside the visible screen. Do not change falling speed, rotation logic, or any other behavior.

Size Variation with Scale Levels

Uniform particles feel artificial. We introduce scaleLevels — a discrete control (1–5) that defines how many size tiers exist in the system.

Examples:

  • 1 → All particles same size
  • 3 → Small / medium / large
  • 5 → Full depth range

Each particle randomly selects from predefined size sets, creating controlled variation without chaos.

Prompt — Create natural variation in particle sizes:

Add a new input called scaleLevels to control size variety. Rules: scaleLevels = 1: all particles scale 1.0. scaleLevels = 2: 0.5 or 1.0. scaleLevels = 3: 0.5, 0.75, or 1.0. scaleLevels = 4: 0.4, 0.6, 0.8, or 1.0. scaleLevels = 5: 0.2, 0.4, 0.6, 0.8, or 1.0. When spawning or respawning, randomly select scale from the list. Store per particle. Apply during rendering. Keep all other behavior unchanged.

Depth with Parallax

With a boolean input 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.

Prompt — Add depth with parallax:

Add a depth effect using the existing boolean input useParallax. Behavior: useParallax = true — larger particles move faster, smaller particles move slower. Keep the effect subtle. useParallax = false — all particles move the same speed. Parallax should be proportional to particle scale. Do not modify any other logic.

Making the System Responsive with Data Binding

To make the particle system truly reusable, it needs to adapt to the size of the Artboard automatically.

We expose the screen width and height as inputs and bind them via Data Binding to a simple ViewModel — where the artboard itself is the source of truth.

Once bound, the system becomes fully responsive:

  • Changing the Artboard size immediately updates the particle area
  • Spawn positions and loop boundaries adjust automatically
  • The same script works across different layouts and screen sizes

At this point, the particle system is no longer tied to a fixed canvas — it becomes data-driven and reusable.

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 straightforward:

  • 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
  • Nested components

This highlights one of Rive's core strengths: you're not limited to preset effects. You're building vector-based, dynamic, interactive systems.

Final Thoughts

This particle system is fully dynamic, flexible, customizable, and reusable.

By combining Rive's scripting feature with the AI Agent, complex behavior can be built incrementally — one prompt, one layer, one test at a time.

One script. Many effects. A reusable foundation.

Get more Rive tips

Weekly tutorials, new lessons, and Rive community highlights — no spam.