← Index · Prev

Prefabs — linked .blend files

Reuse authored content across levels by treating each separate .blend file as a prefab: link it into your level scene, then apply Blender library overrides on the instances you want to customize.


What counts as a prefab

A prefab is a library .blend you link into the level you are exporting — not an app-level or runtime concept. Typical layout:

Linked objects arrive read-only. To move, retarget scripts, or tweak colliders on one copy without changing the library file, you need a library override on that instance (or on a parent collection that contains it).

Authoring workflow

  1. Build the prefab in its own .blend — components on objects, behaviors wired, colliders previewed, GUIDs assigned where references need them.

  2. Link into the levelFile → Link…, pick the prefab .blend, choose the collection (or objects) to bring in. The linked hierarchy appears in the level outliner.

  3. Make a library override — select the linked collection or object, then Object → Relations → Make Library Override (or the outliner override entry). You can override at collection scope so the whole prefab instance is editable as one unit.

  4. Customize the instance — transform, visibility, mesh slots, and Babylon components on the overridden copy. Unchanged fields keep tracking the library; overridden fields are stored on the level .blend.

  5. Export the level as usual — linked + overridden objects in the active scene are flattened into the level .glb and .scene.json like any other renderable object.

flowchart LR
  subgraph lib["Prefab .blend (library)"]
    P[Authored objects + components]
  end
  subgraph level["Level .blend"]
    L[File → Link collection]
    O[Library override on instance]
    E[Export glb + manifest]
  end
  P --> L --> O --> E

Editing components on overrides

Component data lives on Object.bjs_components (and nested PropertyGroups for @exposed fields, trigger rows, particle texture slots, etc.). Blender only allows overrides on RNA properties explicitly marked library-overridable.

The add-on registers that entire chain via core/props.py wrappers around bpy.props — every scalar field gets LIBRARY_OVERRIDABLE; collections (the component stack, exposed-var lists, trigger events, …) also get USE_INSERTION so you can add or remove rows on an instance.

After updating the add-on, disable and re-enable the extension (or restart Blender) so the flags register. Overrides created before that update may need to be re-applied or touched once so Blender records the new override operations.

Use the pin button in Babylon Object → Components when editing entity lists or picking trigger targets across a prefab hierarchy — the inspector stays on the prefab root while you change the viewport selection.

Sync Variables on an override

An override cannot delete its linked base rows — a collection with USE_INSERTION only lets you add or remove rows you inserted locally. So re-reading the .ts on a linked instance could never drop an @exposed var the script no longer declares; the stale row would linger.

To avoid that, Sync Variables treats the prefab source as the structural authority for overrides. When the object is a library override, operators/scripts.py resolves the read-only source object through override_library.reference, finds the matching SCRIPT component, and rebuilds field descriptors from its exposed_vars (build_fields_from_component) instead of parsing the file. The source is never modified — only read.

Because an override's linked rows are the source's rows, the only vars that can be dead on the instance are stray local insertions (from an earlier buggy sync or the enum-reindex corruption). Blender refuses remove() on an override collection (TypeError: … not supported for this collection), so those strays can't be deleted one at a time — but clear() is allowed and drops only the locally-inserted rows while keeping the linked base rows. The override sync therefore clear()s the collection (removing the strays), then reconciles the surviving linked rows in place against the source. Nothing is re-added — the linked rows already match by name — so this avoids the duplication a clear()+rebuild would cause (the classic trap: clear() keeps the linked rows, then re-adding them stacks duplicates).

The result: syncing cleans up strays, keeps per-instance value overrides, and leaves the instance a faithful mirror of the prefab. New @exposed names that exist in the script but not yet on the prefab source are also added as temporary local rows on the override (prefab source names still win for type and default). After you sync and save the prefab library, reload the library in the level and sync again — the temporary local row is cleared and the field lives on the linked base row. To drop a variable from linked instances, edit the script and run Sync Variables in the prefab file (overrides cannot remove linked base rows).

GUIDs and multiple instances

Entity identity is obj["bjs_id"] (see GUID trace). Linked copies initially share the library GUIDs. That is fine when one instance is in the level; for several instances of the same prefab, each object that becomes its own runtime entity needs a unique id.

On export, _dedupe_entity_ids (export/level.py) detects duplicate bjs_id values among scene objects and re-issues fresh GUIDs on the later duplicates. You can also assign overrides per instance ahead of time:

Duplicated linked instances often keep RNA entity pointers aimed at the first linked copy (e.g. Small Rover.003 still constraining to Engine Rover instead of the paired engine instance). Export does not guess or remap — it serializes the object you picked and that object's bjs_id. Re-pick cross-prefab targets on the level override (camera → vehicle, trailer → engine, constraint partners across prefabs). Validation warns when a pointer's instance suffix does not match the owner's.

References inside one prefab (wheels, arms, body self-ref) should point at objects inside that prefab hierarchy in the prefab source file. When you duplicate the prefab in the level, stale same-prefab pointers also need a level override re-pick — or fix them once in the prefab source before linking.

What overrides well vs what stays in the library

Typical override on an instanceUsually stays in the library file
Transform, parenting, viewport/render visibilityDefault mesh topology and materials
Per-instance script @exposed valuesDefault component stack layout
Collider size, body type, trigger messagesShared behavior .ts paths (relative to the app)
Per-instance GUID when duplicatedScene-wide settings (Scene.bjs_scene) — live in the level file only

Material overrides use Blender’s material library system; NME JSON and texture/input/gradient overrides on Material.bjs_nme_* follow the same library-override rules as other material data.

Live Link and prefabs

Live Link re-exports the active level .blend. Edit prefab sources freely, but remember:

Iterating on a prefab: save the library → switch to the level → save (or export once) to push changes through.

Runtime

The engine has no prefab loader — export produces a flat level. Each manifest entity is a glTF node plus components; parenting from Blender is preserved. Multiple instances of the same linked prefab appear as separate entities (with distinct GUIDs after dedupe). When instances share mesh data in the glb, later copies may load as Babylon InstancedMesh; colliders and reflection probes still resolve their geometry via the shared ownership rule in core/meshOwnership.ts.