Lesson 1.2: Control Flow & Loops
Learning Objectives
By the end of this lesson, you will:
- Write
if/then/elsestatements to make decisions - Use inline conditional expressions for cleaner code
- Create loops with
for,while, andrepeat - Understand when to use each type of loop
- Control loop execution with
breakandcontinue
Syntax Note: Coming from JavaScript or After Effects?
| Concept | Luau | JavaScript | After Effects |
|---|---|---|---|
| If block start | if x then | if (x) { | if (x) { |
| Else if | elseif | else if | else if |
| Block end | end | } | } |
| For loop | for i = 1, 10 do | for (let i = 1; i <= 10; i++) | for (var i = 1; i <= 10; i++) |
| While | while x do ... end | while (x) { } | while (x) { } |
Key differences:
- Luau uses words (
then,do,end) instead of braces ({,}) elseifis ONE word in Luau (notelse if)- No parentheses around conditions:
if x > 5 thennotif (x > 5) then
Where Does Control Flow Go?
Control flow code can go in any lifecycle function:
| Function | When to use |
|---|---|
init() | One-time decisions during setup |
advance() | Per-frame logic (game rules, state changes) |
draw() | Conditional rendering |
function init(self: MyScript): boolean
-- One-time decision
if self.difficulty == "hard" then
self.enemyHealth = 200
else
self.enemyHealth = 100
end
return true
end
function advance(self: MyScript, elapsed: number): boolean
-- Per-frame decision
if self.health <= 0 then
self.gameOver = true
end
return true
end
Conditionals: Making Decisions
If-Then-Else
The most basic way to make decisions:
if condition then
-- runs if condition is true
end
The full structure:
if condition then
-- runs if condition is true
elseif otherCondition then
-- runs if first was false and this is true
else
-- runs if all conditions were false
end
then and end!Every if needs then. Every block needs end.
-- WRONG: Missing 'then'
if health > 0
print("Alive")
end
-- WRONG: Missing 'end'
if health > 0 then
print("Alive")
-- CORRECT:
if health > 0 then
print("Alive")
end
// JavaScript
if (health > 0) {
console.log("Alive");
} else if (health === 0) {
console.log("Just died");
} else {
console.log("Dead");
}
-- Luau
if health > 0 then
print("Alive")
elseif health == 0 then
print("Just died")
else
print("Dead")
end
Differences:
- No parentheses around condition
theninstead of{endinstead of}elseif(one word) instead ofelse if(two words)==for equality (same in both)
If-Then-Else Expressions (Inline Conditionals)
Luau has inline conditional expressions — perfect for assigning values based on conditions:
local result = if condition then valueA else valueB
This is like JavaScript's ternary operator (? :):
-- Luau
local status = if health > 0 then "Alive" else "Dead"
// JavaScript equivalent
const status = health > 0 ? "Alive" : "Dead";
Chained expressions:
local grade = if score >= 90 then "A"
elseif score >= 80 then "B"
elseif score >= 70 then "C"
elseif score >= 60 then "D"
else "F"
After Effects uses the ternary operator:
// After Effects
var status = health > 0 ? "Alive" : "Dead";
var grade = score >= 90 ? "A" : score >= 80 ? "B" : "C";
Luau's inline if is more readable for chained conditions.
Loops: Repeating Code
Numeric For Loop
The most common loop — iterate a specific number of times:
for i = start, stop do
-- i goes from start to stop (inclusive)
end
Examples:
-- Count from 1 to 5
for i = 1, 5 do
print(i) -- 1, 2, 3, 4, 5
end
-- Count from 0 to 4 (5 iterations, starting at 0)
for i = 0, 4 do
print(i) -- 0, 1, 2, 3, 4
end
With a step value:
for i = start, stop, step do
-- i changes by 'step' each iteration
end
-- Count down from 5 to 1
for i = 5, 1, -1 do
print(i) -- 5, 4, 3, 2, 1
end
-- Even numbers from 2 to 10
for i = 2, 10, 2 do
print(i) -- 2, 4, 6, 8, 10
end
-- Every third number
for i = 0, 12, 3 do
print(i) -- 0, 3, 6, 9, 12
end
-- WRONG: This won't run at all!
for i = 10, 1 do
print(i) -- Nothing prints!
end
-- CORRECT: Include the -1 step
for i = 10, 1, -1 do
print(i) -- 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
end
// JavaScript: Count 1 to 5
for (let i = 1; i <= 5; i++) {
console.log(i);
}
// JavaScript: Count down 5 to 1
for (let i = 5; i >= 1; i--) {
console.log(i);
}
-- Luau: Count 1 to 5
for i = 1, 5 do
print(i)
end
-- Luau: Count down 5 to 1
for i = 5, 1, -1 do
print(i)
end
Key differences:
- Luau:
for i = start, stop, step do - JS:
for (let i = start; i <= stop; i += step) - Luau's
stopis inclusive, JS uses a condition
While Loop
Repeats while a condition is true:
while condition do
-- runs while condition is true
end
Example: Countdown
local count = 5
while count > 0 do
print(count)
count -= 1
end
print("Blast off!")
Output:
5
4
3
2
1
Blast off!
// JavaScript
let count = 5;
while (count > 0) {
console.log(count);
count--;
}
-- Luau
local count = 5
while count > 0 do
print(count)
count -= 1
end
Repeat Until
Repeats until a condition becomes true. Always runs at least once!
repeat
-- this code runs at least once
until condition -- stops when condition is true
Example: Retry until success
local attempts = 0
repeat
attempts += 1
print(`Attempt {attempts}`)
until attempts >= 3
print("Done!")
Output:
Attempt 1
Attempt 2
Attempt 3
Done!
JavaScript has do...while which is similar:
// JavaScript do-while
let attempts = 0;
do {
attempts++;
console.log(`Attempt ${attempts}`);
} while (attempts < 3); // Note: condition is OPPOSITE
// Luau repeat-until
local attempts = 0
repeat
attempts += 1
print(`Attempt {attempts}`)
until attempts >= 3 -- Stops when TRUE
Key difference:
- JS
whilecontinues while condition is TRUE - Luau
untilcontinues until condition is TRUE (opposite logic!)
Break and Continue
break — Exit the loop immediately:
for i = 1, 100 do
if i == 5 then
break -- Stop at 5
end
print(i)
end
-- Output: 1, 2, 3, 4
continue — Skip to the next iteration:
for i = 1, 5 do
if i == 3 then
continue -- Skip 3
end
print(i)
end
-- Output: 1, 2, 4, 5
// JavaScript - break and continue work the same
for (let i = 1; i <= 5; i++) {
if (i === 3) continue; // Skip 3
console.log(i);
}
// Output: 1, 2, 4, 5
Exercises
Exercise 1: Health Tier ⭐
Premise
Control flow is how your script makes decisions. In Rive, those decisions drive which animation state or visual response should appear.
By the end of this exercise, you will use an if/elseif chain to categorize a health value.
Use Case
A health bar needs a label like Critical, Low, Medium, or Full. That label can drive color changes or trigger warning animations. If your conditions are out of order, the wrong state will show even when the number is correct.
Example scenarios:
- Health warning logic
- Difficulty gating based on thresholds
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise1_HealthTier
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise1 = {}
function init(self: Exercise1): boolean
local health = 45
-- TODO: Set status based on health ranges
-- <= 0: "Dead"
-- <= 20: "Critical"
-- <= 40: "Low"
-- <= 70: "Medium"
-- <= 90: "High"
-- else: "Full"
local status = "TODO"
print(`ANSWER: {status}`)
return true
end
function draw(self: Exercise1, renderer: Renderer)
end
return function(): Node<Exercise1>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Use an if/elseif chain to compute
status - Keep the order from lowest to highest ranges
Expected Output
Your console output should display the health tier status based on where the health value (45) falls in the ordered range checks. The status will be one of: Dead, Critical, Low, Medium, High, or Full.
Verify Your Answer
Checklist
- Conditions are ordered from lowest to highest
-
statusequals "Medium" for health 45 - Output matches the expected line
Exercise 2: Inline Labels ⭐
Premise
Inline conditionals are compact and make assignments readable when you only need a value, not a full block.
By the end of this exercise, you will use inline conditionals to compute three labels.
Use Case
You are assigning labels for score grade, premium discount, and rank title. These are values you might bind to a text run in a HUD or debug panel.
Example scenarios:
- Rank badges and labels
- Feature gating for premium users
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise2_InlineLabels
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise2 = {}
function init(self: Exercise2): boolean
local score = 75
local isPremium = true
local level = 5
-- TODO 1: grade should be "A/B/C/D/F" based on score
local grade = "?"
-- TODO 2: discount should be 20 if premium else 0
local discount = 0
-- TODO 3: title should be "Master" (>=10), "Expert" (>=5), else "Novice"
local title = "?"
print(`ANSWER: {grade},{discount},{title}`)
return true
end
function draw(self: Exercise2, renderer: Renderer)
end
return function(): Node<Exercise2>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Use inline if/elseif expressions for
grade - Use inline if for
discount - Use inline if/elseif for
title
Expected Output
Your console output should display three values separated by commas:
- The letter grade based on score thresholds (90+, 80+, 70+, 60+, else)
- The discount percentage (premium users get a discount)
- The title based on level (10+, 5+, else)
Verify Your Answer
Checklist
- Inline conditionals use
if ... then ... else ... - grade is "C" for score 75
- Output matches the expected line
Exercise 3: Loop Totals ⭐
Premise
Loops let you repeat logic without copy-pasting. They are essential for summing values or iterating over ranges.
By the end of this exercise, you will use a numeric for loop to compute a sum and an even count.
Use Case
You are preparing a debug readout for a chart. You need the total of a range and the count of even markers. These loop patterns show up everywhere in data-driven animations.
Example scenarios:
- Summing data points for a bar chart
- Counting markers for grid lines
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise3_LoopTotals
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise3 = {}
function init(self: Exercise3): boolean
local sum = 0
local evenCount = 0
-- TODO 1: for i = 1, 10 do sum += i end
-- TODO 2: inside the same loop, increment evenCount for even i
print(`ANSWER: sum={sum},evens={evenCount}`)
return true
end
function draw(self: Exercise3, renderer: Renderer)
end
return function(): Node<Exercise3>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Add a numeric for loop from 1 to 10
- Sum the values into
sum - Count even numbers into
evenCount
Expected Output
Your console output should display:
sum=— the total of all numbers from 1 to 10evens=— the count of even numbers in that range
Verify Your Answer
Checklist
- Sum is 55
- Even count is 5
- Output matches the expected line
Exercise 4: While Countdown ⭐
Premise
While loops are best when you do not know the exact number of iterations ahead of time. You loop until a condition changes.
By the end of this exercise, you will use a while loop to count down and track steps.
Use Case
A cooldown timer counts down to zero and you want to know how many ticks happened. This mirrors simple timer loops used in scripts.
Example scenarios:
- Cooldowns and timers
- Resource depletion
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise4_WhileCountdown
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise4 = {}
function init(self: Exercise4): boolean
local energy = 3
local steps = 0
-- TODO: Use a while loop to decrement energy to 0
-- Increment steps each iteration
print(`ANSWER: steps={steps},energy={energy}`)
return true
end
function draw(self: Exercise4, renderer: Renderer)
end
return function(): Node<Exercise4>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Loop while energy > 0
- Decrement energy by 1 each iteration
- Increment steps each iteration
Expected Output
Your console output should display:
steps=— how many iterations the while loop executedenergy=— the final energy value (should be depleted to zero)
Verify Your Answer
Checklist
- While loop runs until energy is 0
- steps ends at 3
- Output matches the expected line
Exercise 5: Repeat Until ⭐⭐
Premise
repeat-until always runs at least once. This is useful for retry logic or initialization loops.
By the end of this exercise, you will use repeat-until to simulate a retry sequence.
Use Case
You are polling for a resource that becomes available on the third attempt. You want to track attempts and stop once it succeeds.
Example scenarios:
- Retrying a load or setup step
- Validating Input until it passes
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise5_RepeatUntil
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise5 = {}
function init(self: Exercise5): boolean
local attempts = 0
local success = false
-- TODO: Repeat until success is true
-- - Increment attempts
-- - Set success to true when attempts >= 3
print(`ANSWER: attempts={attempts},success={success}`)
return true
end
function draw(self: Exercise5, renderer: Renderer)
end
return function(): Node<Exercise5>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Use repeat-until so the loop runs at least once
- Increment attempts each loop
- Set success to true when attempts >= 3
Expected Output
Your console output should display:
attempts=— how many times the loop ran before successsuccess=— the final boolean state (true when threshold reached)
Verify Your Answer
Checklist
- Loop runs at least once
- Attempts stop at 3
- Output matches the expected line
Exercise 6: Break and Continue ⭐⭐
Premise
break and continue let you control loop flow. You can skip unwanted items or stop early when a condition is met.
By the end of this exercise, you will use both break and continue in one loop.
Use Case
You are scanning items and want to skip every third entry, but stop once you reach a certain point. This pattern is common in search logic or filtering.
Example scenarios:
- Skipping invalid entries
- Stopping once a target is found
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise6_BreakContinue
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise6 = {}
function init(self: Exercise6): boolean
local sum = 0
local count = 0
for i = 1, 10 do
-- TODO 1: Skip multiples of 3 with continue
-- TODO 2: Break when i == 6
-- TODO 3: Add i to sum and increment count
end
print(`ANSWER: sum={sum},count={count}`)
return true
end
function draw(self: Exercise6, renderer: Renderer)
end
return function(): Node<Exercise6>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Use
continuewhen i % 3 == 0 - Use
breakwhen i == 6 - Sum the processed values and count them
Expected Output
Your console output should display:
sum=— the total of processed values (after skipping and breaking)count=— how many numbers were actually added to the sum
The loop processes values 1-5, skipping multiples of 3 and breaking at 6.
Verify Your Answer
Checklist
- continue skips 3 and 6
- break stops at 6
- Output matches the expected line
Exercise 7: State Switch ⭐⭐
Premise
Real scripts combine multiple conditions to determine a state. This is the basis of state machines and animation transitions.
By the end of this exercise, you will use an ordered if/elseif chain to set a state and update stamina.
Use Case
A character can be dead, exhausted, wounded, or normal. That state determines which animation should play. You also regenerate stamina when not dead.
Example scenarios:
- Player state machines
- UI state transitions
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise7_StateSwitch
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise7 = {}
function init(self: Exercise7): boolean
local health = 25
local stamina = 10
local state = "idle"
-- TODO 1: Set state based on health/stamina
-- <=0: "dead"
-- stamina <=0: "exhausted"
-- health <=30: "wounded"
-- else: "ok"
-- TODO 2: If not dead and stamina < 20, add 5 stamina
print(`ANSWER: state={state},stamina={stamina}`)
return true
end
function draw(self: Exercise7, renderer: Renderer)
end
return function(): Node<Exercise7>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Use ordered if/elseif to set
state - Regenerate stamina if the state is not dead
Expected Output
Your console output should display:
state=— the character's state based on health/stamina conditionsstamina=— the final stamina value after regeneration (if applicable)
Verify Your Answer
Checklist
- Ordered conditions produce "wounded" for health 25
- Stamina increases by 5
- Output matches the expected line
Knowledge Check
Common Mistakes
- Forgetting
then—if x > 5 thennotif x > 5 - Forgetting
doin loops —for i = 1, 10 donotfor i = 1, 10 - Forgetting
end— Everyif,for,while,functionneedsend - Wrong step in countdown —
for i = 10, 1, -1 donotfor i = 10, 1 do - Using
else ifinstead ofelseif— It's one word in Luau!
Quick Reference
-- If-Then-Else
if condition then
-- code
elseif other then
-- code
else
-- code
end
-- Inline conditional
local x = if condition then valueA else valueB
-- For loop
for i = start, stop, step do
-- code
end
-- While loop
while condition do
-- code
end
-- Repeat until
repeat
-- code
until condition
-- Loop control
break -- exit loop
continue -- skip to next iteration
Next Steps
- Continue to 1.3 Functions & Closures
- Need a refresher? Review Quick Reference