Log inGet started

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 .bundle suffix 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 every entity_template write; 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.json on 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 that Asset component automatically tears down its spawned children and rebuilds them from the new template — same frame, no restart, no manual reload. The onAssetReload hook in Asset.component/init.luau drives 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's original_id) at save time and are replayed by bundleRef:instantiate() on next load. The bundle stays a single source of truth; per-spot tweaks ride on top.
  • Stable child ids across reloads. public.idMap records the original_id → runtimeId assignment on first spawn; on every subsequent spawn the same mapping is reused so cross-entity references (e.g. SkinnedModel.skeletonRoot = "ent_xxx") stay valid.

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 into scene.json verbatim.
  • 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

  1. Registration. Writing into a .bundle/ folder indexes the asset with the container-category restore plumbing.
  2. Inner refs. Component fields that reference assets inside the bundle use the __ref shape ({ "__ref": "<guid>", "name": "...", "type": "..." }). The asset registry resolves them at spawn time.
  3. Identity preservation. The .bundle suffix stays in the identity for container types — <Name>.bundle resolves under that exact string, distinguishing it from any <Name>.scene at the same stem.
  4. Hot reload. Saving entity_template (or any file in the bundle directory) fires the engine's per-asset content-changed hook, which calls onAssetReload("source") on every entity whose Asset component 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() or asset.create("bundle","<name>",entity) over hand-authoring entity_template. The in-scene composer wires __ref shapes 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_template edits, animation hooks) find them.
  • Sub-bundles compose: a bundle's entity_template can __ref a child .bundle/. The asset registry resolves them transitively.

Common pitfalls

  • Confusing the two spawn paths. Asset component = live link, reference-only scene save. bundleRef:instantiate() directly = baked, full hierarchy in scene.json.
  • Inner-ref guids drift. If you copy an entity_template from one bundle into another, the __ref guids may not resolve — re-compose from a live scene rather than copy-pasting JSON.
  • The .bundle suffix 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.
  • Asset.component — the canonical mount point for a live-linked bundle. See its README for the idMap / diff machinery in detail.
  • .scene — owns a whole world's worth of entities; spawns bundles into itself via Asset components on per-spot entities.
  • asset-type
  • reference