- Integrated Lua 5.4 engine (32-bit mode for embedded ARM) - Created LuaGame wrapper class implementing Game interface - Added C++ bindings exposing renderer, game state, and input to Lua - Implemented SD card loader for automatic .lua game discovery - Updated GameLauncher to support std::function for lambda captures - Made Game class members public for Lua bindings access - Added example Lua games: counter, snake, bouncing ball - Included comprehensive API documentation Games can now be written as .lua text files on SD card and loaded without recompilation. Build size: 747KB UF2, Lua VM uses ~50-80KB RAM.
5.8 KiB
Lua Game API Documentation
This document describes the API available to Lua game scripts on the Pico game console.
Script Structure
Every Lua game must define three functions:
function init()
-- Initialize game state
end
function update(event)
-- Process input and update game logic
-- Return true if redraw needed, false otherwise
return needs_redraw
end
function draw()
-- Render the game to screen
end
Metadata Comments
Add metadata at the top of your script:
-- NAME: My Awesome Game
-- DESC: A fun game about squares
Global Tables
game - Game State and Info
game.width()- Get display width in pixelsgame.height()- Get display height in pixelsgame.exit()- Request exit to game launchergame.vars- Table for persistent state variables
Example:
function init()
game.vars.score = 0
game.vars.player_x = 100
game.vars.state = 0
end
renderer - Drawing Functions
All coordinates are in pixels. Color is boolean: true = black/on, false = white/off.
Basic Shapes
renderer.clear(white)- Clear screen to white (true) or black (false)renderer.pixel(x, y, on)- Set single pixelrenderer.line(x0, y0, x1, y1, on, width)- Draw line (width optional, default 1)renderer.rect(x, y, w, h, on, filled)- Draw rectangle (filled optional, default false)renderer.circle(x, y, radius, on, filled)- Draw circle (filled optional, default false)renderer.triangle(x0, y0, x1, y1, x2, y2, on, filled)- Draw triangle
Text
renderer.text(x, y, text, on)- Draw text string
Example:
function draw()
renderer.clear(false) -- Clear to black
renderer.rect(50, 50, 100, 80, true, true) -- Filled white rectangle
renderer.circle(100, 100, 30, true, false) -- White circle outline
renderer.text(10, 10, "Score: " .. tostring(game.vars.score), true)
end
INPUT - Input Event Types
Constants for checking event.type:
INPUT.NONE- No inputINPUT.TOUCH_DOWN- Touch/click startedINPUT.TOUCH_MOVE- Touch/drag in progressINPUT.TOUCH_UP- Touch/click releasedINPUT.BUTTON_0- Physical button 0 pressedINPUT.BUTTON_1- Physical button 1 pressedINPUT.GESTURE- Gesture detected
Input Event Structure
The event parameter passed to update() has these fields:
event.type- Event type (see INPUT constants)event.x- X coordinate (for touch events)event.y- Y coordinate (for touch events)event.button_id- Button identifier (for button events)event.valid- Boolean, true if event is valid
Example:
function update(event)
if event.type == INPUT.TOUCH_DOWN then
print("Touch at: " .. event.x .. ", " .. event.y)
game.vars.last_touch_x = event.x
game.vars.last_touch_y = event.y
return true -- Request redraw
end
return false
end
State Machine Pattern
Use game.vars to implement state machines:
local STATE_MENU = 0
local STATE_PLAYING = 1
local STATE_GAME_OVER = 2
function init()
game.vars.state = STATE_MENU
end
function update(event)
if game.vars.state == STATE_MENU then
-- Handle menu input
if event.type == INPUT.TOUCH_DOWN then
game.vars.state = STATE_PLAYING
return true
end
elseif game.vars.state == STATE_PLAYING then
-- Handle gameplay input
elseif game.vars.state == STATE_GAME_OVER then
-- Handle game over screen
end
return false
end
function draw()
if game.vars.state == STATE_MENU then
-- Draw menu
elseif game.vars.state == STATE_PLAYING then
-- Draw game
elseif game.vars.state == STATE_GAME_OVER then
-- Draw game over screen
end
end
Standard Lua Libraries
Available libraries:
math- Math functions (math.sin,math.random, etc.)string- String manipulationtable- Table operations (table.insert,table.remove, etc.)coroutine- Coroutines for advanced flow control
Not available (embedded environment):
io- File I/O (use SD card APIs in C++ if needed)os- Operating system functionsdebug- Debugging functions
Tips and Best Practices
-
Keep it Simple - Lua is slower than C++. Keep game logic simple for smooth performance.
-
Use Local Variables - Local variables are faster than globals:
local function move_player() -- Local function local speed = 5 -- Local variable game.vars.player_x = game.vars.player_x + speed end -
Minimize Allocations - Reuse tables instead of creating new ones:
-- Good: Reuse existing table game.vars.snake[1].x = new_x -- Bad: Creates garbage game.vars.snake[1] = {x = new_x, y = new_y} -
Efficient Drawing - Only redraw when needed. Return
falsefromupdate()if nothing changed. -
Frame Limiting - For animation, use frame counters:
function update(event) game.vars.frame = (game.vars.frame or 0) + 1 if game.vars.frame % 5 == 0 then -- Every 5 frames -- Update animation return true end return false end
Example: Complete Minimal Game
-- NAME: Click Counter
-- DESC: Count your clicks
function init()
game.vars.count = 0
end
function update(event)
if event.type == INPUT.TOUCH_DOWN then
game.vars.count = game.vars.count + 1
return true
end
return false
end
function draw()
renderer.clear(false)
local text = "Clicks: " .. tostring(game.vars.count)
renderer.text(20, 20, text, true)
end
Deployment
- Save your
.luafile to SD card in/games/directory - Eject SD card and insert into Pico console
- Power on - your game will appear in the launcher menu
- Select and play!
No recompilation needed - edit scripts directly on SD card for rapid iteration.