Project: Catch the Stars
Build a tiny interactive game: click stars to increase a score stored in the ViewModel.
Rive Setup
- Create a ViewModel named
GameVMwith a number propertyscore - Bind
scoreto a text field or UI element - Attach this Node script to a group on your Artboard
Node Script
--!strict
type Star = {
position: Vector,
velocity: Vector,
radius: number,
path: Path,
}
export type StarGame = {
stars: {Star},
paint: Paint,
score: Property<number>?,
}
local function buildStarPath(path: Path, radius: number)
path:reset()
path:moveTo(Vector.xy(0, -radius))
path:lineTo(Vector.xy(radius * 0.4, -radius * 0.2))
path:lineTo(Vector.xy(radius, 0))
path:lineTo(Vector.xy(radius * 0.4, radius * 0.2))
path:lineTo(Vector.xy(0, radius))
path:lineTo(Vector.xy(-radius * 0.4, radius * 0.2))
path:lineTo(Vector.xy(-radius, 0))
path:lineTo(Vector.xy(-radius * 0.4, -radius * 0.2))
path:close()
end
local function spawnStar(): Star
local radius = math.random(10, 18)
local path = Path.new()
buildStarPath(path, radius)
return {
position = Vector.xy(math.random(-150, 150), math.random(-90, 90)),
velocity = Vector.xy(math.random(-40, 40), math.random(-20, 20)),
radius = radius,
path = path,
}
end
function init(self: StarGame, context: Context): boolean
self.stars = {}
for _ = 1, 6 do
table.insert(self.stars, spawnStar())
end
self.paint = Paint.with({ style = "fill", color = Color.rgb(255, 220, 90) })
local vm = context:viewModel()
if vm then
self.score = vm:getNumber("score")
if self.score then
self.score.value = 0
end
end
return true
end
function advance(self: StarGame, seconds: number): boolean
for _, star in ipairs(self.stars) do
star.position = Vector.xy(
star.position.x + star.velocity.x * seconds,
star.position.y + star.velocity.y * seconds
)
if star.position.x > 180 then star.position = Vector.xy(-180, star.position.y) end
if star.position.x < -180 then star.position = Vector.xy(180, star.position.y) end
if star.position.y > 120 then star.position = Vector.xy(star.position.x, -120) end
if star.position.y < -120 then star.position = Vector.xy(star.position.x, 120) end
end
return true
end
function pointerDown(self: StarGame, event: PointerEvent)
for i = #self.stars, 1, -1 do
local star = self.stars[i]
local dx = event.position.x - star.position.x
local dy = event.position.y - star.position.y
if (dx * dx + dy * dy) <= (star.radius * star.radius) then
table.remove(self.stars, i)
table.insert(self.stars, spawnStar())
if self.score then
self.score.value += 1
end
event:hit()
return
end
end
end
function draw(self: StarGame, renderer: Renderer)
for _, star in ipairs(self.stars) do
renderer:save()
renderer:transform(Mat2D.withTranslation(star.position))
renderer:drawPath(star.path, self.paint)
renderer:restore()
end
end
return function(): Node<StarGame>
return {
init = init,
advance = advance,
draw = draw,
pointerDown = pointerDown,
stars = {},
paint = Paint.new(),
score = nil,
}
end
How It Works
Star Entity
Each star is a table containing:
position: Current Vector locationvelocity: Movement direction and speedradius: Size for hit detection and renderingpath: Pre-built star shape Path
Movement System
The advance function updates each star's position using delta time:
star.position = Vector.xy(
star.position.x + star.velocity.x * seconds,
star.position.y + star.velocity.y * seconds
)
Screen Wrapping
Stars wrap around when they leave the play area, creating continuous movement:
if star.position.x > 180 then star.position = Vector.xy(-180, star.position.y) end
Hit Detection
Uses distance-squared comparison for efficient circular hit testing:
if (dx * dx + dy * dy) <= (star.radius * star.radius) then
Score Integration
When a star is caught:
- Remove it from the array
- Spawn a new star
- Increment the ViewModel score
Rendering with Transforms
Each star is drawn by saving Renderer state, translating to position, drawing, then restoring:
renderer:save()
renderer:transform(Mat2D.withTranslation(star.position))
renderer:drawPath(star.path, self.paint)
renderer:restore()
Extension Ideas
- Increase speed as score rises
- Add a timer with a ViewModel property
- Swap stars for artboard instances
- Add particle effects when catching stars
- Implement different star types worth different points
- Add a high score tracker
- Create a game over state after missing too many stars
Next Steps
- Continue to Core Types
- Need a refresher? Review Quick Reference