Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit44fd9e59bd

README.md

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 into a single addressable identity. Bundles are the engine's "prefab" primitive.

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.
    • .metadata: optional.
    • 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>", { entity = entityRef })


-- 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.

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 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. The .bundle suffix is stripped, so a bundle resolves as <Name> (asset.resolve("<Name>", "bundle")); the type argument tells it apart from any <Name> of another type.
  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.
  • ls /zero/source/<Name>.bundle lists the files the folder holds.

Parts and slots

A bundle can be an object assembled from parts, and a part that fills one. A child carrying a Slot component is a named place another part fills (its kind, the tags a part must carry, a default part, and where the part attaches: that child's transform). A bundle whose root carries a Part component is a part, and its fits names the slot kinds it fits. An Assembly on the root holds what fills each slot by path (wheel_fl, engine/turbo, a part's own slots nesting under the slot it fills), and resolves, swaps and explains them at runtime:

local car = entity.spawn("car")
car.component.add("Asset", { source = "Buggy" })     -- a bundle with slots and an Assembly at its root
local asm = car.component.get("Assembly")
asm:configure({ wheel_fl = "SportWheel", engine = { part = "V8", turbo = "Turbo" } })
asm:fill("wheel_fl", "OffroadWheel")                 -- rebuilds that wheel alone
print(asm:fits("wheel_fl", "V8"))                    -- false, and why

The configuration is saved with the scene and synced; the parts are runtime children rebuilt from it. guides { path: "topics/assemblies" } covers the whole family.

Authoring conventions

  • Prefer bundle:update() or asset.create("bundle", "<name>", { entity = ref }) 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.
  • Copying JSON between bundles. A copied entity_template does not bring the assets it references along with it; re-compose from a live scene rather than copy-pasting JSON.
  • 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 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.

In the Inspector

Selecting a bundle opens its Contents section, from the bundle's inspect detail: the template's node count, its root, the component types attached across it, a table of each renderable node's mesh and material, and a link to each of those meshes and materials.