Builtin Library
README.md
walls
Wall-detection and slide helpers for the character controller. Runs the horizontal pass of a character's step, sweeps the character's own capsule along the movement direction, puts a body that overlaps a wall back outside it, probes a single height with a horizontal ray, and slides a movement vector along a wall normal. No state.
Exports
M.move(x: number, y: number, z: number, dx: number, dz: number, height: number, radius: number, skinWidth: number, maxSlopeAngle: number, selfId: string) -> Step: the horizontal pass of one step. Puts a body that overlaps a wall back outside it (pushOut), then carries it along(dx, dz): the body stopsskinWidthshort of the first wall the step runs into, and the rest of the step slides along that wall and along the next one the slide meets. Returns{ dx, dz, pushX, pushZ, blocked, hit }: where the body goes (push included), the push itself, whether the step met a wall and gained no ground towards where it asked to go, and the first wall it met.M.pushOut(x: number, y: number, z: number, height: number, radius: number, skinWidth: number, maxSlopeAngle: number, selfId: string) -> (number, number): the horizontal push that puts a body overlapping a wall back outside it,skinWidthclear of the surface, along the overlap's shortest way out.0, 0for a body that overlaps no wall.M.castBody(x: number, y: number, z: number, dirX: number, dirZ: number, height: number, radius: number, skinWidth: number, maxSlopeAngle: number, selfId: string, distance: number?) -> hit?: sweep the capsule spanning the character from the top of the ground band above(x, y, z)(its feet) up toy + height, reachingdistance + skinWidthpast its own surface (skinWidthalone whendistanceis omitted). Returns the wall standing across the path, ornilwhen the path is clear.M.castDirection(x: number, y: number, z: number, dirX: number, dirZ: number, radius: number, skinWidth: number, selfId: string) -> hit?— single horizontal ray of lengthradius + skinWidth, from whichever sample height the caller passes. The ray passes through the colliders the body carries (selfIdand every entity parented under it).M.slideAlongWall(moveX: number, moveZ: number, normalX: number, normalZ: number) -> (number, number)— strip the component ofmovegoing into the wall (v - (v . n) * n, XZ only). Returns input unchanged whendot >= 0or the XZ-normal is effectively zero.
Usage
local Walls = require("@builtin::systems.characterController.characterController.physics.walls")
local step = Walls.move(px, py, pz, vx * dt, vz * dt, 1.8, 0.3, 0.01, 45, selfId)
px, pz = px + step.dx, pz + step.dz
local hit = Walls.castBody(px, py, pz, dirX, dirZ, 1.8, 0.3, 0.01, 45, selfId)
if hit then
local slideX, slideZ = Walls.slideAlongWall(dx, dz, hit.normal.x, hit.normal.z)
end
Notes
-
movesweeps the whole step, so however long a frame's step is, the body ends outside every wall it walks at, and the next step it asks for starts clear of them, away from a wall it rests against as much as along it. -
A sweep begun inside a wall answers about the overlap in whichever direction it points.
pushOutmeasures how deep the overlap goes by sweeping the same body back in from outside the wall, so a body placed inside a wall, or shoved into one, walks again on the next step. -
A collider the body carries, on
selfIdor on an entity parented under it (a held prop, a trigger volume that travels with the character), is no wall:castBodysweeps through it andpushOutpushes nothing out of it, so a character carrying one stands where it is put and walks. -
castBodysweeps the whole body, so an opening narrower than2 * radiusstops the character even when nothing stands on the centre line — two cabinets 6 cm apart are a wall, not a doorway. -
A contact whose surface faces up or down — the ramps the character walks up, to
maxSlopeAngle, and anything directly overhead — belongs to the ground and ceiling passes.castBodysweeps on past those, so a body on a ramp still reports the wall ahead of it. -
castBodystarts its sweep above the ground band: a cap ofradiusresting on ground of angleareachesradius * (1 / cos(a) - 1)below the surface uphill of it, and a sweep begun inside a surface describes the shortest way out of that overlap instead of the surface standing across the path. The band is measured atmaxSlopeAngle, the steepest ground the character walks, and the ground and step passes own everything under it. -
slideAlongWallnormalises the wall normal in XZ; callers don't need to pre-normalise. The Y component of the normal is ignored because slide-along is a horizontal operation. -
The slide helper returns the input movement unchanged whenever the movement isn't pressing into the wall — there's no "stick to wall" behaviour, only "don't pass through".