Your First Script
Enjin2 scripts are Lua files loaded by the SDL3 runner. The runner calls two globals every frame: update(dt) for logic and draw() for rendering. Everything else — state, input, graphics, time — is accessed through the engine.* API table.
Script Structure
Every script must define update and draw. The runner calls them in that order each frame.
function update(dt)
-- dt: delta time in seconds, clamped to 0.05 max
-- put all game logic here
end
function draw()
-- no arguments; runs after update each frame
-- all draw calls go here
end
tamagotchi.lua is a complete working example that covers every concept below. Run it with:
./build/sdl3/enjin2_sdl --script scripts/tamagotchi.lua
Reading Resolution
engine.config.resolution() returns the canvas width and height as integers. Call it once at the top of your script to use as layout constants.
-- tamagotchi.lua line 5
local W, H = engine.config.resolution()
State Management
engine.state is a built-in named-state FSM. engine.state.switch("name") transitions to a new state immediately. engine.state.current() returns the current state name as a string, which you use to branch logic in update and draw.
-- tamagotchi.lua lines 28–36: reset() transitions back to "alive"
local function reset()
stats.hunger = 0
stats.happiness = 100
stats.energy = 100
show_msg("HELLO!", 2.0)
engine.state.switch("alive")
end
engine.state.switch("alive") -- set initial state at script load
Reading the current state in update:
-- tamagotchi.lua lines 49–56
local cur = engine.state.current()
if cur == "dead" then
if engine.input.just_pressed(BTN.START) or engine.input.just_pressed(BTN.A) then
reset()
end
return
end
Handling Input
engine.input.just_pressed(BTN.X) returns true only on the first frame a button goes down — it does not repeat while held. The BTN table provides: UP, DOWN, LEFT, RIGHT, A, B, START.
-- tamagotchi.lua lines 78–87: feed on LEFT
if engine.input.just_pressed(BTN.LEFT) then
if stats.hunger > 0 then
stats.hunger = clamp(stats.hunger - 20, 0, 100)
stats.energy = clamp(stats.energy + 5, 0, 100)
show_msg("YUM!", 1.0)
else
show_msg("FULL!", 1.0)
end
end
-- tamagotchi.lua line 100: sleep on A
if engine.input.just_pressed(BTN.A) then
show_msg("NIGHT NIGHT", 1.0)
engine.state.switch("sleeping")
end
Drawing
draw() runs after every update call. Start each frame with clear() to erase the previous frame, then set a color with setColor() before each draw command. The bare globals (clear, setColor, text, textCentered, rectangle, circle, line, setPixel) and the engine.graphics.* equivalents are both valid — the SDL3 runner exposes both forms.
-- tamagotchi.lua lines 130–136
function draw()
clear(COLOR.BLACK)
local cur = engine.state.current()
setColor(COLOR.WHITE)
textCentered("TAMAGOTCHI", 10, 2)
setColor(COLOR.GRAY)
text("HUNGER:", 10, 34)
Available color constants: COLOR.BLACK, COLOR.WHITE, COLOR.GRAY, COLOR.DARK_GRAY, COLOR.RED, COLOR.GREEN, COLOR.BLUE, COLOR.PINK, COLOR.YELLOW.
Time
engine.time.now() returns the total seconds elapsed since the script started as a float. Combine it with math.floor and modulo for blinking effects.
-- tamagotchi.lua line 177: blink a "Z" every half second during sleep
if math.floor(engine.time.now() * 2) % 2 == 0 then
text("Z", petX + 35, petY - 30)
end
Running Your Script
./build/sdl3/enjin2_sdl --script path/to/script.lua
An optional --fps N flag caps the frame rate (default is uncapped).
Next Steps
Async Coroutines — write non-blocking timed sequences using engine.async.wait, engine.async.wait_frames, and engine.tween.await without managing frame counters manually.