Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

physics

Convenience wrapper around the `__physics` FFI namespace. Covers raycasting, force / impulse / torque application, velocity reads and writes, body-property mutators (mass, damping, body type, locks), collision groups, joints, constraints, colliders, wheel-collider helpers, and th…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

physics

Convenience wrapper around the __physics FFI namespace. Covers raycasting, force / impulse / torque application, velocity reads and writes, body-property mutators (mass, damping, body type, locks), collision groups, joints, constraints, colliders, wheel-collider helpers, and the observation reads that report what the solver holds for a body and why it is not moving it, in one consistent surface. Exposed as the Physics global via --!global Physics.

Exports

Queries:

  • Physics.raycast(origin, direction, maxDistance?, exclude?) -> table?
  • Physics.raycastAll(origin, direction, maxDistance?, maxHits?) -> table
  • Physics.overlapSphere(center, radius) -> table
  • Physics.sphereCast(origin, radius, direction, maxDistance?, exclude?) -> table?
  • Physics.capsuleCast(origin, radius, halfHeight, direction, maxDistance?, exclude?) -> table?
  • Physics.boxCast(origin, halfExtents, direction, maxDistance?, exclude?) -> table?
  • Physics.getGravity() -> vec3 / Physics.setGravity(g)
  • Physics.getVelocity(entityId?) -> vec3? / Physics.getAngularVelocity(entityId?) -> vec3?

Forces / impulses (each accepts either (vec3) for the script-context entity or (entityId, vec3); a vec3 may be an {x, y, z} table or three loose numbers):

  • Physics.applyForce / Physics.applyForceAtPoint / Physics.applyTorque — act for exactly one physics step; call every frame for continuous thrust
  • Physics.applyImpulse — one-shot velocity change
  • Physics.setVelocity / Physics.addVelocity / Physics.setAngularVelocity

Body properties:

  • Physics.setGravityScale, Physics.setMass, Physics.setLinearDamping, Physics.setAngularDamping, Physics.setCcdEnabled
  • Physics.setBodyType(entityId, "dynamic" | "kinematic" | "static")
  • Physics.setRotationLocks(entityId, x, y, z) / Physics.setTranslationLocks(entityId, x, y, z)

Collision groups & filtering:

  • Physics.setCollisionGroups(entityId, membership, filter)
  • Physics.ignoreCollision(entityIdA, entityIdB, ignore?)

Joints / constraints / colliders:

  • Physics.addJoint(a, b, opts?) / Physics.removeJoint(id) / Physics.setJointMotor(id, vel, maxForce)
  • Physics.addConstraint(id, opts?) / Physics.removeConstraint(id, index?)
  • Physics.addCollider(id, config) / Physics.removeCollider(id)
  • Physics.addWheelCollider(id, config?) / Physics.removeWheelCollider(id) / Physics.getWheelState(id)

Observation (read off the solver, not off the Physics component):

  • Physics.observe(entityId?, opts?) -> PhysicsObservation? — one read of the world, or of one body; bodies is an array whose entries each name their own entity
  • Physics.worldState() -> PhysicsWorldState — bodies by type, awake / asleep, colliders, joints, contact pairs and points, gravity, timestep, and the last step's cost
  • Physics.bodyState(entityId) -> PhysicsBodyState? — the mass, inertia, gravity scale, damping, locks, CCD, collision groups, sleep timers, velocities, queued force and torque, colliders, contacts, joints and transform constraints the solver holds
  • Physics.whyStill(entityId) -> (string?, string?) — the one reason the solver is not advancing a body, and the detail behind it
  • Physics.stillnessReasons() -> {string} — every reason whyStill can answer with
  • Physics.contacts(entityId) -> {PhysicsContact} / Physics.touching(a, b) -> (boolean, number, {PhysicsContactPoint})
  • Physics.stepCost() -> PhysicsStepCost? — the last step's per-stage cost

High-level helpers:

  • Physics.raycastBetween(fromId, toId, maxDistance?) -> table?
  • Physics.hasLineOfSight(fromId, toId) -> boolean

Usage

-- `Physics` is auto-injected by the prelude — no require in user code.
local hit = Physics.raycast({x=0,y=2,z=0}, {x=0,y=-1,z=0})
Physics.applyImpulse(entityId, {x=0, y=5, z=0})
Physics.addJoint(a, b, { kind = "fixed" })

Notes

  • Most apply/set helpers are polymorphic on the first argument: they accept either an explicit entityId (with the value as a second arg) or a value directly (using the script-context entity).
  • addJoint accepts both vec3-style anchor tables (localAnchor = {x,y,z}) and pre-split scalar keys (localAnchorX/Y/Z); both forms forward as split keys to the Joint component.
  • setCollisionGroups adds the CollisionGroup component if missing.
  • getWheelState returns nil when the entity has no WheelCollider component.
  • The sweeps (sphereCast, capsuleCast, boxCast) report distance as how far the shape's centre travels before its surface meets the geometry, and normal as the outward normal of the surface it met — the same normal a ray hit carries.
  • Every hit table carries startedInside, and it says which surface normal describes. false — the query travelled to the collider and crossed its surface, so distance is how far it went, point is where it met the surface, and normal is that surface's outward normal. true — the query's own start already lay inside that collider, so distance is 0, point is the start itself, and normal is the collider's outward surface normal at the surface point nearest the start: the shortest way out of it. normal is a unit vector either way, and every collider shape answers an inside start the same way, so a slope read like math.acos(hit.normal.y) is meaningful without knowing which shape was hit.
  • raycastBetween returns nil if the two entities are coincident (< 0.001 units apart).
  • hasLineOfSight returns true even when nothing is hit — line-of-sight only fails if a third entity sits between the pair.
  • The observation reads answer in edit mode as well as play mode. bodyState reports exists = false with stillness = "noBody" for an entity that carries no rigid body, and nil only when nothing in the scene answers to that id.
  • observe(nil, { bodies = false }) builds the world accounting alone; { contactPoints = false } keeps each contact pair's normal, depth, impulse and point count while leaving out the individual points.
  • stepCost figures cover the one step that ran, so they are already per-step costs; it is nil on a frame where the pipeline did not step.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

global Physics global physics

CollisionGroupJointConstraintWheelCollider

raycast(origin: vec3, direction: vec3, maxDistance: number?, exclude: (string | {string}) →

Cast a ray and return the first hit. Answers from COLLIDERS ALONE: a mesh that renders but carries no collider is not in the physics world, so a ray fired through it reports the same `nil` a ray through open air does. `renderer.raycast` answers the same ray against the geometry the renderer DRAWS, which is what reads the surface of a terrain, a procedurally generated mesh, or any plain `Model`. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way. A `nil` says the ray met no COLLIDER, which `Physics.colliderCount()` separates from a world that holds none for it to meet.

argtypedescription
originvec3Ray origin in world space.
directionvec3Ray direction (does not need to be unit-length; the engine normalises).
maxDistancenumber?Maximum distance along the ray (defaults to 1000).
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local hit = Physics.raycast({x=0,y=2,z=0}, {x=0,y=-1,z=0})
local hit = Physics.raycast(origin, dir, 50, { selfId, carriedId })
if Physics.colliderCount() == 0 then hit = renderer.raycast(eye, down, 200) end

colliderCount( ) →

How many colliders the physics world holds. Zero means no ray, cast or overlap fired into this world can hit anything, so it is what separates a query that MISSED from a query fired into a world that holds nothing to hit. Read off the collider set itself, so it costs the same whatever the world holds.

examples

if Physics.colliderCount() == 0 then print("nothing here is solid") end

raycastAll(origin: vec3, direction: vec3, maxDistance: number?, maxHits: number?, exclude: (string | {string}) →

Cast a ray and return every hit up to `maxHits`. Answers from COLLIDERS ALONE, so a rendered mesh with no collider is absent from the result; `renderer.raycastAll` answers the same ray against the geometry the renderer draws. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way.

argtypedescription
originvec3Ray origin in world space.
directionvec3Ray direction.
maxDistancenumber?Optional distance limit along the ray.
maxHitsnumber?Optional cap on the number of hits returned.
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local hits = Physics.raycastAll(origin, dir, 50, 4)

raycastScreen(sx: number, sy: number, maxDistance: number?, exclude: (string | {string}) →

Cast a ray from a screen pixel into the scene and return the first hit. Unprojects the pixel with `screenToRay`, then casts with `raycast`. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way.

argtypedescription
sxnumberScreen X in viewport-local pixels (the space of `input.mouse_position` and `screenToRay`).
synumberScreen Y in viewport-local pixels.
maxDistancenumber?Maximum distance along the ray (defaults to 1000, matching `raycast`).
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local m = input.mouse_position; local hit = Physics.raycastScreen(m[1], m[2])

overlapSphere(center: vec3, radius: number) →

Find every entity id whose colliders overlap a sphere.

argtypedescription
centervec3Sphere center in world space.
radiusnumberSphere radius.

examples

local ids = Physics.overlapSphere({x=0,y=0,z=0}, 5)

sphereCast(origin: vec3, radius: number, direction: vec3, maxDistance: number?, exclude: (string | {string}) →

Cast a sphere along a direction and return the first hit. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way.

argtypedescription
originvec3Sphere center at the start of the cast.
radiusnumberSphere radius.
directionvec3Cast direction.
maxDistancenumber?Optional distance limit.
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local hit = Physics.sphereCast(o, 0.5, dir, 10)
local hit = Physics.sphereCast(o, 0.5, dir, 10, selfId)

capsuleCast(origin: vec3, radius: number, halfHeight: number, direction: vec3, maxDistance: number?, exclude: (string | {string}) →

Cast an upright capsule along a direction and return the first hit. This is the sweep that answers whether a body of that shape fits through a passage: a capsule of radius `r` reports a hit on anything that leaves it less than `2 * r` of clearance. stands `halfHeight + radius` tall in each direction. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way.

argtypedescription
originvec3Capsule centre at the start of the cast.
radiusnumberCapsule radius.
halfHeightnumberDistance from the centre to either cap centre. The capsule
directionvec3Cast direction.
maxDistancenumber?Optional distance limit.
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local hit = Physics.capsuleCast(o, 0.3, 0.6, dir, 0.5)
local hit = Physics.capsuleCast(o, 0.3, 0.6, dir, 0.5, selfId)

boxCast(origin: vec3, halfExtents: vec3, direction: vec3, maxDistance: number?, exclude: (string | {string}) →

Cast a box along a direction and return the first hit. Applied while sweeping rather than to the answer, so a cast that starts inside an excluded collider reports what is behind it. `startedInside` is `false` for a surface the query crossed on its way there, and `true` when the query's own start already lay inside that collider — where `distance` is 0, `point` is the start itself, and `normal` is the shortest way out of the collider. `normal` is a unit vector either way.

argtypedescription
originvec3Box center at the start of the cast.
halfExtentsvec3Half the size of the box on each axis.
directionvec3Cast direction.
maxDistancenumber?Optional distance limit.
exclude(string | {string}Optional entity id, or array of entity ids, to exclude from hits.

examples

local hit = Physics.boxCast(o, {x=0.5,y=0.5,z=0.5}, dir, 10)
local hit = Physics.boxCast(o, {x=0.5,y=0.5,z=0.5}, dir, 10, { selfId, carriedId })

colliderShapes(entityId: string | entityRef) →

Read an entity's resolved physics collider shape(s) as the physics engine sees them, including auto-sized colliders. `shapeType` is one of `box`, `sphere`, `capsule`, `convex`, `mesh`, `heightfield`, `compound`, `other` — the shape the simulation is running, so a mesh collider reads `mesh`. `params` carries half-extents for a box, radius for a sphere, radius and half-height for a capsule, and the collider's bounding half-extents for the shapes that have no parametric description. A convex collider reports its outline in `linePoints` instead.

argtypedescription
entityIdstring | entityRefTarget entity id.

examples

local shapes = Physics.colliderShapes(id)

colliderManifest( ) →

List every physics collider in the world with what it is and what it takes part in — no geometry, so it is the cheap read to make before deciding what to do with each one. `role` is one of `static`, `dynamic`, `kinematic`, `sensor`. A sensor is a collider the simulation holds as one, reported ahead of the body type behind it, and a collider with no rigid body is static. `exact` says whether `colliderGeometry` would return this collider's true surface or its bounding box. Every collider of one entity shares its `entity`, so this is what to key per-object decisions on. The order is stable across calls over an unchanged world, which is what makes `colliderGeometry`'s positional colours usable.

examples

for _, c in ipairs(Physics.colliderManifest()) do print(c.entity, c.role) end

colliderGeometry(options: table?) →

Read the physics world as drawable triangles: every collider triangulated in world space into one indexed mesh, in GPU buffers ready to draw. Box, sphere, capsule, cylinder, cone, convex, triangle-mesh and heightfield colliders return their real surface, and a compound returns its children folded together; a shape with no triangulation returns its bounding box and reports `exact = false`. `options.colors` is POSITIONAL over `colliderManifest()` — entry `i` colours collider `i` — so you can colour by role, shape, entity or anything else you read there. A position you leave out takes `options.defaultColor`. The returned buffers are yours: destroy them when you replace them. entry of `colliders` is `{ entity, colliderName?, shapeType, role, exact, firstIndex, indexCount }`.

argtypedescription
optionstable?`{ tessellation = "low"|"medium"|"high", colors = { {r,g,b,a}, ... }, defaultColor = {r,g,b,a} }`.

examples

local geo = Physics.colliderGeometry({ tessellation = "high" })

getGravity( ) →

Read the current world gravity vector.

examples

local g = Physics.getGravity()

setGravity(gravity: vec3) → void

Replace the world gravity vector.

argtypedescription
gravityvec3New gravity vector in m/s².

examples

Physics.setGravity({x=0, y=-9.81, z=0})

getVelocity(entityId: (string | entityRef) →

Read the linear velocity of an entity's rigid body.

argtypedescription
entityId(string | entityRefTarget entity id or proxy; resolves from script context when omitted.

examples

local v = Physics.getVelocity(id)

getAngularVelocity(entityId: (string | entityRef) →

Read the angular velocity of an entity's rigid body.

argtypedescription
entityId(string | entityRefTarget entity id or proxy; resolves from script context when omitted.

examples

local w = Physics.getAngularVelocity(id)

isSleeping(entityId: (string | entityRef) →

Whether an entity's rigid body is currently asleep (at rest and not simulating). A body sleeps once it stops moving, to save simulation cost.

argtypedescription
entityId(string | entityRefTarget entity id or proxy; resolves from script context when omitted.

examples

if Physics.isSleeping(id) then Physics.wakeUp(id) end

wakeUp(entityId: (string | entityRef) → void

Wake an entity's sleeping rigid body so it resumes simulating. The motion setters (`applyImpulse`, `setVelocity`, `setAngularVelocity`) wake the body for you; call this to wake one explicitly.

argtypedescription
entityId(string | entityRefTarget entity id or proxy; resolves from script context when omitted.

examples

Physics.wakeUp(id)

idOf(ref: (string | entityRef) → void

Internal: the stable id of an entity id or proxy.

argtypedescription
ref(string | entityRef

observe(entityId: (string | entityRef, opts: ?) →

Read the solver's own state — the world's accounting, and what it holds for each body plus why it is not moving one. Every value comes off the simulation rather than the `Physics` component, so a write the solver refused or clamped reads back as what it kept. Answers in edit mode as well as play mode. builds the world accounting alone, and `contactPoints = false` keeps each contact pair's normal, depth, impulse and point count while leaving out the individual points. Both default to true. scene. `bodies` is an array, not a table keyed by entity id — each entry names its own entity in `entity`.

argtypedescription
entityId(string | entityRefReport on this one entity. Omit for every body in the world.
opts?`{ bodies: boolean?, contactPoints: boolean? }` — `bodies = false`

examples

local o = Physics.observe(); for _, b in o.bodies do print(b.entity, b.stillness) end
local o = Physics.observe(id); print(o.bodies[1].stillness, o.bodies[1].stillnessDetail)

worldState( ) →

How many bodies, colliders, joints and contacts the simulation holds right now, with world gravity, the timestep, whether the pipeline is stepping at all, and what the last step cost. Counted off the solver, so a body that failed to build is absent here while its `Physics` component still exists.

examples

local w = Physics.worldState(); print(w.bodies.awake .. "/" .. w.bodies.total .. " awake")
print(Physics.worldState().contacts.touchingPairs .. " pairs touching")

bodyState(entityId: string | entityRef) →

Everything the solver holds for one body — its type, mass, centre of mass, inertia, gravity scale, damping, lock flags, CCD, collision groups, sleep state, velocities, the force and torque queued for the next step, its colliders, contacts, joints and transform constraints, and why it is not moving. carries no rigid body — or `nil` when nothing in the scene answers to that id.

argtypedescription
entityIdstring | entityRefEntity id or proxy.

examples

local b = Physics.bodyState(id); print(b.bodyType, b.mass, b.stillness)
if not Physics.bodyState(id).exists then print("no body was built") end

whyStill(entityId: string | entityRef) →

Why the solver is not moving a body. Returns `nil` when it IS moving it, and otherwise one of `noBody`, `simulationNotStepping`, `disabled`, `static`, `kinematic`, `infiniteMass`, `translationLocked`, `gravityDisabled`, `asleep`, `outsideIsland`, `resting`, `aboutToMove` — the nearest cause, so the answer names the thing to change. A second return carries the detail: which collider it rests on and how deeply, what its effective gravity works out to, and so on.

argtypedescription
entityIdstring | entityRefEntity id or proxy.

examples

local why, detail = Physics.whyStill(id); if why then print(why, detail) end

stillnessReasons( ) →

Every reason `whyStill` can answer with, in the order the engine considers them. Read from the engine, so the list is the one the answers come from.

examples

for _, reason in Physics.stillnessReasons() do print(reason) end

contacts(entityId: string | entityRef) →

Every contact one body's colliders are in right now, with the other entity, the normal, how deeply the two interpenetrate, the impulse the last step applied, and each contact point. or when the entity carries no rigid body.

argtypedescription
entityIdstring | entityRefEntity id or proxy.

examples

for _, c in Physics.contacts(id) do print(c.other, c.deepestPenetration) end

touching(entityId: string | entityRef, otherId: string | entityRef) →

Whether two entities are touching, and how deeply. in metres and `0` for surfaces that meet without overlapping.

argtypedescription
entityIdstring | entityRefEntity id or proxy.
otherIdstring | entityRefThe other entity id or proxy.

examples

local hit, depth = Physics.touching(a, b); print(hit, depth)

stepCost( ) →

What the last physics step cost, stage by stage — the same figures `worldState().step` carries, for a caller that wants only these. Each covers that one step rather than a window of them, and consecutive steps over the same resting scene vary by tens of percent, so several samples averaged is the honest read of what a step costs. step — a paused simulation, or a world still bootstrapping.

examples

local c = Physics.stepCost(); if c then print(c.stepMs, c.narrowPhaseMs) end

applyForce(entityIdOrForce: string | entityRef | vec3, force: vec3?) → void

Apply a force to an entity's rigid body for the next physics step — call every frame for continuous thrust. With one argument the script-context entity is targeted; with two args the explicit entity id wins.

argtypedescription
entityIdOrForcestring | entityRef | vec3Entity id (when paired with `force`) OR a force vector for the script-context entity.
forcevec3?Optional force vector when targeting an explicit entity.

examples

Physics.applyForce({x=0, y=10, z=0})
Physics.applyForce(entityId, {x=0, y=10, z=0})

applyForceAtPoint(entityId: string | entityRef, force: vec3, point: vec3) → void

Apply a force at a specific world-space point — generates the matching torque from the lever arm.

argtypedescription
entityIdstring | entityRefTarget entity id.
forcevec3Force vector.
pointvec3World-space application point.

examples

Physics.applyForceAtPoint(id, {x=0,y=10,z=0}, {x=1,y=0,z=0})

applyImpulse(entityIdOrImpulse: string | entityRef | vec3, impulse: vec3?) → void

Apply an instantaneous impulse (one-shot velocity change). With one argument the script-context entity is targeted; with two args the explicit entity id wins.

argtypedescription
entityIdOrImpulsestring | entityRef | vec3Entity id (with `impulse`) OR an impulse vector for the script-context entity.
impulsevec3?Optional impulse vector when targeting an explicit entity.

examples

Physics.applyImpulse({x=0, y=5, z=0})
Physics.applyImpulse(entityId, {x=0, y=5, z=0})

applyTorque(entityIdOrTorque: string | entityRef | vec3, torque: vec3?) → void

Apply a torque to an entity's rigid body for the next physics step — call every frame for continuous spin-up. With one argument the script-context entity is targeted; with two args the explicit entity id wins.

argtypedescription
entityIdOrTorquestring | entityRef | vec3Entity id (with `torque`) OR a torque vector for the script-context entity.
torquevec3?Optional torque vector when targeting an explicit entity.

examples

Physics.applyTorque({x=0, y=1, z=0})
Physics.applyTorque(entityId, {x=0, y=1, z=0})

setVelocity(a: string | entityRef | number | vec3, b: (number | vec3, c: ?, d: ?) → void

Set the linear velocity of an entity. Accepts `(x, y, z)` or a `{x, y, z}` vector for the script-context entity, or the same prefixed with an explicit `entityId`.

argtypedescription
astring | entityRef | number | vec3x-component, a `{x, y, z}` vector, or an entity id (explicit target).
b(number | vec3y-component, x-component, or the vector depending on call form.
c?z-component or y-component depending on call form.
d?Optional z-component when targeting an explicit entity.

examples

Physics.setVelocity(0, 10, 0)
Physics.setVelocity(entityId, 0, 10, 0)
Physics.setVelocity(entityId, {x=0, y=10, z=0})

addVelocity(a: string | entityRef | number | vec3, b: (number | vec3, c: ?, d: ?) → void

Add to the linear velocity of an entity. Same call shapes as `setVelocity`.

argtypedescription
astring | entityRef | number | vec3dx, a `{x, y, z}` delta vector, or an entity id (explicit target).
b(number | vec3dy, dx, or the delta vector depending on call form.
c?dz or dy depending on call form.
d?Optional dz when targeting an explicit entity.

examples

Physics.addVelocity(0, 5, 0)
Physics.addVelocity(entityId, 0, 5, 0)
Physics.addVelocity(entityId, {x=0, y=5, z=0})

setAngularVelocity(a: string | entityRef | number | vec3, b: (number | vec3, c: ?, d: ?) → void

Set the angular velocity of an entity (radians/sec). Same call shapes as `setVelocity`.

argtypedescription
astring | entityRef | number | vec3x-component, a `{x, y, z}` vector, or an entity id (explicit target).
b(number | vec3y-component, x-component, or the vector depending on call form.
c?z-component or y-component depending on call form.
d?Optional z-component when targeting an explicit entity.

examples

Physics.setAngularVelocity(0, 0, 1)
Physics.setAngularVelocity(entityId, 0, 0, 1)
Physics.setAngularVelocity(entityId, {x=0, y=0, z=1})

setGravityScale(entityIdOrScale: string | entityRef | number, scale: number?) → void

Set the per-entity gravity scale (1.0 = normal, 0.0 = no gravity). One-arg form targets the script-context entity.

argtypedescription
entityIdOrScalestring | entityRef | numberEntity id (with `scale`) OR scale value (script-context entity).
scalenumber?Optional explicit scale when targeting another entity.

examples

Physics.setGravityScale(0.5)
Physics.setGravityScale(entityId, 0.5)

setMass(entityIdOrMass: string | entityRef | number, mass: number?) → void

Set the mass of an entity's rigid body (kg). One-arg form targets the script-context entity.

argtypedescription
entityIdOrMassstring | entityRef | numberEntity id (with `mass`) OR mass value (script-context entity).
massnumber?Optional explicit mass when targeting another entity.

examples

Physics.setMass(10)
Physics.setMass(entityId, 10)

setLinearDamping(entityIdOrDamping: string | entityRef | number, damping: number?) → void

Set linear damping on an entity's rigid body (0 = no damping). One-arg form targets the script-context entity.

argtypedescription
entityIdOrDampingstring | entityRef | numberEntity id (with `damping`) OR damping value (script-context entity).
dampingnumber?Optional explicit damping when targeting another entity.

examples

Physics.setLinearDamping(0.05)
Physics.setLinearDamping(entityId, 0.05)

setAngularDamping(entityIdOrDamping: string | entityRef | number, damping: number?) → void

Set angular damping on an entity's rigid body. One-arg form targets the script-context entity.

argtypedescription
entityIdOrDampingstring | entityRef | numberEntity id (with `damping`) OR damping value (script-context entity).
dampingnumber?Optional explicit damping when targeting another entity.

examples

Physics.setAngularDamping(0.1)
Physics.setAngularDamping(entityId, 0.1)

setCcdEnabled(entityIdOrEnabled: string | entityRef | boolean, enabled: boolean?) → void

Enable or disable continuous collision detection on an entity's rigid body. One-arg form targets the script-context entity.

argtypedescription
entityIdOrEnabledstring | entityRef | booleanEntity id (with `enabled`) OR boolean (script-context entity).
enabledboolean?Optional explicit boolean when targeting another entity.

examples

Physics.setCcdEnabled(true)
Physics.setCcdEnabled(entityId, true)

setBodyType(entityId: string | entityRef, bodyType: string) → void

Change a rigid body's type at runtime. Mass, colliders, and joints are preserved — only the body's response to forces and position writes changes.

argtypedescription
entityIdstring | entityRefTarget entity id.
bodyTypestringOne of `"dynamic"`, `"kinematic"`, `"static"`.

examples

Physics.setBodyType(entityId, "kinematic")

setRotationLocks(entityId: string | entityRef, x: boolean, y: boolean, z: boolean) → void

Lock or unlock rotation on specific axes.

argtypedescription
entityIdstring | entityRefTarget entity id.
xbooleanLock rotation about the world X axis.
ybooleanLock rotation about the world Y axis.
zbooleanLock rotation about the world Z axis.

examples

Physics.setRotationLocks(id, false, true, false)

setTranslationLocks(entityId: string | entityRef, x: boolean, y: boolean, z: boolean) → void

Lock or unlock translation on specific axes.

argtypedescription
entityIdstring | entityRefTarget entity id.
xbooleanLock translation along the world X axis.
ybooleanLock translation along the world Y axis.
zbooleanLock translation along the world Z axis.

examples

Physics.setTranslationLocks(id, false, false, true)

setCollisionGroups(entityId: string | entityRef, membership: number, filter: number) → void

Set the collision-group membership and filter bitmasks on an entity's colliders. Adds a `CollisionGroup` component if missing.

argtypedescription
entityIdstring | entityRefTarget entity id.
membershipnumberBitmask: which groups this collider belongs to.
filternumberBitmask: which groups this collider can collide with.

examples

Physics.setCollisionGroups(id, 0x0001, 0xFFFF)

ignoreCollision(entityIdA: string | entityRef, entityIdB: string | entityRef, ignore: boolean?) → void

Toggle ignored-collision state between two specific entities.

argtypedescription
entityIdAstring | entityRefFirst entity id.
entityIdBstring | entityRefSecond entity id.
ignoreboolean?When `true` (default) collisions between the pair are skipped.

examples

Physics.ignoreCollision(a, b, true)

addJoint(entityIdA: string | entityRef, entityIdB: string | entityRef, opts: table?) → void

Add a Joint component connecting two entities. Accepts either vec3-style anchor inputs (`localAnchor = {x,y,z}`) or pre-split scalar keys (`localAnchorX/Y/Z`).

argtypedescription
entityIdAstring | entityRefEntity that hosts the Joint component.
entityIdBstring | entityRefConnected entity.
optstable?Optional joint description (kind, anchors, axis, stiffness, damping, restLength, maxDistance, breakForce, breakTorque).

examples

Physics.addJoint(a, b, { kind = "fixed" })
Physics.addJoint(a, b, { kind = "hinge", axis = {x=0,y=1,z=0} })
Physics.addJoint(a, b, { kind = "rope", maxDistance = 8 })
Physics.addJoint(a, b, { kind = "fixed", breakForce = 1200, breakTorque = 800 })

comp(prefix: ?, vec: ?) → void

Joint's schema is split-form (localAnchorX/Y/Z, etc.). Accept either the vec3 table form callers naturally write OR the pre-split keys that phys.addJoint emits, and always forward as split keys so component.add doesn't reject anything.

argtypedescription
prefix?
vec?

removeJoint(entityId: string | entityRef) → void

Remove the Joint component from an entity (if present).

argtypedescription
entityIdstring | entityRefTarget entity id.

examples

Physics.removeJoint(id)

setJointMotor(entityId: string | entityRef, targetVelocity: number, maxForce: number) → void

Set a motor on an entity's joint.

argtypedescription
entityIdstring | entityRefTarget entity id (must carry a Joint component).
targetVelocitynumberDesired joint velocity.
maxForcenumberMaximum force the motor can apply.

examples

Physics.setJointMotor(id, 5.0, 1000)

pumpJointBreaks( ) → void

The engine hands each break over exactly once, so every record this reads is the only copy there will ever be: buffer the whole batch before any listener runs, and run each listener under pcall, so one handler that raises costs its own delivery and nothing else's.

pumpJointBreaks( ) → void

Deliver every joint break the simulation has recorded to the registered listeners. An enabled `Joint` component calls this each tick, so listeners fire on their own wherever joints come from that component. A joint made by writing `ecs.PhysicsJoint` directly has no such tick behind it — call this each frame, or poll `jointBreaks`, to deliver its breaks.

examples

Physics.pumpJointBreaks()

jointReaction(entityId: string | entityRef) →

The load an entity's joint is carrying right now, as the constraint solver resolved it on the last physics step. This is the same quantity a break threshold is measured against, so it is what to size `breakForce` and `breakTorque` from. the entity owns no joint.

argtypedescription
entityIdstring | entityRefEntity carrying the Joint component.

examples

local r = Physics.jointReaction(id); print(r and r.force)

jointBreaks( ) →

Every joint that has broken since the last call to this function. A joint breaks when the reaction it carries exceeds the `breakForce` (newtons of linear reaction) or `breakTorque` (the angular row of the same reaction) its joint was given; each joint reports once and its constraint is already released when the record arrives. The 256 most recent are kept: a structure that comes apart while nothing reads them drops the oldest beyond that, as the engine's own queue does beyond 1024.

examples

for _, e in ipairs(Physics.jointBreaks()) do print(e.entityId, e.force) end

onJointBreak(fn: (table) →

Call `fn` for every joint that breaks from now on, with the same record `jointBreaks` returns.

argtypedescription
fn(tableReceives one break record per broken joint.

examples

local off = Physics.onJointBreak(function(e) print(e.kind, e.force, e.position) end)

addConstraint(entityId: string | entityRef, opts: table?) → void

Add a transform constraint to an entity.

argtypedescription
entityIdstring | entityRefTarget entity id.
optstable?Optional constraint description (targetEntityId, position, rotation, scale, lookAt, targetPosition, axes, weight).

examples

Physics.addConstraint(id, { targetEntityId = parent, position = true })

removeConstraint(entityId: string | entityRef, index: number?) → void

Remove transform constraints from an entity (if any are present).

argtypedescription
entityIdstring | entityRefTarget entity id.
indexnumber?Optional constraint index (currently ignored — the whole component is removed).

examples

Physics.removeConstraint(id)

colliderOn(entityId: string | entityRef) → string

Which collider component an entity carries, or nil when it carries none.

argtypedescription
entityIdstring | entityRefTarget entity id.

examples

local which = Physics.colliderOn(id)

addCollider(entityId: string | entityRef, component: string, config: table?) → void

Add a collider component to an entity, naming the shape you want.

argtypedescription
entityIdstring | entityRefTarget entity id.
componentstringOne of `Physics.COLLIDER_COMPONENTS`.
configtable?The component's own fields, e.g. `{ radius = 0.5 }` for a sphere.

examples

Physics.addCollider(id, "SphereCollider", { radius = 0.5 })

removeCollider(entityId: string | entityRef) → string

Remove whichever collider component an entity carries.

argtypedescription
entityIdstring | entityRefTarget entity id.

examples

Physics.removeCollider(id)

addWheelCollider(entityId: string | entityRef, config: table?) → void

Add a WheelCollider to an entity. The entity must be a child (or descendant) of a rigid body — the system walks up the hierarchy to find the Physics component.

argtypedescription
entityIdstring | entityRefTarget entity id.
configtable?Optional wheel configuration (`radius?`, `suspensionDistance?`, `springRate?`, `damperRate?`, `motorTorque?`, `brakeTorque?`, `steerAngle?`, `forwardFriction?`, `sidewaysFriction?`, `is2D?`).

examples

Physics.addWheelCollider(id, { radius = 0.35, motorTorque = 500 })

removeWheelCollider(entityId: string | entityRef) → void

Remove the WheelCollider component from an entity (if present).

argtypedescription
entityIdstring | entityRefTarget entity id.

examples

Physics.removeWheelCollider(id)

getWheelState(entityId: string | entityRef) →

Read a wheel collider's runtime state. Reads the native component the wheel system writes after each physics step.

argtypedescription
entityIdstring | entityRefTarget entity id (must carry a WheelCollider component).

examples

local state = Physics.getWheelState(id)

raycastBetween(fromId: string, toId: string, maxDistance: number?) →

Cast a ray from one entity toward another and return the first hit.

argtypedescription
fromIdstringOrigin entity id.
toIdstringTarget entity id.
maxDistancenumber?Optional distance cap (default 1000).

examples

local hit = Physics.raycastBetween(a, b)

hasLineOfSight(fromId: string, toId: string) →

Check whether two entities have line-of-sight between their origins.

argtypedescription
fromIdstringViewer entity id.
toIdstringTarget entity id.

examples

if Physics.hasLineOfSight(a, b) then ... end
⌬ Types
PhysicsLocks = {PhysicsSleepState = {PhysicsColliderState = {PhysicsContactPoint = {PhysicsContact = {PhysicsJointMotor = {PhysicsJointState = {PhysicsTransformConstraintState = {PhysicsBodyState = {PhysicsMovingThreshold = {PhysicsStepCost = {PhysicsWorldState = {PhysicsObservation = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

15items
·
other · born here
▤file
▲ 0↑ born
◇
component · born here
❒asset
# WheelCollider Raycast-based wheel collider (Unity-style). Add to a child entity of a rigid body; the system finds the nearest ancestor with a `Physics` component and applies suspension, drive, and friction forces to it. Drive conventions: positive `motorTorque` drives toward the parent body's local **-Z** (the engine's forward); positive `steerAngle` steers right. Suspension `springRate`/`damperRate` are clamped per step to what the chassis mass can integrate stably, so an over-stiff spring on a light body settles instead of oscillating — tune rates to the vehicle's mass for the intended feel. Public fields: `suspensionDistance`, `springRate`, `damperRate`, `targetPosition`, `radius`, `width`, `motorTorque`, `brakeTorque`, `steerAngle`, `forwardFriction`, `sidewaysFriction`, `rollingResistance`, `is2D`. Runtime-read-only (updated every physics tick): `isGrounded`, `compression`, `angularVelocity` — also readable via `Physics.getWheelState(wheelId)`. Methods: `setMotor(torque)`, `setBrake(torque)`, `setSteer(angle)`. ```luau entity(wheelId).component.add("WheelCollider", { radius = 0.3, suspensionDistance = 0.3, springRate = 35000, damperRate = 4500, }) ```
▲ 0↑ born
◇
component · born here
❒asset
# Joint Connects two physics bodies with a joint constraint. Both entities must have `Physics` components. Joint kinds: `"fixed"`, `"hinge"`, `"ball"`, `"prismatic"`, `"spring"`, `"rope"`. Public fields: `kind`, `connected`, `localAnchorX/Y/Z`, `remoteAnchorX/Y/Z`, `axisX/Y/Z`, `stiffness`, `damping`, `restLength`, `maxDistance`. Methods: `:setMotor(targetVelocity, maxForce)`. ```luau entity(id).component.add("Joint", { connected = frameEntityId, kind = "hinge", axis = {0, 1, 0}, }) ``` A `"rope"` joint is a hard maximum-distance limit solved inside the physics step: the bodies move freely while the anchors are closer than `maxDistance` (slack rope) and are stopped from separating beyond it (taut rope). It requires `maxDistance > 0`, and `maxDistance` is reactive — writing it on a live joint retunes the limit in place, so a winch can reel a rope in or out without recreating the joint. ```luau entity(ballId).component.add("Joint", { connected = hookEntityId, kind = "rope", maxDistance = 8, }) ```
▲ 0↑ born
◇
component · born here
❒asset
# CollisionGroup Sets collision group membership and filter masks on an entity's physics colliders using a bitmask. Two colliders collide only when `(a.membership & b.filter) ~= 0` AND `(b.membership & a.filter) ~= 0`. Public fields: `membership` (default `0xFFFFFFFF`), `filter` (default `0xFFFFFFFF`). Methods: `setGroups(membership, filter)`, `ignoreEntity(entityId, ignore?)`. ```luau entity(id).component.add("CollisionGroup", { membership = 1, filter = 3 }) ```
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.