Skin specification
This reference describes the file structure and JSON format used to create controller skins for Linkboy.
Package format
A Linkboy skin package is a ZIP archive with a filename ending in .lbskin. The archive must contain info.json at its root.
Any file referenced in info.json or a layout JSON file can use a path relative to the archive root. For example, you can place layout files in layouts/ and background or overlay images in images/, then reference them as layouts/portrait.json and images/background.png. Paths are always relative to the archive root, even when the referring layout file is in a subdirectory.
Control images are looked up by the naming convention described under Control images, rather than by a file field.
| Example file | Purpose |
|---|---|
info.json |
Skin identifier, display name, and layout entries |
layout-1080x2400.json |
A layout for a reference canvas of 1080 × 2400 |
background-1080x2400.png |
Artwork referenced by a layout |
A.png |
Base image for the A button |
A_pressed.png |
Optional pressed image for the A button |
dpad.png |
Base image for the D-pad |
dpad_u.png |
Optional image for the up direction |
Use valid JSON with quoted property names and no comments or trailing commas. Match the capitalization shown for input names and control filenames.
Skin metadata: info.json
| Field | Type | Required | Description |
|---|---|---|---|
id |
String | Yes | Unique skin identifier, for example com.example.skins.my_skin |
name |
String | Yes | Display name shown to users |
author |
String | No | Creator's name |
layouts |
Array of layout entries | No | Defaults to [] |
Skin identifier
The skin ID must start with a letter or digit and may contain only ASCII letters (A–Z, a–z), digits (0–9), dots (.), underscores (_), and hyphens (-).
We recommend a fully qualified reverse-domain-style identifier, such as com.example.skins.my_skin, to help avoid conflicts with other skins. Keep the identifier stable when updating the same skin.
Layout entries
Each layout entry contains:
| Field | Type | Required | Description |
|---|---|---|---|
width |
Integer | Yes | Virtual canvas width |
height |
Integer | Yes | Virtual canvas height |
file |
String | Yes | Layout JSON path relative to the archive root |
{
"id": "com.example.my_skin",
"name": "My skin",
"author": "Pixel Respawn",
"layouts": [
{ "width": 1080, "height": 2400, "file": "layout-1080x2400.json" },
{ "width": 2400, "height": 1080, "file": "layout-2400x1080.json" }
]
}
The width and height define a virtual canvas. All coordinates and sizes in that layout JSON file relate to this canvas. Linkboy converts them to real screen coordinates at runtime; the canvas dimensions do not have to match the device’s screen resolution.
Linkboy chooses the best layout according to the screen’s aspect ratio and resolution. When two layouts have very close aspect ratios, it chooses the layout whose canvas size is closest to the screen resolution.
If the chosen layout’s virtual canvas size differs from the screen size, Linkboy scales the layout to fit the screen while preserving its aspect ratio, then centers it on the screen.
A control skin can omit layouts:
{
"id": "com.example.skins.my_controls",
"name": "My controls",
"author": "Example Studio"
}
Layout files
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
gbaScreen |
Rectangle | Yes | — | GBA game screen placement |
gbcScreen |
Rectangle | Yes | — | GB/GBC game screen placement |
controls |
Array of controls | No | [] |
Touch controls |
backgrounds |
Array of images | No | [] |
Background artwork |
overlays |
Array of images | No | [] |
Overlay artwork |
Rectangles
All rectangles use the following four required integer fields. Coordinates are measured from the top-left of the layout’s virtual canvas: x increases to the right and y increases downward.
| Field | Meaning |
|---|---|
x |
Left position |
y |
Top position |
width |
Rectangle width |
height |
Rectangle height |
{ "x": 0, "y": 286, "width": 1080, "height": 720 }
Controls
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
input |
Input object | Yes | — | D-pad or button mapping |
frame |
Rectangle | Yes | — | Control position and size |
alpha |
Number from 0 to 1 | No | 1 |
Control image opacity |
alpha is inclusive of both endpoints: 0 is invisible, 1 is fully opaque, and values between them are partially transparent. An invisible control remains touchable. Set alpha to 0 when the control artwork is already drawn into a background or overlay.
Button input
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
type |
String | No | "button" |
Button input type |
key |
Key name or array of key names | Yes | — | Input or combined inputs |
If type is omitted, it defaults to "button":
{ "key": "A" }
You can also specify "type": "button" explicitly. A single key can use either form:
{ "type": "button", "key": "A" }
{ "type": "button", "key": ["A"] }
Multiple keys combine their inputs:
{ "type": "button", "key": ["A", "B"] }
Supported keys
Key names are case-sensitive. The table below lists them in declaration order, which also determines the filename order for combined inputs. Read each row from left to right, then continue to the next row.
| Category | Names |
|---|---|
| Directional | Up, Down, Left, Right |
| Console | A, B, L, R, Select, Start |
| Turbo | TurboA, TurboB, TurboL, TurboR |
| Save states | Load, Save |
| Speed | FastForward, SlowMotion |
| Other actions | Screenshot, Menu |
D-pad input
For a standard D-pad, use:
{ "type": "dpad" }
The up, down, left, and right fields define the input for each D-pad direction. Each accepts a single key name from Supported keys, such as "Up" or "A". Arrays are not supported. All four fields are optional; omit them to use the standard directional inputs.
| Field | Default |
|---|---|
up |
"Up" |
down |
"Down" |
left |
"Left" |
right |
"Right" |
Background and overlay images
Images and controls are displayed in this order, from back to front:
- Background images (
backgrounds). - The game screen.
- Overlay images (
overlays). - All controls.
Both backgrounds and overlays contain image objects:
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
frame |
Rectangle | Yes | — | Image position and size |
file |
String | Yes | — | Image path relative to the archive root |
alpha |
Number from 0 to 1 | No | 1 |
Image opacity |
{
"frame": { "x": 0, "y": 0, "width": 1080, "height": 2400 },
"file": "images/background.png",
"alpha": 1
}
Example layout
This abbreviated example illustrates the structure.
{
"gbaScreen": { "x": 0, "y": 286, "width": 1080, "height": 720 },
"gbcScreen": { "x": 0, "y": 160, "width": 1080, "height": 972 },
"backgrounds": [
{
"frame": { "x": 0, "y": 0, "width": 1080, "height": 2400 },
"file": "background-1080x2400.png"
}
],
"controls": [
{
"input": { "type": "dpad" },
"frame": { "x": 40, "y": 1580, "width": 440, "height": 440 }
},
{
"input": { "type": "button", "key": "A" },
"frame": { "x": 878, "y": 1688, "width": 164, "height": 164 }
},
{
"input": { "type": "button", "key": "B" },
"frame": { "x": 658, "y": 1748, "width": 164, "height": 164 }
}
]
}
Control images
Linkboy looks up control images by filename; individual controls do not have an image filename field.
Buttons
Use the key name for the base image and append _pressed for the optional pressed image.
For combined inputs, Linkboy joins the key names with + in the declaration order shown in Supported keys, regardless of their order in the JSON array. For example, a button combining A, B, Select, and Start uses A+B+Select+Start.png and optionally A+B+Select+Start_pressed.png.
| Control | Base image | Optional pressed image |
|---|---|---|
| A | A.png |
A_pressed.png |
| B | B.png |
B_pressed.png |
| L / R | L.png / R.png |
L_pressed.png / R_pressed.png |
| Select / Start | Select.png / Start.png |
Select_pressed.png / Start_pressed.png |
| Turbo A | TurboA.png |
TurboA_pressed.png |
| Turbo B | TurboB.png |
TurboB_pressed.png |
| Turbo L | TurboL.png |
TurboL_pressed.png |
| Turbo R | TurboR.png |
TurboR_pressed.png |
| Load state | Load.png |
Load_pressed.png |
| Save state | Save.png |
Save_pressed.png |
| Fast-forward | FastForward.png |
FastForward_pressed.png |
| Slow motion | SlowMotion.png |
SlowMotion_pressed.png |
| Screenshot | Screenshot.png |
Screenshot_pressed.png |
| Menu | Menu.png |
Menu_pressed.png |
| A+B | A+B.png |
A+B_pressed.png |
If a button’s pressed image is omitted, its base image is used in both states.
D-pad
| State | Filename |
|---|---|
| Neutral / base | dpad.png |
| Up | dpad_u.png |
| Down | dpad_d.png |
| Left | dpad_l.png |
| Right | dpad_r.png |
| Up-left | dpad_ul.png |
| Up-right | dpad_ur.png |
| Down-left | dpad_dl.png |
| Down-right | dpad_dr.png |
All directional images are optional. Any missing directional image uses dpad.png. Supply only dpad.png for a static D-pad.
Default skin fallback
If a control’s base image is absent, Linkboy uses the default skin’s image for that control. This lets a skin replace selected controls without supplying a complete image set.
If the base image is present but a pressed or directional image is absent, the supplied base image is used for that state.
Invalid controls
Controls that fail deserialization, such as a button with an unknown key name or a control missing a required rectangle field, are skipped individually. If a control does not appear, check its JSON and key spelling. This does not mean a malformed JSON document or an invalid top-level layout can be loaded.