Builtin Library
README.md
GravityField
Gravity fields: planet gravity, a pull toward a point, an axis or a surface, or a zone with its own direction. Every dynamic body a field reaches (rigid bodies, ragdoll bones, a vehicle's chassis, soft body particles) takes it as its gravity inside every physics substep, from where the body stands then, so a moon keeps its orbit, a slingshot past a planet bends the way it should, and a crate dropped anywhere around a small planet falls onto its surface. Where no field reaches, a body takes the world's gravity vector (Physics.setGravity). Fields are scene data, synced like any component, so every peer pulls the same way.
Physics.gravityAt(point) reads the gravity at any point (exactly what the step applies there), Physics.upAt(point) the unit up against it (the way a character stands on a planet and a camera keeps level), and Physics.gravityFieldsAt(point) which fields act there and what each gives. Physics.bodyState(id).effectiveGravity is the gravity a body takes, after its gravityScale. The built-in CharacterController stands and walks against Physics.upAt, so a player walks all the way around a small planet.
Public fields:
shape(default"point"): what the field pulls toward, from where the entity stands and turned with it."point": the entity's position (a planet, a star, a black hole)."axis": the entity's Y axis, or a segment of itlengthlong (a cylinder, a capsule; with a negative strength, a rotating station's spin gravity)."direction": alongdirectioninside the boxhalfExtents(an anti-gravity zone, a wall to walk on, a current)."surface": the nearest point of the entity's own colliders, whatever their shape (a box, a capsule, a convex hull, a mesh, a terrain)."path": the nearest point of the line throughpoints(a path, a spline sampled into points, a track a body rides along; repeat the first point at the end for a loop).strength(default9.81): the pull atradius, in m/s². Negative pushes away: a"point"field with a negative strength andrangethe shell's radius is an inverted sphere, dropping what is inside onto the shell.falloff(default"constant"): how the pull changes pastradius."constant";"linear", to nothing atrange;"inverseSquare",strength * (radius / distance)^2, a body of mass withradiusits surface;"curve",strengthtimes the scalecurvestates at the distance.radius(default1) andrange(default0: everywhere): where the falloff starts, and how far the field reaches, in world units from what it pulls toward (for a"direction"field: the height above the plane through the entity, against its pull).halfExtents(default zero: everywhere) anddirection(default{ 0, -1, 0 }): a"direction"field's box and pull, in the entity's own frame.length(default0: the whole line): an"axis"field's segment length.priority(default0) andcombine(default"priority"): where several fields reach a place, the"priority"fields of the highest priority are its gravity, summed when they share it, in place of the world's vector;"add"fields add on top of whatever acts there (a wind, a current, a tractor beam).groups(default0xFFFFFFFF): the collision groups the field acts on, against a body'sCollisionGroupmembership (a body with none belongs to every group).curve(default empty): a"curve"falloff's{ distance, scale }points, in order of distance.points(default empty): a"path"field's{ x, y, z }points, in the entity's own frame.
A body's gravityScale scales the gravity it takes, field or world. A field leaves its own entity's body alone, so a moving planet carries its gravity with it. Disabling the component (.enabled = false) switches the field off from the next step.
The step evaluates fields at every substep for every dynamic body while any field stands: about bodies times fields vector work each substep.
-- a small planet of radius 10 that walkers and crates fall onto, all the way round
local planet = entity.spawn("planet")
planet.component.add("SphereCollider", { radius = 10 })
planet.component.add("GravityField", { strength = 9.81, falloff = "inverseSquare", radius = 10, range = 80 })
-- a moon on a circular orbit 30 m out, set going on the first step: the field
-- and the moon's body stand in the physics world from the next step
local moon = entity.spawn("moon")
moon.localPosition = { 30, 0, 0 }
moon.component.add("Physics", { kind = "dynamic" })
moon.component.add("SphereCollider", { radius = 1 })
local off
off = Physics.onBeforeStep(function()
off()
Physics.setVelocity(moon.id, Physics.orbitVelocity(moon.position, planet.position))
end)
-- an anti-gravity zone that wins over the planet inside its box
zone.component.add("GravityField", { shape = "direction", direction = { 0, 1, 0 }, strength = 2, halfExtents = { 5, 5, 5 }, priority = 1 })
-- a ring station spinning gravity outward from its axis
station.component.add("GravityField", { shape = "axis", strength = -9.81, range = 50 })
-- which way is down here
local up = Physics.upAt(entity(player).position)