Skip to content

Project export format

File ▸ Export ▸ Export Project writes the whole project as one ZIP archive. Live Sync serves exactly the same files to a running game. This page describes those files so you can load them in your own engine.

Map_1.lostproj the manifest, named after the first map
maps/<name>.lostmap one per map
tilesets/<name>.lostset one per tileset
entities/<name>.lostentity one per entity
resources/<name>.png tileset images
index.json the list of texture sprite sheets and animation files
sprites/<name>_<id>.png one sprite sheet per texture
animations/<name>_<id>.lostanim one per texture

An animated tileset also writes resources/<name>_frame<N>.png for each frame. The export is deterministic: the same project always gives the same bytes.

A name becomes a file name like this: letters, digits, _ and - are kept, spaces become _, and everything else is dropped. Case is kept, so My Map! becomes My_Map. Names that only differ in case get _2, _3 and so on. A name with nothing left becomes unnamed.

Renaming something in the editor renames its file, so to your game it looks like one file deleted and another added.

  • All text files are UTF-8 JSON.
  • Positions and sizes are in pixels. X runs right and y runs down.
  • Ids are 64-bit integers, such as 4378603846990036993. Read them as 64-bit integers. JavaScript’s JSON.parse rounds them, so in Node.js 22 or a Chromium browser, keep them exact with a reviver:
const data = JSON.parse(text, (key, value, { source }) =>
typeof value === 'number' && !Number.isSafeInteger(value) ? BigInt(source) : value,
);

Lists every other file and the project’s physics layers.

{
"version": "2.1",
"name": "Map 1",
"hash": null,
"lastModified": "live",
"resources": {
"tilesets": ["tilesets/Ground.lostset", "tilesets/Tiles.lostset"],
"maps": ["maps/Map_1.lostmap"],
"entities": ["entities/Coin.lostentity"]
},
"physicsLayers": [{ "index": 0, "name": "Default", "collidesWith": [0] }]
}

physicsLayers comes from Project Settings ▸ Physics. Colliders refer to a layer by its index.

{
"version": "7.2",
"id": 4378603846990036993,
"name": "Map 1",
"layers": [ ... ],
"entities": [ ... ],
"points": [ ... ],
"colliders": [ ... ]
}

layers is in draw order, back to front. Layer groups from the editor are flattened out. Every layer has id, name, type, visible, order, parallaxX, parallaxY, offsetX, offsetY, tint (r, g, b, a, where a already includes the layer’s opacity), blendMode and properties. type is tile, entity or collision.

A tile layer also has tileWidth, tileHeight and mode:

  • mode: "grid": tiles are stored in chunks of chunkWidth × chunkHeight cells (8 × 8). The key is the chunk’s position, "cx,cy", and the value is 64 tile ids, row by row. Cell i of chunk "cx,cy" is map cell (cx * 8 + i % 8, cy * 8 + floor(i / 8)). A 0 is an empty cell.
  • mode: "free": tiles are placed anywhere, as tiles: [{ "x", "y", "tileId" }] in pixels.

A tile id packs a tile’s place in a tileset into one number:

Bits Meaning
0–15 x of the tile in the tileset image, in pixels
16–31 y of the tile in the tileset image, in pixels
32–45 the tileset’s order, from its .lostset (tilesets are numbered from 1 by name)
46 flipped horizontally
47 flipped vertically

Tile ids stay below 2⁵³, so plain numbers are safe in JavaScript. Use division rather than bit operators, which only work on 32 bits:

function decodeTile(id) {
return {
x: id % 65536,
y: Math.floor(id / 65536) % 65536,
tileset: Math.floor(id / 2 ** 32) % 2 ** 14,
flipX: Math.floor(id / 2 ** 46) % 2 === 1,
flipY: Math.floor(id / 2 ** 47) % 2 === 1,
};
}
decodeTile(4294967296); // { x: 0, y: 0, tileset: 1, ... }: the top-left tile of tileset 1

Each item in entities is one placed entity:

{
"id": 9, "name": "", "entityDefId": 2, "layerId": 4,
"x": 64, "y": 32, "rotation": 0, "flipX": false, "flipY": false,
"scale": { "x": 1, "y": 1 }, "zIndex": 0,
"properties": { "value": "5" }, "refs": {}
}

entityDefId is the id of an entity’s .lostentity file. properties only holds the values changed on this instance; everything else is the entity’s default. Property values are written as strings, even numbers and booleans: convert them with the property’s type from the .lostentity.

points are named positions, with x, y, name, type, nodes (for paths) and properties. colliders are polygons: position is the centre and points are relative to it, plus name, type, physicsLayer and properties.

{
"version": "1.0",
"id": 4378603846990036994,
"name": "Ground",
"order": 1,
"imagePath": "resources/Ground.png",
"textureId": 4378603846990036995,
"tileWidth": 16,
"tileHeight": 16,
"tiles": [],
"terrainLayers": []
}

tiles only lists tiles that carry extra data from the tileset editor: a name, type, colliders, properties or animation frames. order is the number used in tile ids.

An entity has id, name, type, sprites, colliders and properties. Each property is defined as { "name": "value", "type": "integer", "default": "1", "options": [] }: type is one of string, number, integer, float, boolean, enum, color and ref, and options lists an enum’s choices. Each sprite has its source and how it is drawn:

  • From a tileset: tilesetId and rect (x, y, width, height in the tileset image).
  • From a texture: textureId and animation, which point into the sprite sheets below.
  • Placement: offset, origin, scale, rotation, panX, panY, tint, blendMode and zIndex.
  • name, type and mask, as set on the sprite in the entity editor.

index.json lists one sprite sheet per texture and its animation file:

{
"name": "Textures",
"resources": [{
"id": "tex_4378603846990036997", "name": "Coin",
"path": "sprites/Coin_4378603846990036997.png",
"tileWidth": 16, "tileHeight": 16,
"imageWidth": 12, "imageHeight": 12, "packed": true
}],
"animationFiles": [{ "name": "Coin", "path": "animations/Coin_4378603846990036997.lostanim" }]
}

A .lostanim holds the texture’s animations. Each has a name, loop, durationMs and tracks. A sprite track lists keys: at timeMs, draw rect from the sprite sheet named by spritesheetId.

With Project Settings ▸ Pack sprite sheets on (the default), frames are trimmed to their pixels and identical frames are stored once. That is why the 16 × 16 coin above has a 12 × 12 sheet. rect is where the trimmed frame sits in the sheet, and trim is where it goes inside the full tileWidth × tileHeight frame. With it off, the sheet is a plain grid with one cell per frame.