Paths: splines, roads, tracks, rails, lanes and routes
| Piece | What it is |
|---|---|
@builtin::modules.spline | Curve math: linear, Catmull-Rom and Bezier splines, eased sampling, placement along a curve (fence posts, rows of props), and measuring a curve by arc length. |
@builtin::modules.pathNetwork | Networks of paths built at runtime: roads, streets, race tracks, railway and tram lines, footpaths, river courses, patrol and delivery routes. Drivable surfaces with colliders and a material, junctions, terrain cut and fill, directed lanes, and route queries. |
PathFollower component | Moves an entity along a path at a constant speed: a train, a tram, a patrol, a camera dolly, a ghost car. |
A single curve
local spline = require("@builtin::modules.spline")
local path = spline.new({ { 0, 0, 0 }, { 20, 2, 10 }, { 40, 0, 0 } }, { mode = "catmullRom" })
local mid = spline.evaluate(path, 0.5) -- by parameter
local m = spline.measure(path) -- by arc length
local p, dir = spline.atDistance(m, 12) -- 12 metres along, constant speed
local posts = spline.fence(path, 2.5) -- placements every 2.5 m
A spline's parameter does not advance at a constant rate along the curve, so anything that moves in metres per second reads the measured form.
Moving something along a path
local e = entity.spawn("tram")
e.component.add("Model", { model = "@builtin::meshes.cube" })
e.component.add("PathFollower", { speed = 8, mode = "loop" })
e.component.get("PathFollower"):follow(spline.new(points, { mode = "catmullRom", closed = true }))
PathFollower takes a list of points, a spline, or anything carrying points
(a network route, a lane loop), and loops, ping-pongs or stops at the end. It
sets the entity's transform, so it drives entities with no dynamic body. A
physics vehicle follows a path by steering instead (below).
A network of paths
local paths = require("@builtin::modules.pathNetwork")
local net = paths.new({ lanes = { forward = 1, backward = 1 }, speedLimit = 15 })
local a = net:addNode({ 0, 0, 0 })
local b = net:addNode({ 200, 0, 40 })
local c = net:addNode({ 380, 0, -60 })
net:addEdge(a, b, { points = { { 100, 0, 40 } } })
net:addEdge(b, c, { bank = 5 })
net:build()
- Edges run through control points; settings per edge or per network:
lanes each way, lane width, shoulder, speed limit, bank, material, tint and
the collider's fields (
friction, ...). - Junctions form where three or more edges meet; the edges stop short and a junction surface joins their ends.
- Elevation:
"terrain"lays the surface on the ground (smoothed);"points"takes the control points' heights. A bridge is an edge withelevation = "points"andconform = false. - Terrain: with a
Terrainin the scene,buildcuts and fills the ground to meet each surface and blends it back overfalloffmetres, throughTerrain:setHeights, which rebuilds the drawn ground and its collision where the change reaches. Moving or removing a path gives the ground back. - Live editing:
moveNode,setEdge,removeEdge, thenbuild()again rebuilds only what the edit reaches;buildanswers what it rebuilt and the milliseconds it took. - Procedural layout:
paths.procedural.connect(places),.grid({...}),.circuit({...})answer plans forpaths.fromPlan(plan). - A railway is a one-lane, one-way edge (
lanes = { forward = 1, backward = 0 },width = 3); a footpath a narrow edge with no collider; a river course an edge whose surface is a water material, lowered withlift.
Lanes, routes and AI drivers
Every edge carries directed lanes (<edge>:+1 from its first node,
<edge>:-1 back), joined through the nodes into a graph:
local route = net:route(car.position, goal, { by = "time" }) -- lanes, length, points
-- each tick: where the car is on its route, and the point it steers toward
local here = net:nearest(car.position, { heading = forward, lanes = route.lanes })
local target = net:lookAhead({ lane = here.lane, distance = here.laneDistance }, 15, { route = route.lanes })
A car built from WheelColliders with VehicleController { controlSource = "script" }
drives a network by steering toward target.position each tick
(setIntent { throttle, steer }); traffic reads lane.stopLine and
lane.speedLimit from net:lane(id).
man pathNetwork and the module's README list every call.