Instances
The fundamental building block of Luduvo worlds is the Instance. Every single entity in a place (whether it’s a solid geometric part, a dynamic point light, a user interface button, or a player character) is an instance.
Gameplay code spends most of its time finding instances, moving them around the tree, copying them, and tearing them down. This page covers the rules each of those operations follows.
How the engine stores and walks that tree inside: Entities and Instances.
The place tree#
The engine arranges all instances into an n-ary tree known as the place tree. Each instance carries:
The parent is the instance directly above it in the tree, and the children are the instances directly below. The components hold the transform, mesh and material, tags, attributes, and script bindings.
Because parent-child relationships define both spatial coordinate hierarchies and lifecycle visibility, manipulating instances in scripts directly alters this underlying tree structure.
Accessing instances#
Nearly every script requires referencing other objects in the world. Luduvo
provides two primary ways to locate instances: direct lookup using handles and hierarchical tree search via FindFirstChild.
Direct O(1) access with handles#
Locating an object by path, as in workspace.Buildings.RedTeam.Door, walks one
child array per path segment, and the path breaks as soon as someone reorganizes
the hierarchy.
Luduvo answers this with handles:
In the editor, select an instance and assign it a handle name, such as LobbyDoor or BossSpawn. During place startup, the engine registers these
named shortcuts into the global handles table. Any script then resolves the
name directly:
-- Resolved by name, no walking the hierarchylocal door = handles.LobbyDoordoor.Position = Vector3.new(0, 10, 0)Accessing handles.LobbyDoor resolves in constant time however deep the
instance sits, and the name keeps working when someone reorganizes the
hierarchy. Prefer handles when referencing statically placed level geometry
and critical scene objects.
Searching the hierarchy: FindFirstChild#
When working with dynamically spawned entities or unhandled objects, search
an instance’s direct children at runtime using FindFirstChild:
local door = self.Parent:FindFirstChild("Door")if door then door.Position = Vector3.new(0, 4, 0)else warn("No child named 'Door' found under", self.Parent.Name)endCalling FindFirstChild(name) searches the instance’s direct children only,
and returns nil when no child of that name exists. Children further down the
tree are not searched. Names are interned as 32-bit ids, so the search compares
integers rather than strings.
The search runs over every child in turn, so its cost grows with the number of
children. Whenever possible, favor direct O(1) lookups via handles, or cache
the resolved instance into a local variable rather than querying FindFirstChild every frame inside Update.
Managing the hierarchy: Parent#
Control an instance’s location in the place tree using the Parent property:
-- Attach this instance under a new containerself.Parent = handles.DebrisFolder-- Detach from the active hierarchyself.Parent = nilWhenever assigning instance.Parent = newParent, the engine does this:
The engine removes the instance from its old parent’s children and adds it to newParent as its last child. The world transform is not recomputed on the
spot: the engine recomputes it relative to the new parent after the physics
step of the same tick.
Setting Parent = nil unlinks the instance and its entire subtree from
the active world hierarchy. Both the Box3D physics solver and the rendering
pipeline immediately ignore detached entities. The detached instance stays
alive for as long as a Luau variable holds a reference to it.
Creating instances: Clone#
To duplicate existing entities or spawn prefabricated templates during
gameplay, use Clone:
-- Deep-copy self and its descendantslocal projectile = handles.BulletTemplate:Clone()-- Configure initial stateprojectile.Position = self.Position + Vector3.new(0, 2, 0)-- Insert into the active simulation worldprojectile.Parent = handles.WorldFolderWhat Clone does#
Calling Clone triggers a recursive deep-copy of the target instance and its entire
subtree:
The engine copies every node in the subtree along with its saved component
values: spatial transforms, colors, materials, collision filters, and custom
attributes. Every cloned node receives a brand-new, unique 64-bit entity ID. The
returned root arrives with Parent = nil: it is a floating subtree, part of no
world, until explicitly attached by assigning its Parent.
Cleaning up: Destroy#
Invoke Destroy to tear down an instance and all its descendants:
-- Destroy the instance and all its descendants (Server only)self:Destroy()Component cleanup and lifecycle#
Parent = nil detaches the subtree. Destroy executes engine teardown:
The engine recursively unlinks the instance from its parent, sets Parent = nil,
and permanently locks the Parent property so that scripts can never re-parent
or resurrect it. Unregistration then removes all rigid bodies, collision shapes,
and constraints from the Box3D physics world, releases vertex buffers, mesh
resources, and texture handles from the renderer, and terminates all running
script threads bound across the subtree.
Signal disconnection severs and frees all event listeners and callbacks
connected to any Signal across the instance, so no
closure keeps the subtree alive. Last, the handle itself is marked destroyed:
if Luau scripts index or mutate properties on the destroyed
instance later on, the engine raises the runtime error attempt to index a destroyed Instance.
Destroy is server-only. Calling Destroy in a client script (*.client.lua)
throws runtime error 'Destroy' can only be called from the server.
Connected clients observe entity deletions when the server replicates state
deltas across the network.