Log inGet started

Physics

Physics in Zero has two surfaces, reached in two different ways:

  • Physics — a Luau global you call from inside scripts and components: forces, velocity, raycasts, overlaps, sleep control, gravity, joints, wheels, collision groups. This is the code surface.
  • phys — a toolbox you drive from the agent tool surface (search_toolsuse_tool), not a Luau global. Its verbs compose an entity + model + body + collider in one call, so you can stand up physical objects without writing a script. phys is not available inside execute code — calling phys.spawnStatic(...) in a script fails with "attempt to index nil".

Physics simulates during play; in edit mode the world is posed, not running (the engine guide covers modes).

Making something physical (in code)

A body is static (immovable — floors, walls), dynamic (moved by forces and gravity), or kinematic (moved by your code, pushes dynamics). Build one from the component primitives: give an entity a Collider for its shape and a Physics component for its body.

local crate = entity.spawn("crate")
crate.position = { 0, 8, 0 }
crate.component.add("Collider", { shape = "box" })       -- shape sized by the entity's scale
crate.component.add("Physics", { kind = "dynamic" })     -- static | dynamic | kinematic

A Collider alone (no Physics) is a static collider fixed in world space — the right thing for ground and walls:

local ground = entity.spawn("ground")
ground.localScale = { 40, 1, 40 }
ground.component.add("Collider", { shape = "box" })

Collider also takes shape = "sphere" / "capsule" with radius / height. A dynamic body's mass comes from its collider volume by default; set Physics.mass to a value greater than zero to pin the exact total mass instead (a value of zero or below keeps the volume-derived mass).

Driving and querying (in code)

Physics.applyImpulse(id, { 0, 10, 0 })   -- one-shot push
Physics.setVelocity(id, 0, 5, 0)         -- direct velocity (numeric args), or setVelocity(id, {0,5,0})
Physics.setGravity({ 0, -9.81, 0 })      -- world gravity

local hit = Physics.raycast({ 0, 5, 0 }, { 0, -1, 0 }, 100)   -- origin, direction, maxDist
if hit then print(hit.entityId, hit.distance) end

local near = Physics.overlapSphere({ 0, 0, 0 }, 10)           -- entity ids whose colliders overlap

A body at rest sleeps to save simulation cost. applyImpulse, setVelocity, and setAngularVelocity wake it for you; call Physics.wakeUp(id) to wake one yourself, and Physics.isSleeping(id) to check.

Joints, wheels, groups

Joint motors, wheel colliders, and collision-group filtering live on Physics (Physics.setJointMotor, Physics.addWheelCollider, Physics.setCollisionGroups). The phys toolbox composes the common constraints (fixed, revolute, prismatic, spherical, spring, rope) from the tool surface.

Rope and chain behavior is a joint, not a script: kind = "rope" with maxDistance gives a hard maximum-distance limit solved inside the physics step — slack is free, taut resists extension, and an orbiting body on a taut rope keeps its speed. A constraint solved per-frame from Luau reads state one step late and writes one step later; for a steadily rotating configuration that lag feeds energy in every solve, so a hand-rolled rope pumps an orbiting body faster and faster. Reel a rope in or out by writing maxDistance on the live joint.

The full surface

lsp.methods("Physics") lists every Physics method with its signature; search_tools surfaces the phys toolbox verbs. The model to hold: phys composes from the tool surface, Physics controls from code, and simulation happens in play.

  • documentation
  • guide