Bundle (asset type)
A bundle is a composable, spawnable entity assembly— a saved entity hierarchy plus its components, sub-assets (meshes, textures, animations, materials, sub-bundles), and any nested data, packaged…
There are two ways to bring a bundle into a scene — and the difference matters enormously for scene saving and live editing. See "How to spawn" below before you write anything.
If you want one snapshot of a whole world / level, use a .scene.
Where it lives
- Source:
/zero/source/.../<Name>.bundle/ - Identity:
<Name>(the.bundlesuffix strips for use in identities, but the on-disk folder keeps the suffix). - Folder shape (container type — open child set):
entity_template— JSON of the entity hierarchy + components. Required.preview.png— a rendered still of the assembled hierarchy, the bundle's persisted visual description. Required. Regenerated on everyentity_templatewrite; it syncs, publishes, feeds image-based search, and gives browsers a thumbnail.- Anything else: meshes / textures / materials / animations /
sub-bundles. Validator allows arbitrary additional files
(
allow_unlisted: true).
How to create one
Two paths, depending on whether you have an in-scene hierarchy or are authoring from scratch:
-- From an existing in-scene entity: composes the given entity tree into a bundle.
local bundle = asset.create("bundle", "<Name>",<entityProxy/id>)
-- Or scaffold an empty bundle, then update it from an entity:
local bundleRef = asset.create("bundle", "<Name>")
bundleRef:update(entityId)
How to spawn
1. Asset component — live-linked (the canonical way)
This is the primary way to put a bundle into a scene that survives
saving as an asset reference. Spawn an empty entity, attach the
Asset component, point it at the bundle:
local car = entity.spawn("car_1")
car.component.add("Asset", { source = "<bundleIdentity>" })
What you get:
- The bundle's hierarchy spawns as temporary children under the
owning entity. They're real, addressable runtime entities, but
they are NOT serialized into
scene.jsonon save, only the Asset component and its entity is saved. - Live editing of the bundle is supported. When you edit
anything inside the
.bundle/(entity_template, meshes, materials, sub-bundles, components), every entity in the world carrying thatAssetcomponent automatically tears down its spawned children and rebuilds them from the new template — same frame, no restart, no manual reload. TheonAssetReloadhook inAsset.component/init.luaudrives this. - Per-instance overrides survive across reloads. Move a spawned
child, rename it, add/remove components on it, edit a component's
public state — the changes land in the Asset's
public.diff(a sparse delta keyed by the child'soriginal_id) at save time and are replayed bybundleRef:instantiate()on next load. The bundle stays a single source of truth; per-spot tweaks ride on top. - Stable child ids across reloads.
public.idMaprecords theoriginal_id → runtimeIdassignment on first spawn; on every subsequent spawn the same mapping is reused so cross-entity references (e.g.SkinnedModel.skeletonRoot = "ent_xxx") stay valid.
2. bundleRef:instantiate — baked, no live link
Resolve the bundle and instantiate it onto an entity — this spawns the
children without the live-link plumbing the Asset component
provides:
local car = entity.spawn("car_1")
asset.resolve("<Name>", "bundle"):instantiate(car)
What you get — and the trade-offs:
- The children spawn as regular (non-temporary) entities. On the
next
layers.active:save(), the entire spawned hierarchy is serialized intoscene.jsonverbatim. - No live link to the bundle. Editing the
.bundle/after the fact has no effect on already-spawned hierarchies. The bundle was effectively consumed at spawn time. - Scene-save / restore round-trip works the same as for any other hand-authored entity tree.
Use this path when you want a one-shot expansion of the bundle into permanent scene state (e.g. you're populating a scene's static layout from a parametric bundle and don't want a live link).
You can convert a live-linked instance into a baked one explicitly
via Asset:bakeIntoScene() — this drops the Asset component and
promotes the temporary children to permanent scene entities.
How it operates
- Registration. Writing into a
.bundle/folder indexes the asset with the container-category restore plumbing. - Inner refs. Component fields that reference assets inside the
bundle use the
__refshape ({ "__ref": "<guid>", "name": "...", "type": "..." }). The asset registry resolves them at spawn time. - Identity preservation. The
.bundlesuffix stays in the identity for container types —<Name>.bundleresolves under that exact string, distinguishing it from any<Name>.sceneat the same stem. - Hot reload. Saving
entity_template(or any file in the bundle directory) fires the engine's per-asset content-changed hook, which callsonAssetReload("source")on every entity whoseAssetcomponent points at the bundle. Each carrier despawns its children and re-instantiates against the new template — typically next frame.
Discovery
asset.list("bundle")— every registered bundle.asset.inspect("<name>")— sub-asset catalog, source, this type README.cat /zero/source/<Name>.bundle— same summary.
Authoring conventions
- Prefer
bundle:update()orasset.create("bundle","<name>",entity)over hand-authoringentity_template. The in-scene composer wires__refshapes correctly and pulls in every transitively-needed asset. - Keep bundles focused — one prefab per bundle. If you have a "vehicle
- driver + sound" assembly, split into three bundles and reference them from a scene rather than packing all three into one bundle.
- Name child entities meaningfully — bundle interiors are
inspectable, and stable IDs let other tooling (
entity_templateedits, animation hooks) find them. - Sub-bundles compose: a bundle's
entity_templatecan__refa child.bundle/. The asset registry resolves them transitively.
Common pitfalls
- Confusing the two spawn paths.
Assetcomponent = live link, reference-only scene save.bundleRef:instantiate()directly = baked, full hierarchy inscene.json. - Inner-ref guids drift. If you copy an
entity_templatefrom one bundle into another, the__refguids may not resolve — re-compose from a live scene rather than copy-pasting JSON. - The
.bundlesuffix is part of the identity. This is the container-category convention. Don't strip it in reference strings. - Live editing only fires for live-linked carriers. Already-baked entities don't update when you edit the source bundle.
Related types
Asset.component— the canonical mount point for a live-linked bundle. See its README for theidMap/diffmachinery in detail..scene— owns a whole world's worth of entities; spawns bundles into itself viaAssetcomponents on per-spot entities.