232 lines
5.2 KiB
Markdown
232 lines
5.2 KiB
Markdown
# Lua game package format
|
|
|
|
A game package is a manifest plus Lua scripts and optional assets.
|
|
|
|
Typical structure:
|
|
|
|
```text
|
|
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`.
|
|
|
|
```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:
|
|
|
|
```lua
|
|
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:
|
|
|
|
```text
|
|
scripts/*.lua
|
|
```
|
|
|
|
Runtime shared module paths:
|
|
|
|
```text
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"gameId": "ludo",
|
|
"base": "_framework",
|
|
"modules": { ... }
|
|
}
|
|
```
|
|
|
|
The `base` field is metadata. Actual loading is controlled by `RuntimeOptions.basePackages`:
|
|
|
|
```dart
|
|
LuaGameWidget(
|
|
gameId: 'ludo',
|
|
runtimeOptions: const RuntimeOptions(
|
|
basePackages: ['_framework'],
|
|
),
|
|
)
|
|
```
|
|
|
|
Loading order:
|
|
|
|
1. Base packages are loaded first, in `basePackages` order.
|
|
2. The game package is loaded last.
|
|
3. All modules are merged into a flat map.
|
|
4. Later packages override earlier packages on name collision.
|
|
5. 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")`:
|
|
|
|
1. Look up `xxx` in the merged module map (game modules first, then framework).
|
|
2. If not found, throw `FormatException: Lua module is not declared in manifest.modules`.
|
|
|
|
Framework modules are transparent to game code:
|
|
|
|
```lua
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
dart run tool/generate_lua_runtime_defs.dart
|
|
```
|
|
|
|
Check without rewriting:
|
|
|
|
```bash
|
|
dart run tool/generate_lua_runtime_defs.dart --check
|
|
```
|
|
|
|
## Package sources
|
|
|
|
Host apps can load packages from three common sources:
|
|
|
|
```dart
|
|
// 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:
|
|
|
|
```text
|
|
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.
|
|
|
|
```json
|
|
{
|
|
"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`:
|
|
|
|
```text
|
|
runtimeApiVersion
|
|
runtimeVersion
|
|
hostBuild
|
|
platform
|
|
channel
|
|
```
|
|
|
|
Configure them through `RuntimeOptions`:
|
|
|
|
```dart
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
example/assets/games/
|
|
```
|