Skip to main content

Your First Rive Script

No Programming Experience? You're in the Right Place!

This page teaches Rive scripting from scratch. We'll explain every concept as we go - no prior knowledge needed.

Already comfortable with programming? Skip to How Rive Scripts Work for the fast-track version with JavaScript/After Effects comparisons.


What is a Script?

A script is a set of instructions that tells a computer what to do. Think of it like a recipe:

RecipeScript
"Add 2 cups of flour""Set the circle size to 50"
"Stir for 3 minutes""Move the shape right by 10 pixels"
"Repeat until smooth""Keep animating until stopped"

The difference? A recipe is for humans. A script is written in a programming language that computers understand.

Rive uses a language called Luau (pronounced "loo-ow"). It's designed to be readable and beginner-friendly.

Q:What is a script?

Storing Information: Variables

Imagine you have labeled boxes where you can store things:

  • A box labeled "score" containing the number 0
  • A box labeled "playerName" containing the text "Alex"
  • A box labeled "isGameOver" containing false (meaning "no")

In programming, these labeled boxes are called variables. Here's how you create them in Luau:

local score = 0
local playerName = "Alex"
local isGameOver = false

Let's break down local score = 0:

PartMeaning
local"I'm creating a new variable"
scoreThe name of the variable (the label on the box)
="Put this value inside"
0The value being stored

Why "local"? It tells Luau this variable belongs to this specific part of your script. Always use local when creating variables.

✏️Fill in the Blanks
-- Create a variable named "health" with value 100
 health = 100

-- Create a variable named "name" with text "Hero"
local  = "Hero"

Types of Values

Variables can hold different types of values:

TypeWhat it isExamples
numberAny numeric value42, 3.14, -10, 0
stringText (always in quotes)"Hello", "Player 1", ""
booleanTrue or falsetrue, false
local age = 25           -- number
local greeting = "Hi!" -- string (notice the quotes)
local isReady = true -- boolean
Why Does This Matter?

Rive needs to know what type of value you're working with. You can't do math with text, and you can't display a true/false as a name. Types help prevent mistakes.

Q:What type of value is: "Game Over"
Q:What type of value is: false

Reusable Instructions: Functions

Sometimes you want to run the same instructions multiple times. Instead of writing them over and over, you put them in a function - a named block of instructions you can reuse.

Think of a function like a recipe card with a name:

-- Define a function named "sayHello"
local function sayHello()
print("Hello there!")
print("Welcome to Rive!")
end

This creates a recipe called sayHello. The instructions inside (between function and end) don't run yet - they're just saved for later.

To actually run those instructions, you call the function:

sayHello()  -- This runs the instructions inside

The () after the name means "run this function now."

Q:What does 'calling a function' mean?

Functions That Take Input

Functions become more powerful when they can receive information. These inputs are called parameters:

local function greet(name)
print("Hello, " .. name .. "!")
end

greet("Alex") -- Prints: Hello, Alex!
greet("Jordan") -- Prints: Hello, Jordan!

Here, name is a parameter - a placeholder that gets filled in when you call the function.

You can have multiple parameters:

local function add(a, b)
print(a + b)
end

add(5, 3) -- Prints: 8
add(10, 20) -- Prints: 30
✏️Fill in the Blanks
-- A function that takes a number and doubles it
local function double()
  print(x * 2)
end

-- Call the function with the value 5
(5)  -- Prints: 10

Functions That Give Back Values

Some functions calculate something and return the result:

local function double(x)
return x * 2
end

local result = double(5) -- result is now 10
print(result) -- Prints: 10

The return keyword sends a value back to wherever the function was called.

Q:What does 'return' do in a function?

Quick Review: The Building Blocks

Before we look at Rive scripts, let's make sure we've got the basics:

ConceptWhat it isExample
VariableA named container for storing valueslocal score = 0
StringText data (in quotes)"Hello"
NumberNumeric data42 or 3.14
BooleanTrue or falsetrue or false
FunctionA reusable block of instructionslocal function doSomething() ... end
ParameterInput that a function receivesfunction greet(name)
ReturnValue that a function sends backreturn x * 2
Q:Which line correctly creates a variable named 'speed' with value 100?

Now Let's Look at a Rive Script

Here's the simplest possible Rive script. Don't worry - we'll explain every line:

--!strict

export type MyScript = {}

function init(self: MyScript): boolean
print("Script started!")
return true
end

function draw(self: MyScript, renderer: Renderer)
-- Drawing code goes here
end

return function(): Node<MyScript>
return {
init = init,
draw = draw,
}
end

That's a lot of code to do almost nothing! But each part has a purpose. Let's go through them one by one.


Part 1: Strict Mode

--!strict

This line turns on strict mode - a helper that checks your code for mistakes before it runs.

  • -- normally starts a comment (text the computer ignores)
  • But --!strict is special - it's an instruction to enable checking

Think of it like a spell-checker for your code. It underlines problems before you even run the script.

tip

Strict mode is optional but highly recommended. It catches typos and mistakes early!

Q:What does --!strict do?

Part 2: Defining Your Data

export type MyScript = {}

This line describes what data your script will store. Right now the {} is empty, but you'd add your variables here:

export type MyScript = {
score: number, -- I'll store a number called "score"
playerName: string, -- I'll store text called "playerName"
}

Why do this? Two reasons:

  1. Documentation - Anyone reading your code knows what data it uses
  2. Error checking - Strict mode can warn you if you try to use data you didn't declare

The export keyword makes this type definition visible to Rive.

✏️Fill in the Blanks
-- Define a script type that stores:
-- - a number called "health"
-- - a boolean called "isAlive"

export type Player = {
  health: ,
  isAlive: ,
}

Part 3: The Init Function (Runs Once)

function init(self: MyScript): boolean
print("Script started!")
return true
end

This function runs once when your script starts. It's where you set up initial values.

Let's decode the first line:

PartMeaning
function initWe're defining a function called "init"
(self: MyScript)It receives your script's data as "self"
: booleanIt must return true or false

What is self? It's your script's personal storage - all the variables you defined in your type. You access them with self.variableName.

Why return true or false?

  • return true = "Everything is fine, keep running"
  • return false = "Something went wrong, stop the script"
Q:When does the init function run?
Q:What does 'return true' mean in init?

Part 4: The Draw Function (Renders Graphics)

function draw(self: MyScript, renderer: Renderer)
-- Drawing code goes here
end

This function is called when Rive needs to display your graphics. The renderer parameter is your tool for drawing shapes.

Important rules for draw:

  • Only draw things here - don't change your data
  • It might be called multiple times per second
  • Even if you're not drawing anything, this function must exist
Q:What is the draw function for?

Part 5: The Factory Function (Creates Instances)

This is the trickiest part. Bear with me:

return function(): Node<MyScript>
return {
init = init,
draw = draw,
}
end

Why is this necessary?

Imagine you attach your script to 5 different shapes. Each shape needs its own separate data. If they shared the same variables, changing one would change all of them!

The factory function creates a fresh copy of your script for each shape. Every time Rive needs a new instance, it calls this function to get one.

Think of it like a cookie cutter:

  • The factory function is the cookie cutter
  • Each shape gets its own fresh cookie (instance)
  • They all look the same but are separate cookies
Q:Why does a Rive script need a factory function?

Putting It All Together

Now let's arrange these parts in the correct order:

🧩Arrange the Script Parts

Put these parts in the order they appear in a Rive script:

Drag and drop to arrange the code blocks in the correct order:

Exercise: Your First Working Script ⭐

Premise

Now it's time to create a real script in Rive! This exercise walks you through the complete process: creating a script, attaching it to a shape, and seeing it run.

Goal

By the end of this exercise, you will have created and run your first Rive script, seen console output, and understood what happens when something goes wrong.

Use Case

This is the foundation for everything you'll build in Rive scripts.

What you'll learn:

  • How to create a Node Script in Rive
  • How to attach a script to a shape
  • How to see console output
  • What errors look like in the Problems panel

Setup

In Rive Editor:

  1. Create a new file (or use an existing one)

  2. Draw any shape:

    • Use the Rectangle or Ellipse tool to draw a shape on the Artboard
    • This gives your script something to attach to
  3. Create the script:

    • Go to the Assets panel (left side)
    • Click + → Script → Node Script
    • Name it MyFirstScript
  4. Open the Console:

    • Menu: View → Console
    • This is where print() messages appear
  5. Open the Problems panel:

    • Menu: View → Problems
    • This is where errors appear

Starter Code

Copy and paste this entire script into the Rive script editor:

--!strict

export type MyFirstScript = {
message: string,
counter: number,
}

function init(self: MyFirstScript): boolean
-- This runs once when the script starts
print("=== MY FIRST SCRIPT ===")
print("init() is running!")
print("Message: " .. self.message)

self.counter = 0

print("Setup complete!")
print("ANSWER: success")
return true
end

function advance(self: MyFirstScript, elapsed: number): boolean
-- This runs every frame
self.counter = self.counter + 1

-- Only print for the first 3 frames (to avoid spam)
if self.counter <= 3 then
print("advance() frame #" .. self.counter)
end

return true
end

function draw(self: MyFirstScript, renderer: Renderer)
-- This runs when graphics need to be drawn
-- We're not drawing anything yet - that comes later!
end

return function(): Node<MyFirstScript>
return {
init = init,
advance = advance,
draw = draw,
message = "Hello from Rive!",
counter = 0,
}
end

Assignment

Complete these steps:

  1. Paste the code into your Node Script
  2. Attach the script to your shape:
    • Select your shape on the Artboard
    • In the Inspector (right panel), find "Scripts"
    • Click + and select MyFirstScript
  3. Press Play (the ▶ button or spacebar)
  4. Check the Console - you should see the messages from print()
  5. Find the ANSWER: line and copy it into the validator

Expected Output

Your Console should show something like:

=== MY FIRST SCRIPT ===
init() is running!
Message: Hello from Rive!
Setup complete!
ANSWER: <your result>
advance() frame #1
advance() frame #2
advance() frame #3

Copy the ANSWER: line from your Console into the validator below.


Verify Your Answer

Verify Your Answer

What If Something Goes Wrong?

Let's intentionally break the script to see what errors look like!

Try This: Remove return true

  1. In your init function, delete the line return true
  2. Check the Problems panel

You'll see an error like:

Type 'nil' could not be converted into 'boolean'

This is the type checker telling you: "You said init returns a boolean, but you're not returning anything!"

Fix it: Add return true back.


Try This: Misspell a Variable

  1. Change self.counter to self.countr (typo!)
  2. Check the Problems panel

You'll see:

Key 'countr' not found in type 'MyFirstScript'

The type checker caught your typo before you even ran the script!

Fix it: Correct the spelling back to self.counter.


Checklist

  • Script created and named
  • Script attached to a shape
  • Pressed Play and script ran
  • Console shows the expected messages
  • ANSWER: line appears in Console and validated successfully
  • (Bonus) Tried breaking the script and saw errors in Problems panel

Key Points

  • print() sends messages to the Console - essential for debugging
  • init() runs once at startup - you'll see its messages first
  • advance() runs every frame - limit your prints to avoid spam!
  • Problems panel shows errors before you run - the type checker is your friend
  • return true in init means "everything worked, keep running"

New thing: advance - This function runs repeatedly (every animation frame) for updating values over time. We'll cover this more in future lessons.


Knowledge Check

Q:What does 'self' represent in a Rive script?
Q:Which parts are REQUIRED in every Node script?

Summary: The Script Pattern

Every Rive script follows this pattern:

  1. --!strict - Enable error checking (recommended)
  2. export type - Declare what data your script stores
  3. function init - Set up initial values (runs once)
  4. function draw - Render graphics (runs on repaint)
  5. return function() - Factory that creates instances

Once this pattern clicks, you'll recognize it in every Rive script!


What's Next?

You've completed the beginner introduction! You now understand the core concepts: variables, types, functions, and the structure of Rive scripts.

Continue to: Variables, Types & Operators

This takes you into Part 2: Luau Fundamentals, where you'll deepen your understanding of the language and write more sophisticated scripts.

Optional Reference

The How Rive Scripts Work page covers the same script structure concepts but with JavaScript and After Effects comparisons. You can reference it later if you want to see how Rive concepts map to other languages — but it's not required for your learning path.