entity
The entity namespace — the engine's Luau API reference for entity.
The entity namespace — 70 functions.
entity/__batchReadIds
entity.__batchReadIds(target, component?, field?, sink?) -> {any?} | number
Read a component-field across many entities in one FFI crossing. Polymorphic on the shape of target and sink:
entity.batchRead(ids)/(ids, comp)/(ids, comp, field)— returns one value per entity (table or scalar). Three sigs: ids-only is a per-entity snapshot, ids+comp is one component table per entity, ids+comp+field is one field value per entity. Missing entities/components/fields surface as nil at that slot.entity.batchRead(binding, comp, field, buffer)— reads each entity's field directly into a typed CPU substrate buffer (substrate.createBuffer({type="vec3"})etc.). Returns the count of successful reads. No JSON, no per-entity Lua table allocation. Vec3/Quat field shapes today; per-entity work is pointer arithmetic + 12/16-byte memcpy.
target accepts an entity-id array or a world.bindEntities(ids) handle. Buffer sinks require a binding (the typed kernel is binding-only). The dispatcher errors loudly when the shape combination is invalid.
entity/__batchReadToBuffer
entity.__batchReadToBuffer(binding, component, field, buffer) -> number
FFI primitive — backs entity.batchRead(binding, ..., buffer). Prefer the unified entity.batchRead (auto-dispatches by argument shape); this entry stays for power users / debug code, also reachable as entity.__batchReadToBuffer. Reads each entity's component field directly into a typed CPU substrate buffer (no JSON, no per-entity Lua table allocation). After the call, read the buffer via buf:read(0, count*stride). Vec3/Quat field shapes today.
entity/__batchWriteBound
entity.__batchWriteBound(binding, component, field, values) -> number
FFI primitive — backs entity.batchWrite(binding, ...). Prefer the unified entity.batchWrite (auto-dispatches by argument shape); this entry stays for power users / debug code that wants to skip dispatch overhead, also reachable as entity.__batchWriteBound.
entity/__batchWriteFromBuffer
entity.__batchWriteFromBuffer(binding, component, field, buffer) -> number
FFI primitive — backs entity.batchWrite(binding, ..., buffer). Prefer the unified entity.batchWrite (auto-dispatches by argument shape); this entry stays for power users / debug code, also reachable as entity.__batchWriteFromBuffer. Caller fills a typed substrate buffer (substrate.createBuffer({type="vec3"})) once via buf:write(flat_floats), then the substrate memcpys 12 (Vec3) or 16 (Quat) bytes per entity into the component field. Buffer count and binding count should match — mismatch processes min(both). Vec3/Quat field shapes today.
entity/__batchWriteIds
entity.__batchWriteIds(target, component, field, source) -> number
Write a single component-field across many entities in one FFI crossing. Polymorphic on the shape of target and source:
entity.batchWrite(ids, comp, field, values)— per-call entity-id resolution;valuesis a Lua array the same length asids(nil slots are skipped). Use for one-shot writes.entity.batchWrite(binding, comp, field, values)— binding handle fromworld.bindEntities(ids); skips per-call id resolution. Use for per-frame writes against a stable entity set.entity.batchWrite(binding, comp, field, buffer)— typed CPU buffer source (substrate.createBuffer({type="vec3"})etc.). Eliminates per-entity{x,y,z}table allocation; per-entity work is pointer arithmetic + 12/16-byte memcpy. Vec3/Quat field shapes today.
Returns the count of successful writes. Buffer sources require a binding (the typed kernel is binding-only). The dispatcher errors loudly when the shape combination is invalid.
entity/batchAddComponent
entity.batchAddComponent(ids, type_name, data?) -> number
Add the same component type to many entities in a single FFI crossing. Mirrors entity.batchSpawn: type validation, init-data conversion, and scene-context resolution all happen once instead of per-id, and the per-entity loop runs in Rust so very large N does not stress the Luau VM string interner. Returns the count of entities the component was added to (entities already carrying an unnamed instance of the same type are skipped to avoid duplicate-add #727).
entity/batchDespawn
entity.batchDespawn(ids) -> number
Despawn many entities in a single FFI crossing. Mirrors entity.batchSpawn for teardown: the per-id resolve + lock check + despawn queue runs in Rust, so very large N costs one Luau crossing instead of N. Locked or unresolvable entities are skipped. Returns the count queued for despawn.
entity/batchProxy
entity.batchProxy(ids: {string}) -> {entity}
Resolve an array of entity ids to proxies in a single FFI call. Each output slot is the standard entity(id) proxy; ids missing from the frame cache surface as nil at that index. Use when iterating over a snapshot of entities so per-id metatable lookups don't dominate the hot path.
entity/batchRead
entity.batchRead(target, component?, field?, sink?) -> {any?} | number
Read a component-field across many entities in one FFI crossing. Polymorphic on the shape of target and sink:
entity.batchRead(ids)/(ids, comp)/(ids, comp, field)— returns one value per entity (table or scalar). Three sigs: ids-only is a per-entity snapshot, ids+comp is one component table per entity, ids+comp+field is one field value per entity. Missing entities/components/fields surface as nil at that slot.entity.batchRead(binding, comp, field, buffer)— reads each entity's field directly into a typed CPU substrate buffer (substrate.createBuffer({type="vec3"})etc.). Returns the count of successful reads. No JSON, no per-entity Lua table allocation. Vec3/Quat field shapes today; per-entity work is pointer arithmetic + 12/16-byte memcpy.
target accepts an entity-id array or a world.bindEntities(ids) handle. Buffer sinks require a binding (the typed kernel is binding-only). The dispatcher errors loudly when the shape combination is invalid.
entity/batchReadToBuffer
entity.batchReadToBuffer(binding, component, field, buffer) -> number
FFI primitive — backs entity.batchRead(binding, ..., buffer). Prefer the unified entity.batchRead (auto-dispatches by argument shape); this entry stays for power users / debug code, also reachable as entity.__batchReadToBuffer. Reads each entity's component field directly into a typed CPU substrate buffer (no JSON, no per-entity Lua table allocation). After the call, read the buffer via buf:read(0, count*stride). Vec3/Quat field shapes today.
entity/batchSpawn
entity.batchSpawn(count, name_prefix?) -> { string }
Spawn count entities in a single FFI crossing. Returns an array of the new entity IDs in spawn order. Each entity is given a display name of <name_prefix><i> (or entity<i> if prefix is omitted). Use this instead of looping entity.spawn when creating large entity counts — the Luau-side per-call overhead drops from N to 1.
entity/batchWrite
entity.batchWrite(target, component, field, source) -> number
Write a single component-field across many entities in one FFI crossing. Polymorphic on the shape of target and source:
entity.batchWrite(ids, comp, field, values)— per-call entity-id resolution;valuesis a Lua array the same length asids(nil slots are skipped). Use for one-shot writes.entity.batchWrite(binding, comp, field, values)— binding handle fromworld.bindEntities(ids); skips per-call id resolution. Use for per-frame writes against a stable entity set.entity.batchWrite(binding, comp, field, buffer)— typed CPU buffer source (substrate.createBuffer({type="vec3"})etc.). Eliminates per-entity{x,y,z}table allocation; per-entity work is pointer arithmetic + 12/16-byte memcpy. Vec3/Quat field shapes today.
Returns the count of successful writes. Buffer sources require a binding (the typed kernel is binding-only). The dispatcher errors loudly when the shape combination is invalid.
entity/batchWriteBound
entity.batchWriteBound(binding, component, field, values) -> number
FFI primitive — backs entity.batchWrite(binding, ...). Prefer the unified entity.batchWrite (auto-dispatches by argument shape); this entry stays for power users / debug code that wants to skip dispatch overhead, also reachable as entity.__batchWriteBound.
entity/batchWriteFromBuffer
entity.batchWriteFromBuffer(binding, component, field, buffer) -> number
FFI primitive — backs entity.batchWrite(binding, ..., buffer). Prefer the unified entity.batchWrite (auto-dispatches by argument shape); this entry stays for power users / debug code, also reachable as entity.__batchWriteFromBuffer. Caller fills a typed substrate buffer (substrate.createBuffer({type="vec3"})) once via buf:write(flat_floats), then the substrate memcpys 12 (Vec3) or 16 (Quat) bytes per entity into the component field. Buffer count and binding count should match — mismatch processes min(both). Vec3/Quat field shapes today.
entity/despawn
entity.despawn(target)
Despawn an entity and all its components. Pass an id string or an entity proxy to despawn that ONE entity. Pass a name to despawn EVERY entity with that name — names are not unique, so a name argument despawns all matches (not one arbitrary match). A despawned id becomes invalid after this call. Errors if no entity matches; for a bulk name despawn, locked entities are skipped with a logged summary and only error if every match is locked.
entity/duplicate
entity.duplicate(sourceId, name?) -> string
Duplicate an entity with all its components (transform, script components, attributes, visuals, material). Returns the new entity's ID.
entity/exists
entity.exists(idOrProxy) -> boolean
Check if an entity currently exists in the scene. Accepts an entity-id string or an entity proxy (as returned by entity() / entity.find() / entity.findAll()), matched by EntityId — id/proxy only, so it agrees exactly with entity(). To check by name, resolve it with entity.find(name) and test that proxy.
entity/find
entity.find(name) -> entityProxy | nil
Find the first entity matching name. Matches against exact ID and Name component (plus same-frame pending spawns), skipping anything queued for despawn in the same frame. Returns a single entity proxy, or nil if nothing matches. Names are NOT unique — use entity.findAll(name) when you need every match.
entity/findAll
entity.findAll(name?) -> {entityProxy}
Enumerate entity proxies. With a name argument, returns every entity whose Name component or ID matches (names are not unique). With no argument, returns every entity in the current snapshot, including same-frame pending spawns. Same-frame despawns are filtered out. Empty array if nothing matches.
entity/getChildren
entity.getChildren(id) -> {string}
Get an array of child entity IDs for the given entity.
entity/getDescendants
entity.getDescendants(id) -> {string}
Get every descendant entity ID (children, grandchildren, and deeper) of the given entity in breadth-first order, excluding the entity itself. Resolves the whole subtree in one linear pass over the entity set, so a large subtree costs O(total entities) rather than the O(subtree x total entities) that recursively calling getChildren pays.
entity/getParent
entity.getParent(id) -> string | nil
Get the parent entity ID, or nil if the entity is a root entity.
entity/hierarchy/swap
entity.hierarchy.swap(entityId, assetPath, opts?) -> string | nil, string?
Replace a blockout entity with a generated/imported asset, fitting the asset to the source's bounds. Returns the new entity id, or nil + error string on failure. Options: { fit = "bounds" | "bounds_xy" | "none", source_origin = "bottom" | "center" | "top", asset_origin = "bottom" | "center" | "top", keep = {string}, timeout = number }.
entity/instantiate
entity.instantiate(handle: number, count: number, fn) -> { string }
Spawn count instances of a template registered with entity.template. Each instance gets a fresh entity id; the optional fn(i) callback runs Luau-side per instance (i in 1..=count) and may return an overrides table. Override keys: name, position {x,y,z}, rotation {x,y,z,w}, scale (number | {x,y,z}), parent (id), temporary/active/hidden (bool), attributes ({ key = value }), components (script components: { [type] = partial-data } merged over the template body's data for that type — a type the template lacks is added fresh), and ecs (native components: { ecs.X{...}, ... } merged the same way over the template's ecs data). Each override supersedes the template's shared config for that instance. The whole batch crosses FFI once and lands as a single BulkInstantiate deferred mutation the engine drainer expands into bulk ECS work — per-instance cost drops from a full Lua↔Rust roundtrip to one callback + one mutation push. Inside queue() the batch is deferred onto the cross-frame ring; outside, it lands in the next frame's drain. Returns the array of newly-minted entity IDs in spawn order.
entity/proxy/active
entity(id).active -> boolean
True if this entity's own active flag is enabled. Read-only — set via entity(id).setActive(bool).
entity/proxy/activeInHierarchy
entity(id).activeInHierarchy -> boolean
True if this entity and every ancestor are active. Disabling a parent makes the whole subtree inactive in hierarchy without overwriting each child's own active flag.
entity/proxy/attribute/get
entity(id).attribute.get(key) -> value | nil
Get a custom attribute value by key.
entity/proxy/attribute/list
entity(id).attribute.list() -> {string}
List all attribute keys on this entity.
entity/proxy/attribute/remove
entity(id).attribute.remove(key)
Remove a custom attribute from this entity.
entity/proxy/attribute/set
entity(id).attribute.set(key, value)
Set a custom attribute on this entity.
entity/proxy/bounds
entity(id).bounds() -> { min, max, center, size } | nil
World-space axis-aligned bounding box of this entity's own mesh geometry. Returns a table with min/max/center/size Vec3 fields, or nil if the entity has no mesh. Use hierarchyBounds() to include all descendants.
entity/proxy/component/add
entity(id).component.add(type, data?)
Add a component to this entity.
entity/proxy/component/addSynced
entity(id).component.addSynced(type, data?) -> table | nil
Add a component that replicates to all peers. Same as add() — including returning the new component's live public proxy — but forces sync registration even without a sync {} block.
entity/proxy/component/get
entity(id).component.get(type, instanceName?) -> table | nil
Get a component's live public proxy. Returns nil if not present. Writes through the proxy hit the running component.
entity/proxy/component/getAll
entity(id).component.getAll(type?) -> { {type: string, instance: string?, data: table} }
Every component on this entity as { type, instance?, data } records, where data is the live public proxy. Pass a type to get every instance of that type.
entity/proxy/component/getInParent
entity(id).component.getInParent(type) -> table | nil
Get the nearest component of the given type on this entity or any ancestor (self inclusive), or nil. The hierarchy lookup so callers never hand-roll a getParent() walk.
entity/proxy/component/has
entity(id).component.has(type) -> boolean
Check if this entity has a component of the given type.
entity/proxy/component/list
entity(id).component.list() -> {string}
List all component type names on this entity.
entity/proxy/component/remove
entity(id).component.remove(type)
Remove a component from this entity by type name.
entity/proxy/component/setEnabled
entity(id).component.setEnabled(type, enabled)
Enable or disable a component on this entity.
entity/proxy/despawn
entity(id).despawn()
Despawn this entity and all its components.
entity/proxy/destroy
entity(id).destroy()
Destroy this entity. Alias for despawn().
entity/proxy/getChildren
entity(id).getChildren() -> {string}
Get an array of child entity IDs.
entity/proxy/getParent
entity(id).getParent() -> string | nil
Get the parent entity ID, or nil if this is a root entity.
entity/proxy/hide
entity(id).hide()
Hide this entity's mesh and all its descendants' meshes from the rendered scene.
entity/proxy/hierarchyBounds
entity(id).hierarchyBounds() -> { min, max, center, size } | nil
World-space AABB encompassing this entity AND all its descendants' mesh geometry. For leaf entities, equivalent to bounds(). Returns nil if no geometry is loaded in the subtree.
entity/proxy/id
entity(id).id -> string
Entity unique ID. Use this for all API calls that require an entityId.
entity/proxy/name
entity(id).name -> string
Entity display name (from Name component). Returns an empty string when the entity has no Name component yet. Coherent with same-frame rename() and spawn(name) in the same execute() call.
entity/proxy/position/add
entity(id).position = { entity(id).position.x + dx, entity(id).position.y + dy, entity(id).position.z + dz }
Translate by (dx, dy, dz) in world space — read components and assign.
entity/proxy/position/get
entity(id).position.x / .y / .z
Read entity world position components (e.g. local x, y, z = e.position.x, e.position.y, e.position.z).
entity/proxy/position/set
entity(id).position = { x, y, z }
Set entity world position (also accepts { x=, y=, z= }, a vector, or another entity's .position). Single axis: entity(id).position.x = n.
entity/proxy/rename
entity(id).rename(newName)
Rename this entity (updates the Name component).
entity/proxy/rotation/get
entity(id).rotation.x / .y / .z / .w (or entity(id).rotation.eulerAngles)
Read entity rotation quaternion components, or read .eulerAngles for a { x=pitch, y=yaw, z=roll } degrees vec3.
entity/proxy/rotation/rotateAxisAngle
entity(id).rotation.rotateAxisAngle(ax, ay, az, radians)
Rotate this entity around an arbitrary axis. Composes the new rotation onto the current one (delta * current). Kept as a method (incremental operation).
entity/proxy/rotation/set
entity(id).rotation = { qx, qy, qz, qw } | entity(id).rotation.eulerAngles = { pitch, yaw, roll }
Set entity rotation. Assign a 4-component quaternion (auto-normalized), or assign .eulerAngles with degrees (ZYX).
entity/proxy/scale/get
entity(id).localScale.x / .y / .z
Read entity scale components.
entity/proxy/scale/set
entity(id).localScale = { x, y, z }
Set entity scale (single axis: entity(id).localScale.y = n).
entity/proxy/setActive
entity(id).setActive(active)
Enable or disable this entity (Unity-style activeSelf). Disabling fires onDisable on every script component of this entity AND its descendants — so Model/SkinnedModel hide their mesh — and stops them participating in activeInHierarchy checks; re-enabling fires onEnable again. Per-component enabled flags and each descendant's own active flag are preserved, so a child that was independently disabled stays disabled when its parent is re-activated.
entity/proxy/setParent
entity(id).setParent(parentId, opts?)
Set parent entity. The entity keeps its world-space position, rotation, and scale across the reparent; pass { keepWorldTransform = false } to keep the LOCAL transform instead, reinterpreted in the new parent's space.
entity/proxy/setTemporary
entity(id).setTemporary(temporary)
Mark this entity as temporary. Temporary entities behave normally at runtime but are excluded from scene.save() along with all their descendants. Use this to spawn helper entities from scene entrypoints that must not be baked into the saved scene.
entity/proxy/show
entity(id).show()
Show this entity's mesh and all its descendants' meshes in the rendered scene.
entity/proxy/temporary
entity(id).temporary -> boolean
True if this entity is explicitly marked temporary (carries the Temporary component). Temporary entities are excluded from scene.save() along with all their descendants. Read-only — set via entity(id).setTemporary(bool).
entity/proxy/temporaryInHierarchy
entity(id).temporaryInHierarchy -> boolean
True if this entity OR any ancestor is marked temporary — i.e. scene.save will skip this entity. Use this when you want to know whether something will actually get saved, irrespective of WHICH node in the chain flipped the flag.
entity/proxy/transform/forward
entity(id).transform.forward -> { x, y, z }
Read-only. Returns the entity's forward direction vector (local -Z axis) in world space, derived from the current rotation. Returns a normalized vec3 table.
entity/proxy/transform/right
entity(id).transform.right -> { x, y, z }
Read-only. Returns the entity's right direction vector (local +X axis) in world space, derived from the current rotation. Returns a normalized vec3 table.
entity/proxy/transform/up
entity(id).transform.up -> { x, y, z }
Read-only. Returns the entity's up direction vector (local +Y axis) in world space, derived from the current rotation. Returns a normalized vec3 table.
entity/proxy/unparent
entity(id).unparent(opts?)
Remove this entity from its parent; it becomes a root entity and keeps its world-space transform. Pass { keepWorldTransform = false } to keep the LOCAL transform instead.
entity/spawn
entity.spawn(name?, opts?) -> entityProxy
Spawn a new entity and return its PROXY (the same value entity(id) yields) — act on it immediately (entity.spawn(name).component.add(...), .localPosition = ...) with no second entity(id) round trip. The proxy still exposes .id for the rare site that needs the raw string. Entity starts with only a Transform and is invisible -- add components (Model, PointLight, etc.) to make it visible. Mirrors entity.find / entity.findAll, which also return proxies.
entity/spawnSynced
entity.spawnSynced(name?, opts?) -> entityProxy
Spawn an entity already flagged multiplayer-synced at the root — the explicit form of entity.spawn for SHARED, host-authoritative content. The entity's existence broadcasts to every peer; joiners receive it from the relay snapshot instead of spawning their own copy. Use this (NOT entity.spawn) for anything that must be the SAME object on all clients: enemies, pickups, projectiles, dynamic world props. Call it ONLY where exactly one client runs the code — a scene's onHostLoad (host-only) phase, or behind multiplayer.isHost(). Calling it in all-client code (onLoad, a component awake that runs on every peer) makes every client spawn+sync its own copy — the double-spawn 'explosion'. Equivalent to entity.spawn(name, { synced = true }); identical in every other respect.
entity/template
entity.template(def: { components?, ecs?, temporary?, active?, hidden?, attributes? }) -> number
Construct a reusable spawn template. Captures a shared entity config ONCE (validated + converted to ScriptValue) and returns a stable integer handle for entity.instantiate(handle, count, fn?) — Bevy Commands::spawn_batch analog, one FFI crossing per batch. def keys: components ({ [type] = init-data } script components), ecs (array of ecs.X{...} native components), temporary (bool — instances are non-persistent, skip-on-save), active/hidden (bool spawn state), attributes ({ key = value } applied to every instance). Every config value is a shared default; a per-instance instantiate override supersedes it. The template body is captured by value: later edits to the source table do NOT affect created templates.