Creating a Node Script
- Create a new script and select Node as the type.
- Add it to the scene.
Documentation Index
Fetch the complete documentation index at: /llms.txt
Use this file to discover all available pages before exploring further.
-- Define the script's data and inputs.
type MyNode = {}
-- Called once when the script initializes.
function init(self: MyNode): boolean
return true
end
-- Called every frame to advance the simulation.
-- 'seconds' is the elapsed time since the previous frame.
function advance(self: MyNode, seconds: number): boolean
return false
end
-- Called when any input value changes.
function update(self: MyNode) end
-- Called every frame (after advance) to render the content.
function draw(self: MyNode, renderer: Renderer) end
-- Return a factory function that Rive uses to build the Node instance.
return function(): Node<MyNode>
return {
init = init,
advance = advance,
update = update,
draw = draw,
}
end
function rectangle(self: Rectangle)
-- Update the path with current width and height
self.path:reset()
local halfWidth = self.width / 2
local halfHeight = self.height / 2
-- Draw rectangle centered at origin
self.path:moveTo(Vector.xy(-halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, halfHeight))
self.path:lineTo(Vector.xy(-halfWidth, halfHeight))
self.path:close()
-- Update paint color
self.paint.color = self.color
end
function draw(self: Rectangle, renderer: Renderer)
renderer:drawPath(self.path, self.paint)
end
type Enemy = {
artboard: Artboard<Data.Enemy>,
position: Vector,
}
export type MyGame = {
-- This is the component that we will dynamically add to our scene
-- See: /scripting/script-inputs
enemy: Input<Artboard<Data.Enemy>>,
enemies: { Enemy },
}
function createEnemy(self: MyGame)
-- Create an instance of the artboard
local enemy = self.enemy:instance()
-- Keep track of all enemies in self.enemies
local entry: Enemy = {
artboard = enemy,
position = Vector.xy(0, 0),
}
table.insert(self.enemies, entry)
end
function init(self: MyGame)
createEnemy(self)
return true
end
function advance(self: MyGame, seconds: number)
-- Advance the artboard of each enemy
for _, enemy in self.enemies do
enemy.artboard:advance(seconds)
end
return true
end
function draw(self: MyGame, renderer: Renderer)
-- draw each enemy
for _, enemy in self.enemies do
renderer:save()
enemy.artboard:draw(renderer)
renderer:restore()
end
end
return function(): Node<MyGame>
return {
init = init,
advance = advance,
draw = draw,
enemy = late(),
enemies = {},
}
end
--- Fixed Timestep Advance
--- Keeps movement consistent across different frame rates
--- by advancing the simulation in fixed time steps.
export type CarGame = {
speed: Input<number>,
accumulator: number,
fixedStep: Input<number>,
direction: number,
currentX: number,
currentY: number,
}
-- Prevent the script from running too many catch-up steps
-- after a long pause or frame drop.
local MAX_STEPS = 5
function advance(self: CarGame, seconds: number): boolean
-- Add the time since the last frame to the accumulator.
self.accumulator += seconds
local dt = self.fixedStep
local steps = 0
-- Run the simulation in small, fixed steps.
-- If the frame took longer than one step, multiple steps may run this frame.
while self.accumulator >= dt and steps < MAX_STEPS do
-- Move forward by speed * time.
-- Using a fixed dt keeps movement stable even if the frame rate changes.
self.currentX += self.speed * math.cos(self.direction) * dt
self.currentY += self.speed * math.sin(self.direction) * dt
-- Subtract one fixed step from the accumulator
-- and repeat until we've caught up to real time.
self.accumulator -= dt
steps += 1
end
return true
end
-- Create a new instance of the CarGame script with default values.
-- The simulation runs 60 fixed steps per second.
return function(): Node<CarGame>
return {
speed = 100,
accumulator = 0,
direction = 0,
fixedStep = 1 / 60,
currentX = 0,
currentY = 0,
}
end
Was this page helpful?