Scene and prefab files
A scene is a JSON file named *.scene.json under the project’s asset folder. It is the same SceneData that SceneSerializer reads, so a game loads the file the editor wrote without any editor code.
{ "version": 2, "entities": [ { "uid": "00000000-0000-4000-8000-000000000003", "components": { "children": { "firstChild": "00000000-0000-4000-8000-000000000004", "lastChild": "00000000-0000-4000-8000-000000000004", "childCount": 1 }, "name": { "value": "Tinted Crate" }, "position": { "x": 220, "y": 0, "z": 0 } } }, { "uid": "00000000-0000-4000-8000-000000000004", "components": { "name": { "value": "Badge" }, "parent": { "entity": "00000000-0000-4000-8000-000000000003", "nextSibling": null, "prevSibling": null }, "position": { "x": 0, "y": -90, "z": 0 } } } ]}(Shortened: the editor writes one field per line.)
What the editor writes
Section titled “What the editor writes”- Entities in hierarchy order. Root entities in the order the hierarchy panel shows them, each followed by its descendants. Reordering roots in the editor reorders the file.
- Components sorted by key, fields in schema order.
- Shortest numbers. A
Float32is written as the shortest decimal that reads back to the same value:0.1, not0.10000000149011612. - Tabs and a final newline.
Together these give the property that matters for version control: opening a scene and saving it without changes writes the same bytes. A diff shows what was edited and nothing else.
Scene files are machine-written. Keep formatters away from them (for Prettier, add the asset folder’s *.json to .prettierignore). Editing one by hand is fine; the next save from the editor puts it back into this form.
What is kept that the editor does not understand
Section titled “What is kept that the editor does not understand”- Components no plugin registers. If a scene uses a component the edit world does not know (a plugin is missing, or a branch has not been merged), the editor keeps that component’s data out of the world, warns once, and writes it back unchanged on save.
- Other top-level keys and metadata, such as
resources, are written back as they were.
Editor-only data
Section titled “Editor-only data”Components whose serial key starts with editor: exist for the editor. They are stored apart from the entity, so a game that loads the file never sees them:
{ "version": 2, "entities": [{ "uid": "a1", "components": { "name": { "value": "Spawn Point" } } }], "metadata": { "editor": { "components": { "a1": { "editor:note": { "text": "check collision here" } } } } }}Entity identity
Section titled “Entity identity”Every entity has a uid. References between entities (the parent and children components, and any field of type Entity) store UIDs. The editor keeps UIDs stable: undoing a delete restores the same UID, and only copies (duplicate, paste) get new ones, with references inside the copied group pointing at the copies.
Assets in component fields
Section titled “Assets in component fields”A field that refers to an asset stores the URL the game loads it from: the project’s assetBaseUrl plus the asset’s path, such as /assets/textures/crate.png.
When an asset is renamed or moved in the editor’s Assets panel, the editor updates these URLs: in every other scene and prefab file directly (only the URL changes, the rest of the file keeps its bytes), and in the open scene as an undo step. A file moved outside the editor, in a file manager or with git mv, is not followed.
Prefab files
Section titled “Prefab files”A prefab is a JSON file named *.prefab.json: a tree of nodes, each with components and children. It is the PrefabData that PrefabSerializer.deserialize() turns into a prefab the game can spawn.
{ "version": 2, "root": { "id": "6f1c2a9e-5b7d-4c3a-9e21-0d4f8a7b6c55", "components": { "example:spin": { "speed": 2, "clockwise": true }, "position": { "x": 0, "y": 0, "z": 0 } }, "children": [ { "id": "b2a4c6e8-1f3d-4a5b-8c7d-9e0f1a2b3c4d", "slotName": "moon", "components": { "position": { "x": 70, "y": 0, "z": 0 } }, "children": [] } ] }}(Shortened in the same way.)
| Field | Meaning |
|---|---|
id | Identifies the node across edits. The editor writes it; a file without ids (version 1) gets them on its first save. |
slotName | The name code uses to reach a child of a spawned prefab: spawned.get('moon'). Children only. |
components | By serial key, sorted, as in a scene. |
children | Child nodes, in order. |
Opened in the editor, a prefab is edited like a scene: its nodes are entities in the hierarchy, and the slot name of a child is the “Prefab Node” section in the inspector. Three things differ from a scene:
- One root. A prefab is a single entity with what is inside it. The editor refuses to save a prefab document that has several root entities, and says so.
- No references to entities. A field that points at another entity is left out: a template has no entities to point at.
- Editor-only data only for what it holds. A prefab that holds instances of other prefabs keeps their links under
metadata.editor, as a scene does. A prefab that holds none has nometadata.
As with scenes, saving an unchanged prefab writes the same bytes, and components no plugin registers are written back unchanged.
Prefab instances in a scene
Section titled “Prefab instances in a scene”Adding a prefab file to a scene puts its nodes into the scene as ordinary entities with new UIDs. The scene file holds them in full, so the game loads the scene without knowing about prefabs. What ties them to the prefab is editor-only data:
"metadata": { "editor": { "components": { "a1": { "editor:prefabInstance": { "source": "prefabs/orbiter.prefab.json" }, "editor:prefabLink": { "source": "prefabs/orbiter.prefab.json", "node": "7c9e…", "overrides": "+name example:spin.speed position.x position.y", "removed": "" } }, "a2": { "editor:prefabLink": { "source": "prefabs/orbiter.prefab.json", "node": "f02b…", "overrides": "", "removed": "" } } } }}| Key | Meaning |
|---|---|
node | The id of the prefab node the entity stands for. |
overrides | What the instance changed, space separated: key.field for a field, +key for a component it added, -key for one it removed. Written on save. |
removed | On the root: the ids of the prefab nodes this instance deleted. |
An instance follows its prefab file. When the file changes, every field, component, and node an instance did not change itself is taken from the file again:
- Scene files that are not open are rewritten as soon as the prefab file changes, whoever changed it: the editor, an agent, or a text editor. This needs the editor to be running. Run Update Prefab Instances from the command palette for prefabs that changed while it was not.
- A scene is brought in step when it is opened. If that changed anything, it opens with unsaved changes.
- The open scene follows at once when it has no unsaved changes, and after its next save when it has.
An entity added inside an instance is kept. An entity of an instance cannot be moved within it or out of it; the instance moves as a whole. Unpack Prefab Instance turns an instance into plain entities.
In the inspector, a field the instance changed has a bold label. The component’s “More” menu has Revert to Prefab, and Apply to Prefab, which writes the component into the prefab file. Undo takes back what Apply did to the scene, not what it wrote to the file.
A prefab inside a prefab
Section titled “A prefab inside a prefab”Open a prefab and add another prefab to it (Add to Prefab in the assets panel, or a drop on the hierarchy or the scene view). It is an instance, placed under the prefab’s root, and it follows its file the way an instance in a scene does: bold labels for what the outer prefab changed, Revert to Prefab, Apply to Prefab.
The outer prefab’s file still holds every node in full, so the game spawns it without knowing what it was built from. The children keep the slot names their own prefab gave them. The links are beside the nodes, keyed by node id:
{ "version": 2, "root": { "id": "b1", "components": {}, "children": [{ "id": "a1", "components": { "…": {} }, "children": [] }] }, "metadata": { "editor": { "components": { "a1": { "editor:prefabInstance": { "source": "prefabs/lot.prefab.json" }, "editor:prefabLink": { "source": "prefabs/lot.prefab.json", "node": "7c9e…", "overrides": "+name position.x", "removed": "" } } } } }}A change travels through the files. When the inner prefab changes, the editor rewrites every prefab file that holds it, then every prefab file that holds one of those, and then the scenes. As with scenes, this needs the editor to be running; Update Prefab Instances catches up afterwards.
A scene knows only the prefab that was added to it. Inside its instance, the entities of the inner prefab are nodes of the outer one: Apply to Prefab there writes to the outer prefab, as its change to the inner one. To change the inner prefab, open it.
A prefab cannot hold itself, directly or through a prefab it holds. The editor refuses to add one, and refuses to save a prefab that came to hold itself another way.
There is no scene inside a scene. What several scenes share is a prefab, added to each.
A prefab defined in code is followed too. Its source reads code:<key>, with the key it has in the project’s prefabs. A change to its code reloads the editor; the scene that opens has followed, and the scene files that are not open are rewritten then. It has no file, so Apply to Prefab and Open Prefab are not offered for it: change the code.
The root’s position is a field like any other: an instance left where the prefab puts it moves when the prefab’s root moves.