Skip to main content

Dynamic Instantiation

Prerequisites

Before this section, complete:

  • Node Protocol — Factory function and lifecycle
  • ViewModels — Data binding for component artboards
  • Tables — Managing collections of instances
  • Iteration — Looping through instance arrays

Spawn and manage Rive components at runtime to build particle systems, object pools, and procedural content.

Rive Context

Rive lets you instance Artboards from script inputs. This is how you build reusable components (particles, enemies, cards, etc.) with real Rive visuals that can be spawned and controlled from code.

Core steps:

  1. Add an Input<Artboard<Data.YourVM>> to reference a component Artboard
  2. Call self.yourInput:instance() to create a new instance
  3. Store instances and call advance() on them in your advance callback, and draw() in your draw callback

Exercise 1: Spawn Multiple Instances ⭐⭐⭐

Premise

Dynamic instantiation creates runtime copies of component artboards. You must call advance() on each instance in your advance callback, and draw() in your draw callback.

Goal

By the end of this exercise, you will be able to Complete the spawn function to create instances, and the advance/draw functions to update and render them.

Use Case

This pattern shows up whenever you build behavior in Rive scripts.

Example scenarios:

  • Debugging script behavior
  • Driving animation logic

Setup

In Rive Editor:

  1. Create the script:

    • Assets panel → + → Script → Node Script
    • Name it Exercise1_Exercise1SpawnMultipleInstances
  2. Attach and run:

    • Attach to any shape and press Play
  3. Open the Console:

    • View → Console

Starter Code

--!strict
-- Spawn and animate multiple artboard instances

type Spawned = {
artboard: Artboard<Data.Star>,
position: Vector,
speed: number,
}

export type SpawnDemo = {
star: Input<Artboard<Data.Star>>,
count: Input<number>,
items: {Spawned},
}

local function spawn(self: SpawnDemo)
-- TODO 1: Create an instance from self.star using :instance()
local instance = nil -- Replace with self.star:instance()

-- TODO 2: Create a Spawned entry with random position and speed
local entry: Spawned = {
artboard = instance,
position = Vector.xy(math.random(-140, 140), math.random(-80, 80)),
speed = math.random(30, 90),
}

-- TODO 3: Add to self.items using table.insert
end

function init(self: SpawnDemo): boolean
self.items = {}
print(`Spawning {self.count} stars...`)

for _ = 1, self.count do
spawn(self)
end

print(`{#self.items} stars spawned!`)
print("ANSWER: spawned")
return true
end

function advance(self: SpawnDemo, seconds: number): boolean
-- TODO 4: Loop through self.items and for each:
-- a) Update position: item.position.x + item.speed * seconds
-- b) Wrap when x > 160 (reset to -160)
-- c) Call item.artboard:advance(seconds)
return true
end

function draw(self: SpawnDemo, renderer: Renderer)
-- TODO 5: Loop through self.items and for each:
-- a) save(), transform with position, draw artboard, restore()
end

return function(): Node<SpawnDemo>
return {
init = init,
advance = advance,
draw = draw,
star = late(),
count = 5,
items = {},
}
end

Assignment

Complete these tasks:

  1. Complete the spawn function to create instances, and the advance/draw functions to update and render them.
  2. Run the script and verify the console output
  3. Copy the ANSWER: line into the validator

Expected Output

Console prints the relevant debug lines for this exercise.
ANSWER: <your result>

Verify Your Answer

Verify Your Answer

Checklist

  • --!strict is at the top
  • All TODOs are replaced with working code
  • Console output includes the ANSWER: line

Editor setup:

  1. Create a component artboard named Star with a Star ViewModel
  2. Select the Node script and assign the star Input to the component artboard

How it works:

  1. init spawns the initial set of instances based on count
  2. advance moves each instance and wraps it when it goes off-screen
  3. draw renders each instance at its current position using transform save/restore

Common Patterns

Object Pooling

For frequently spawned/despawned objects, maintain a pool:

--!strict

export type ObjectPool = {
template: Input<Artboard<Data.Bullet>>,
active: {Artboard<Data.Bullet>},
inactive: {Artboard<Data.Bullet>},
}

local function acquire(self: ObjectPool): Artboard<Data.Bullet>
if #self.inactive > 0 then
local instance = table.remove(self.inactive)
table.insert(self.active, instance)
return instance
end
local instance = self.template:instance()
table.insert(self.active, instance)
return instance
end

local function release(self: ObjectPool, instance: Artboard<Data.Bullet>)
for i, active in ipairs(self.active) do
if active == instance then
table.remove(self.active, i)
table.insert(self.inactive, instance)
break
end
end
end

function init(self: ObjectPool): boolean
self.active = {}
self.inactive = {}
return true
end

function draw(self: ObjectPool, renderer: Renderer)
for _, instance in ipairs(self.active) do
instance:draw(renderer)
end
end

return function(): Node<ObjectPool>
return {
init = init,
draw = draw,
template = late(),
active = {},
inactive = {},
}
end

Key Takeaways

  • Dynamic instantiation bridges designer-built visuals with procedural logic
  • Use Input<Artboard<Data.YourVM>> to reference component artboards
  • Call :instance() to create new instances at runtime
  • Remember to call advance() and draw() on each instance in your respective callbacks
  • Use object pooling for frequently spawned/despawned objects

Q:What method creates a new instance from an artboard input?

Next Steps