# Assets: import and export
A project keeps its files under `assets/`: images, glTF models, sounds and
data assets. Import copies a file into `assets/`, checks that the engine
can load it, and records where it came from. Export builds a release and
packages the game with its assets into one folder that runs without the
engine checkout.
For typed data files (`.rdata`), see "Data assets" in
[Core concepts](concepts.md). For where to find free models and
textures, see [Look and feel](look-and-feel.md).
## Import a file
```bash
rusting asset import my_game ~/Downloads/crate.png --to props --license CC0-1.0 --author "Kenney"
```
```text
Asset: assets/props/crate.png (5cd9ac84-b54f-4ed7-88ad-c8e73cb76421, license CC0-1.0)
```
| Image | `.png`, `.jpg`, `.jpeg`, `.bmp`, `.tga` |
| Model | `.gltf` (with its external buffers and images), `.glb` |
| Sound | `.wav`, `.ogg` |
| Font | `.ttf`, `.otf` |
Import loads the file as the game would, then copies it to
`assets/<folder>` (`--to props` and `--to assets/props` mean the same
folder). Any other type fails with `ASSET_UNSUPPORTED`, a file the engine
cannot read fails with `ASSET_INVALID`, and a file that is already there
fails with `ASSET_EXISTS`: replace it with `asset reimport` instead.
Flags:
- `--author`, `--license`, `--url`, `--generator` and `--notes` record
where the file came from. Use an SPDX name for the license, such as
`CC0-1.0` or `CC-BY-4.0`. Without a license, import warns
`ASSET_NO_LICENSE`.
- `--max-size 512` scales an image down so its longest edge is 512 pixels,
keeping its shape.
- `--linear` marks an image as data, not color: a normal map, a
metallic-roughness map or an occlusion map. Import also cooks
`<name>-linear.rtexture` next to it, and the `reference` field points at
that file, so the texture reads its values without sRGB decoding.
- `--dry-run` runs every check on a staged copy and writes nothing.
Importing a glTF cooks it at once, so the first play does not pay for it.
The report lists every cooked mesh under `meshes`, with its triangle count
and the triangle count of each generated level of detail:
```text
Asset: assets/models/fir.glb (5cd9ac84-..., license CC0-1.0)
Mesh: fir.mesh-0-0.rmesh 48210 triangles, LODs 24104/12051/6024
```
A count that is far too high for the model's size on screen is the cue to
bake its foliage into cards (see "Bake foliage into cards").
### The `.rmeta` file
Import writes `<file>.rmeta` next to the asset:
```json
{
"format_version": 1,
"id": "5cd9ac84-b54f-4ed7-88ad-c8e73cb76421",
"kind": "image",
"settings": {"max_size": null},
"dependencies": [],
"content_hash": "a5c162ecbe3adeea",
"source": {
"original": "crate.png",
"author": "Kenney",
"license": "CC0-1.0",
"url": null,
"generator": null,
"notes": null
}
}
```
- `id` is stable. It survives a reimport or a replacement, so tools can
follow an asset across edits. Scenes still refer to the file by path.
- `dependencies` lists the files a glTF loads, such as its `.bin` buffers
and textures.
- `cooked` lists the files import cooked beside the asset: a glTF's
`.rmesh` meshes, their `.lodN.rmesh` levels and `.rlod` groups, and its
`.rtexture` textures, or a `--linear` image's `-linear.rtexture`. The
field is absent when nothing was cooked. Reimport recooks the files and
rewrites the list.
- `content_hash` spots edits made to the file outside import.
- `source` is the license record. Keep the `.rmeta` with the asset in
version control.
## Use an asset in a scene
Scenes refer to a file by its path relative to the scene file. The
`reference` field of `asset import --json` gives that path. A texture goes
into a material slot:
```json
{
"operations": [
{"op": "set", "name": "Cube", "path": "/mesh_renderer/material/Inline/base_color_texture", "value": "../assets/textures/bricks_3.png"}
]
}
```
The other slots are `normal_texture`, `metallic_roughness_texture`,
`occlusion_texture` and `emissive_texture`; `uv_scale` repeats the
texture across a face. A plain image (`.png`, `.jpg`) in the normal,
metallic-roughness or occlusion slot loads as linear data, and one in the
base color or emissive slot loads as sRGB. A cooked `.rtexture` keeps the
color space it was cooked with. Run `rusting docs show component/mesh_renderer`
for every field.
A model goes in as one object with a child for each glTF node:
```bash
rusting scene add-model my_game/scenes/main.rscene my_game/assets/models/barrel.glb --name "Barrel 1"
```
`add-model` keeps the model's materials, textures, animations and skin.
It writes the converted meshes (`.rmesh`) and textures (`.rtexture`) next
to the model; keep them with the project. Run it again with another
`--name` for each copy, or `spawn_copy` the object in code.
A material whose textures carry a `KHR_texture_transform` (Blender and
Poly Haven exports set one for tiling bark and ground) has the offset,
rotation and scale folded into the mesh's UVs on import. A mesh has one
set of UVs, so the import stops with an error naming the material when
its textures use different transforms; bake them into `TEXCOORD_0` first.
Sounds are the exception: `rusting.sound_cue.clip` and `play_sound` take a
path relative to `assets/`, such as `sounds/hit.wav`. See
[Audio](audio.md).
### Cap texture size for low-end GPUs
`scene.set_max_texture_size(1024)` caps every texture side at 1024 pixels,
so a Low video preset loads only the 1K level of 2K textures. Larger
textures are halved until they fit and upload again when the cap changes;
0 removes the cap. HDR textures keep their full size. The cap is not saved:
set it again at startup like the other video settings.
## Replace an asset
```bash
rusting asset reimport my_game assets/props/crate.png --from art/crate_v2.png
```
Reimport replaces the file and keeps its ID. The asset is named by its
path or its ID. The new file comes from `--from`, else from the recorded
original if it still exists; otherwise reimport only checks the asset in
place. It takes the same provenance flags and `--max-size` as import, and
each given flag replaces the recorded value. A glTF also cooks its
`.rmesh` and `.rtexture` files again, next to the copy in the project.
If the project copy was edited after the last import, reimport without
`--from` keeps the edited copy instead of copying the recorded original
over it. It warns `ASSET_EDITED_COPY` and records the copy as the new
original, so the next reimport keeps it too.
Reimport reports the scenes that use the asset. A running game or the
editor reloads the file, so the change shows without a restart.
A file put under `assets/` by hand, or by `scene add-model`, has no
`.rmeta`. Reimport it to give it an ID and register it.
## List and check assets
```bash
rusting asset list my_game
```
```text
Asset: assets/models/barrel_4.glb (e01073d3-883d-4f5a-a711-8836b88e0aeb, license CC0-1.0)
Asset: assets/props/crate.png (5cd9ac84-b54f-4ed7-88ad-c8e73cb76421, license none)
warning [ASSET_NO_LICENSE]: no license recorded (.../assets/props/crate.png)
```
With `--json`, each asset also lists its dependencies, settings,
provenance and the scenes that refer to it (`referenced_by`). The command
fails on a missing file or dependency and on invalid or duplicate
metadata. It warns on a file changed since import, a file without
`.rmeta`, and an asset without a license. Run it before you ship.
`rusting validate` checks that every asset a scene refers to exists.
## Generate assets
`asset generate` makes a file from a prompt and imports it. Five
generators are built in, in one shared palette and chunky style. Each
gives the same CC0 file for the same prompt, and the optional number
picks a variation:
| `sfx` | `jump`, `coin`, `hit`, `explosion`, `laser`, `powerup` or `blip`, then a seed: `coin 7` | a WAV |
| `texture` | `grid`, `checker`, `bricks`, `planks`, `tiles` or `noise`: `bricks 3` | a 256 px tileable PNG |
| `sprite` | `circle`, `square`, `triangle`, `diamond`, `star` or `heart`: `star 2` | a 128 px outlined PNG on a clear background |
| `mesh` | `crate`, `barrel`, `rock`, `tree` or `gem`: `barrel 4` | a flat-shaded low-poly `.glb` standing on its origin |
| `font` | `pixel` or `bold`, with no number | a TrueType pixel font of printable ASCII, for `rusting.hud` `font` and `set_text_in_font` |
```bash
rusting asset generate my_game texture "bricks 3" --to textures
```
```text
Asset: assets/textures/bricks_3.png (2e0d5d19-64f4-48e8-9a46-3872c71afc82, license CC0-1.0)
```
`--replace <asset>` reimports an existing asset from the output instead,
keeping its ID.
### Your own generator
A project can add hooks that run any tool or model service. List them in
`project.json`:
```json
"generators": {
"icons": {"command": ["python3", "tools/make_icon.py"], "license": "CC0-1.0", "settings": {"max_size": 256}}
}
```
The engine runs the command with the prompt in `RUSTING_PROMPT` and an
empty folder in `RUSTING_OUTPUT_DIR`. The command writes one file there
and prints, as its last line, JSON naming it:
```json
{"file": "icon.png", "license": "CC0-1.0", "author": "...", "generator": "my-model v2"}
```
Only `file` is required. The file then goes through `asset import`, so
its type, size and metadata are checked like any other file. Output with
no license, from the hook or its config, is refused. A hook named `sfx`,
`texture`, `sprite`, `mesh` or `font` replaces the built-in one.
## Bake foliage into cards
Scanned and modelled trees often model every leaf or needle: a free
fir can have four million triangles. Simplifying such a mesh destroys the
crown. `asset bake-foliage` replaces the foliage with alpha-tested cards
instead:
```bash
rusting asset bake-foliage art/fir_tree_01_2k.gltf --node fir_tree_01_a_LOD0 --cards 1500 --impostor --out assets/trees/fir_a
```
```json
{"source_triangles": 4176819, "foliage_triangles": 4066119, "cards": 1500,
"card_triangles": 6000, "impostor_triangles": 4, "coverage": 0.52,
"files": ["assets/trees/fir_a-cards.glb", "assets/trees/fir_a-impostor.glb"]}
```
- Foliage is every triangle whose material is `MASK` or `BLEND`. The
triangles are grouped into `--cards` clusters, and each cluster's
texture is drawn onto two crossed quads, so each card costs 4
triangles. Texels the source texture cuts away are left out.
- The cards share one RGBA atlas (`--atlas`, 2048 px) and a `MASK`,
double-sided material. `--tint 3e4b2a` scales the atlas's mean colour
to that sRGB colour, to match an art direction.
- `--impostor` also writes two crossed quads showing the whole tree,
bark included, for the farthest level of detail.
- `--node` bakes one node and its children at that node's origin. Use it
for files that place several variants side by side.
- The bark (the opaque triangles) is not in the cards file. Draw it from
a simplified copy of the model, as a second object at the same place.
- The same model and flags always write the same bytes.
Import the files with `asset import`, then list the cards and the
impostor as the far levels of a `.rlod` (see
[Look and feel](look-and-feel.md)).
## Export the game
```bash
rusting export my_game ~/exports
```
```text
Exported: /home/you/exports/my_game_export
Game exit code: 0, timed out: false
```
Export validates and cooks the main scene, builds a release, and copies
into a new folder:
- the executable;
- the cooked main scene (`build/`);
- `project.json`, `assets/` and the source scenes in `scenes/`;
- the engine license, as `RUSTING_ENGINE_LICENSE.md`;
- a `README.txt` that says where settings and saves live.
It then copies the folder to a fresh temporary folder and runs the game
there for 60 headless ticks. A game that reads a file from outside its
folder fails this check with `EXPORT_VERIFY_FAILED`. An existing export
is never replaced; the next one is named `my_game_export_2`.
`--target x86_64-pc-windows-gnu` builds for another system, with that
Rust target installed. The folder is named after the system, such as
`my_game_windows_export`. A cross-target export cannot run here, so it is
not checked and export warns `EXPORT_NOT_VERIFIED`.
### Icon
Set `icon` in `project.json` to a PNG under `assets/`, so exports carry
it:
```json
"icon": "assets/icon.png"
```
The game window shows it on Windows and X11; Wayland shows the icon of the
desktop entry instead. A file that cannot be read prints a warning and
keeps the system's icon. The icon of the `.exe` file itself is not set
yet.
### Export presets
To keep the settings of each kind of build, name them in `project.json`
under `export_presets`:
```json
"export_presets": {
"windows": {"target": "x86_64-pc-windows-gnu", "features": ["steam"]},
"demo": {"features": ["demo"], "exclude": ["assets/source/**", "**/*.psd", "scenes/test_*"]}
}
```
Then export with one of them:
```bash
rusting export my_game ~/exports --preset demo
```
- `target` is the Rust target to build for. Leave it out to build for
this machine.
- `features` turns on Cargo features of the game's own crate. Declare
them under `[features]` in its `Cargo.toml`, and use
`#[cfg(feature = "demo")]` in game code.
- `exclude` leaves files out of `assets/` and `scenes/`. Patterns are
relative to the project folder: `*` matches within one name, `**` any
number of folders. The executable, the cooked main scene and
`project.json` are always copied.
- `sign` is a code-signing command, run on the exported executable before
the export is checked. Give the program, then its arguments; `{exe}`
becomes the executable's path, as in `["signtool", "sign", "/a",
"{exe}"]`. The command must sign the file in place. It runs in the
project folder.
When it fails, the export folder is deleted and export fails with
`EXPORT_SIGN_FAILED`.
- `pack` set to `true` packs the exported `assets/` and `scenes/` folders
into `assets.rpak` and `scenes.rpak` after excluded files are removed,
and deletes the loose folders. The game reads a file that is missing on
disk from the `.rpak` beside its folder, so asset paths stay the same and
a loose file still wins over the packed one. Files are read straight from
the pack without unpacking it; streamed audio holds the encoded clip in
memory. A glTF model imported while the game runs writes its cooked
meshes beside the source, so leave `pack` off for a game that calls
`add_gltf`; models placed in the editor are cooked already and load from
the pack.
Packing only happens at export.
- `zip` set to `true` zips the finished export folder into
`<folder>.zip` beside it, after it is signed and checked, ready to
upload to a store or share. Files sit under the folder's name inside
the zip and keep their Unix permissions, so the game stays executable
after unzipping on Linux. The files are stored without compression, and
a zip holds at most 65,535 files and 4 GiB.
The export's JSON lists the `features`, the `excluded` files, the
`packed` archives and the `zip` path. A native
export is still run headless, so leaving out a file the game loads fails
with `EXPORT_VERIFY_FAILED`. An unknown preset name fails with
`EXPORT_PRESET_UNKNOWN`, which lists the presets the project has.
Packed data is not compressed or encrypted yet.
### Crash reports
When game code or the engine panics, the game writes a crash report
before it exits and prints its path:
```text
crash report written to /home/you/.local/share/my_game/crashes/crash-1791682755782.txt
```
Reports go to `crashes/` in the user data folder, the same folder as
saves. Each one holds the engine version, the system, the panic message
and where it happened, and the stack trace with function names, such as
`my_game::update`. The folder keeps the 20 newest reports. Ask players
to send the newest file with a bug report.
A release build has function names but no file and line numbers in the
trace. To add them, put this in the game's `Cargo.toml`:
```toml
[profile.release]
debug = "line-tables-only"
```
Before you ship, run `rusting asset list` and add the credits that
`CC-BY` assets need to your README.