Log inGet started

Component (asset type)

A component is a reusable behavior + data unit attached to an entity. The component file declares public fields, methods, and lifecycle callbacks; the engine instantiates one per attachment and…

When to use one

  • You need per-entity state that survives across frames.
  • You need per-entity behavior driven by engine lifecycle hooks (start, update, destroy).
  • You want to expose configurable fields to designers / entity.spawn callers / scenes without code changes.

If you need a global service that outlives any entity, use a .service instead. If you need a stateless pure-function library, use a .module.

Where it lives

  • Source: /zero/source/.../<Name>.component/
  • Identity: <Name> (the .component suffix strips from the identity, but the folder retains the suffix on disk).
  • Folder shape:
    • init.luau (or init.lua) — declares the component. Required.
    • README.md — instance-level documentation. Required.

The structural contract is enforced by asset.validate against this type's type.yaml.

How to create one

Always start with the scaffold — it guarantees the folder shape matches type.yaml from the moment it's on disk:

asset.create("component", "<Name>")
-- Creates: /zero/source/<Name>.component/
--   init.luau   (canonical declare + public block + hooks stubbed)
--   README.md   (instance README template)

After scaffolding, edit init.luau to fill in the actual fields and behaviour. The component registers as soon as the file is written — hot-reload is automatic; no engine restart required.

How it operates

  1. Registration. Writing init.luau into a .component/ folder indexes the asset, mints a guid (in the .meta sidecar), and surfaces the type to the engine's component registry. Same-frame visible to entity.component.add.

  2. Attachment. entity(id).component.add(name, data) (or the batch form sc.addComponent(ids, name, data)) attaches an instance. The data table's fields override the component's declared defaults; omitted fields take the declared default. The data values are copied into public.* BEFORE awake() runs.

  3. Lifecycle. The hooks run in this order:

    • awake() — same frame as attach, immediately after the data copy. Initialise state, set up native bridges, register with other systems. Other components on the same entity may not have awoken yet.
    • start() — the next frame. Every component on the entity has completed awake() by then, so cross-component setup is safe here.
    • update(dt) — same frame as start(), then every frame after. Play mode only — edit mode does not call update / fixedUpdate / editorUpdate. See man modes.
    • onDestroy() — when the component is removed via component.remove or when the owning entity is despawned. Release resources, drop native ECS components.

    For the full list (onEnable, onDisable, fixedUpdate, editorUpdate, onPropertyChanged, onAssetReload, onBeforeSave, …) and the timing details, read man components — that is the canonical lifecycle reference. Generic component patterns (declare blocks, public fields, sync, ECS bindings, typed methods) all live there.

  4. Public surface. Fields declared in public are inspectable, editable in the inspector, and persist with the entity. Methods declared on the table become callable via entity(id).component.get(name):method(args).

  5. Events. An events block declares typed moments the component fires from inside — events = { onHit = Event({ dmg = Field.number(0, NoSync) }) }. The owner fires with events.onHit:fire({ dmg = 10 }); outside code subscribes through the proxy's subscribe-only facade, entity(id).component.get(name).events.onHit:connect(fn) (:once / :wait too). Pass Sync as the event's second argument to replicate a fire to other peers. Events let one component react to another's moments without polling. Full reference: man components, Events section.

  6. Hot reload. Editing init.luau reloads the component definition; live instances are reconciled with the new declaration in-place.

Discovery

  • asset.list("component") — every registered component (engine + world + libraries).
  • asset.inspect("<name>") — identity, scope, public fields, lifecycle methods, source path, and this type README. detail carries the component's declared fields, methods, and events, parsed from its source — tools.use("assets","describe", "<name>") renders it as markdown, so a component's events are visible without opening its source.
  • cat /zero/source/<Name>.component — the raw filesystem view (a different projection than asset.inspect).
  • entity(id).component.list() — components attached to an entity.

Authoring conventions

  • Annotate public fields with --!desc so the inspector + LSP show documentation alongside the field name.
  • Declare lifecycle hooks (start, update, destroy) explicitly rather than relying on engine defaults — readers can see at a glance what runs when.
  • Keep update(dt) cheap — it runs every frame per instance. Move heavy work to start, to a service, or behind a job (man jobs).
  • For multi-entity batching, prefer sc.addComponent(ids, ...) over a loop of entity(id).component.add — same hot-path, one mutation enqueue per batch.

Common pitfalls

  • Naming. The component identity is the folder stem with .component stripped (e.g. <Spinner>.component/ → identity Spinner). Don't repeat the suffix in the identity passed to component.add.
  • init.lua vs init.luau. Either is accepted (it's a one_of_group: "entrypoint"), but .luau is canonical. Mixing both in one folder is invalid.
  • update in edit mode. It doesn't run — by design. Use wld.play() to test update logic; wld.edit() to return.
  • Renaming. Renaming the folder changes the identity. Update every component.add(<oldname>, ...) call and the scene JSON entries that reference it.
  • .module — pure-function library, no per-entity state.
  • .service — globally-scoped behavior, no per-entity state.
  • .preset — a captured component configuration, replayable into component.add without re-typing fields.
  • .bundle — entity hierarchies that may carry component attachments as part of the bundle.
  • asset-type
  • reference