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.
This commit is contained in:
233
games/lua_examples/API.md
Normal file
233
games/lua_examples/API.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# 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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
|
||||
```lua
|
||||
-- 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:
|
||||
```lua
|
||||
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:
|
||||
```lua
|
||||
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:
|
||||
```lua
|
||||
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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
```lua
|
||||
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:
|
||||
```lua
|
||||
-- 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:
|
||||
```lua
|
||||
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
|
||||
|
||||
```lua
|
||||
-- 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.
|
||||
Reference in New Issue
Block a user