5.2 KiB
Lua game package format
A game package is a manifest plus Lua scripts and optional assets.
Typical structure:
assets/games/<gameId>/
manifest.json
scripts/
main.lua
state.lua
ui.lua
runtime_defs.lua
assets/
image.png
audio/
click.wav
Manifest modules
Lua modules must be declared in manifest.json.
{
"id": "template",
"version": "0.1.0",
"entry": "main",
"modules": {
"main": "scripts/main.lua",
"state": "scripts/state.lua",
"ui": "scripts/ui.lua",
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"layout": "runtime:layout.lua"
}
}
Lua imports by module name:
local ui = runtime.import("ui")
local widgets = runtime.import("runtime_widgets")
Do not use require, package, dofile, loadfile, or os.
Path rules
Package-local module paths:
scripts/*.lua
Runtime shared module paths:
runtime:*.lua
runtime: paths must not contain /, .., or an empty filename.
Base packages
A game manifest can declare a base field to indicate it depends on a framework package:
{
"gameId": "ludo",
"base": "_framework",
"modules": { ... }
}
The base field is metadata. Actual loading is controlled by RuntimeOptions.basePackages:
LuaGameWidget(
gameId: 'ludo',
runtimeOptions: const RuntimeOptions(
basePackages: ['_framework'],
),
)
Loading order:
- Base packages are loaded first, in
basePackagesorder. - The game package is loaded last.
- All modules are merged into a flat map.
- Later packages override earlier packages on name collision.
- The entry script always comes from the last (game) package.
This means a game can override any framework module by declaring a module with the same name in its own manifest.
Multi-package module resolution
When Lua code calls runtime.import("xxx"):
- Look up
xxxin the merged module map (game modules first, then framework). - If not found, throw
FormatException: Lua module is not declared in manifest.modules.
Framework modules are transparent to game code:
local app = runtime.import("app") -- resolved from framework
local state = runtime.import("state") -- resolved from game
Entry module
The manifest entry module should expose lifecycle/event functions expected by the script engine. Keep game-specific state in Lua modules and return runtime diffs/commands through the approved protocol.
Runtime helper modules
Shared helpers are provided by the runtime package:
assets/runtime/lua/runtime_ui.lua
assets/runtime/lua/runtime_widgets.lua
assets/runtime/lua/runtime_commands.lua
assets/runtime/lua/layout.lua
Use helpers for authoring convenience, but remember helpers must normalize into supported Runtime protocol nodes/commands before Dart validation.
Generated Lua definitions
runtime_defs.lua files are generated from common definitions. After changing helper APIs, run:
dart run tool/generate_lua_runtime_defs.dart
Check without rewriting:
dart run tool/generate_lua_runtime_defs.dart --check
Package sources
Host apps can load packages from three common sources:
// Bundled app assets.
AssetGamePackageRepository(runtimeOptions: runtimeOptions)
// Local development directory, useful when images should not be bundled into app assets.
FileGamePackageRepository(
baseDirectory: 'E:/lua_packages',
runtimeOptions: runtimeOptions,
)
// Remote update server.
RemoteGamePackageRepository(
baseUri: Uri.parse('https://example.com/lua-packages/'),
runtimeOptions: runtimeOptions,
)
A local development directory uses the same package layout as a downloaded remote zip:
E:/lua_packages/gomoku/
manifest.json
scripts/
assets/
Remote compatibility
Remote manifests may include a compat block. The server should use request query values to return the newest compatible package, and the client validates the returned manifest again before download.
{
"gameId": "gomoku",
"version": "0.3.0",
"packageUrl": "https://example.com/packages/gomoku-0.3.0.zip",
"sha256": "...",
"compat": {
"runtimeApiVersion": 1,
"minRuntimeVersion": "0.4.0",
"maxRuntimeVersion": "0.4.9",
"minHostBuild": 120,
"platforms": ["windows", "android"],
"channels": ["dev", "prod"]
}
}
RemoteGamePackageRepository sends these query parameters when fetching remote_manifest.json:
runtimeApiVersion
runtimeVersion
hostBuild
platform
channel
Configure them through RuntimeOptions:
RuntimeOptions(
runtimeVersion: '0.4.0',
hostBuild: 120,
platform: 'windows',
channel: 'dev',
)
If compatibility fails, the remote package is not downloaded. The repository falls back to stable cache, previous stable cache, then bundled assets.
Package validation
A host repository can validate a game package with:
dart run tool/check_runtime_package.dart assets/games/template packages/flame_lua_runtime/assets/runtime/lua
Within this package repo, example game packages live under:
example/assets/games/