Files
basic1/games/lua_examples/API.md
Adolfo Reyna e6e4eca188 Add Lua 5.4 scripting integration for dynamic game loading
- 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.
2026-02-07 11:56:03 -05:00

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 pixels
  • game.height() - Get display height in pixels
  • game.exit() - Request exit to game launcher
  • game.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 pixel
  • renderer.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 input
  • INPUT.TOUCH_DOWN - Touch/click started
  • INPUT.TOUCH_MOVE - Touch/drag in progress
  • INPUT.TOUCH_UP - Touch/click released
  • INPUT.BUTTON_0 - Physical button 0 pressed
  • INPUT.BUTTON_1 - Physical button 1 pressed
  • INPUT.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 manipulation
  • table - 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 functions
  • debug - Debugging functions

Tips and Best Practices

  1. Keep it Simple - Lua is slower than C++. Keep game logic simple for smooth performance.

  2. 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
    
  3. 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}
    
  4. Efficient Drawing - Only redraw when needed. Return false from update() if nothing changed.

  5. 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

  1. Save your .lua file to SD card in /games/ directory
  2. Eject SD card and insert into Pico console
  3. Power on - your game will appear in the launcher menu
  4. Select and play!

No recompilation needed - edit scripts directly on SD card for rapid iteration.