walkerplugin
A kinematic body on any ground: walk, slide, seek.
Putting it in a scene
walker has no row of its own and draws nothing. The scene's world.walker.kinds is a table of the kinds whose rows walk, each with the numbers its bodies are tuned by, and a rule calls walk, slide, carry and seek for its own row. A child scene inherits world.walker from the manager scene. The ground is named by mods: with no height mod, a rule cannot ask for the scene's ground.
"world": {
"walker": {
"kinds": {
"xbot": {
"radius": 0.4, "height": 1.8, "accel": 12, "airAccel": 2, "turnRate": 10, "turnMax": 12,
"gravity": 20, "jumpH": 0.9, "step": 0.6, "maxSlope": 0.7, "slopeBand": 0.1,
"wade": 0.5, "span": 0.5, "probe": 2, "slide": 4, "seekSpeed": 3,
"bodies": ["xbot"]
}
},
"mods": { "height": "terrain3d", "depth": "terrain3d", "cell": "terrain3d", "solids": "scatter3d" }
}
}
Properties
Read from world.walker.
.kinds : { [kind]: numbers }
Kind name to the numbers its bodies are tuned by. A row walks by its own kind's entry, or by the entry of the kind its as field names, so a seat's player row that plays an xbot walks as one. A kind the table does not name cannot walk. Every number is required except turnMax, airAccel and bodies. The table is checked once, the first time walker reads it, and the limits below are what it checks.
| Number | Meaning |
|---|---|
radius | Metres: the body's circle against solids. Above 0. |
height | Metres: the body's height, for whatever stands on it. Above 0. |
accel | 1/s. vel eases toward want by 1 − exp(−accel·dt) of the gap a step. Above 0. |
airAccel | Optional, 1/s: the same rate for a body that starts the step in the air: not grounded, and higher above the stand height than one step's fall. 0 keeps the horizontal speed it left the ground with, so a leap or a fling carries all the way to the landing; a small rate lets the keys steer it a little. accel when absent. At least 0. |
turnRate | 1/s. yaw eases toward its aim by 1 − exp(−turnRate·dt) of the gap a step. Above 0. |
turnMax | Optional, rad/s: the fastest yaw turns, so a heavy body swings round at a steady rate and eases in only at the end. No limit when absent. Above 0. |
gravity | m/s², downward. Above 0. |
jumpH | Metres: a jump leaves the ground at √(2·gravity·jumpH). At least 0. |
step | Metres: the tallest rise a foot takes in one move, and the spread of feet that one stand height is taken over. Above 0. |
maxSlope | Rise over run: the steepest grade climbed. Above 0. |
slopeBand | Rise over run. A slide starts where the ground is steeper than maxSlope and ends only where it is under maxSlope − slopeBand, so ground whose grade hovers at maxSlope does not turn grounded on and off. Above 0 and under maxSlope. |
slide | m/s: the speed a body on steep ground slides down it at. Above 0. |
wade | Metres: the deepest water walked into. At least 0. |
span | Metres: the side of the footprint square. Above 0. maxSlope · span · √2 must be under step, so that on any grade up to maxSlope all the feet stay within step of each other. |
probe | Metres: how far ahead the slope rule looks for a rise past step. Above 0. |
seekSpeed | m/s: the speed seek walks at. Above 0. |
bodies | Optional: the kinds of the table whose bodies are solid to this kind's, or player, whose rows walk by their as. Each row of one, except the body itself and one that is limp (ragdoll3d's, lying), is a solid circle of its kind's radius at its pos. slide, carry and seek ask for them with the ground's solids. None when absent. |
.mods : { height, depth?, cell?, solids? }
The plugins that provide the ground (see Mods). With no solids there are none, and with no cell the ground is asked at each point a foot stands on.
The row
Fields on a walking row. A rule writes arrays as new arrays, never element by element, since an actor entry's arrays are the loaded scene's own.
| Field | Meaning |
|---|---|
pos | [x, y, z], the feet. |
yaw | Radians, kept in (−π, π]. Yaw θ faces (sin θ, cos θ) in x and z. |
vel | [x, y, z] in m/s. |
grounded, sliding | Whether the body ends the step on walkable ground, and whether it is on steep ground (see below). A body on steep ground, or within one step's fall of it, is not grounded and cannot jump. |
speed | The horizontal speed it moved at. |
side, turned | Kept by seek while it goes round an obstacle. |
want | Intent, [x, z]: the horizontal velocity the rule asks for, in m/s. |
face | Intent: a yaw to turn to, or null to face the way want goes. |
jump | Intent: true to jump. It is cleared on the next walk, whether the body jumped or not. |
as | The kind whose entry the row walks by, when it is not the row's own. |
Steep ground, steps and water
The body stands on four feet at the corners of a span square, square to the world's axes, and one at its centre. The stand height is the mean height of the feet within step of the highest, so across a ledge it is the feet still on top. The ground is steep where the stand height rises faster than maxSlope. A body on steep ground slides: the uphill part of its want is dropped and its downhill part is at least slide. A steep feature whose whole rise, measured over probe metres, is within step is stepped over, and a taller one is refused, whether a wall, stacked risers or a narrow ridge. So no hop climbs a face steeper than maxSlope further than jumpH + step above the last walkable ground the body stood on. Water depth is the plain mean of the four corners' depth.
A ground with a cell, in metres, is stood on as the triangles of a lattice of that cell. Each corner, a whole number of cells along x and z, is asked of height and depth once in a call, and each cell is split into two triangles along the diagonal from its corner of least x and z to its corner of greatest x and z, as terrain3d draws its finest ring. Every foot, gradient and probe of the call is worked out from those corners, so a body stands on the ground that is drawn, and a game that draws its ground finer has its bodies stand on that finer ground.
Methods
From #plugins/walker/walker.sim.ts. ground is { height(x, z), depth?(x, z), solids(x, z, r), cell? }: height is the ground a foot stands on, depth the water over it, solids every circle { x, z, radius } reaching within r of (x, z), and cell the side in metres of the lattice whose triangles the body stands on.
walk( me, w, dt, ground )
Steps a walking row once. Its vel eases toward want, horizontally and as a vector, at accel, or at airAccel when the body starts the step in the air, and yaw eases the short way round toward face, or toward want's bearing when face is null and want is not zero. A jump while grounded sets the vertical speed to √(2·gravity·jumpH) and leaves the ground. The body then moves by vel·dt as slide does. On the ground it follows a rise or a flat. In the air it loses gravity·dt of vertical speed each step and lands when it reaches the stand height. A drop is followed while the ground stays within what gravity would drop the body in a step, and below that the ground has fallen away faster and the body leaves it. vel and speed are read back from the horizontal motion that was made, so a wall shows as vel easing toward a want it did not reach. An unobstructed body's vel is the eased velocity exactly.
slide( me, w, delta, ground ) : [x, z]
Moves the body horizontally by delta [x, z] within the limits below, and returns the move it made. It leaves pos.y to walk. The limits are applied in rounds, at most 6. A refused move has its part along the refusal's direction removed, so the body slides along what stopped it.
| Limit | What it refuses |
|---|---|
| Solids | The point is pushed out of every circle it is within radius of, straight out from the circle's centre, so solids are slid round. |
| Wading | A move into water deeper than wade, and deeper than the footprint already stood in, is refused across the depth's gradient, which projects the move onto the shoreline. A footprint already that deep moves freely while it does not deepen. |
| Step | A move is refused if the ground under some foot at the new point is more than step above that foot. A foot over a drop or in the air counts as at the body's height. Walking off a ledge is never refused, a vertical riser taller than step is refused at any angle, and a kerb up to step passes. A body in the air steps up from its own height. |
| Slope | A move onto steep ground that raises the stand height is refused across the gradient there. Otherwise a move that raises the stand height above both where it started and pos.y is refused when the grade at the new point is over maxSlope and the highest ground sampled out to probe metres along that grade is more than step above the stand height at the start. A move along a steep face's contour or down it does not rise, and flat ground beside a drop is never a climb. |
carry( me, w, delta, ground ) : [x, z]
For a clip's measured travel, such as a lunge or a roll. It is slide with delta first cut where the body's circle first meets a solid's on the way, so the body stops against it where a walk would slide round. A body touching a solid, or within it, does not move toward it at all, and a move away from it, or past it, is not cut. A rule carries after it walks. carry then puts the body on the ground where the move leaves it: it stands on the stand height where that is at or above pos.y, and follows a drop of up to step onto ground that is not steep. Any other drop leaves the body in the air at its height, and walk lets it fall.
seek( me, w, target, stop, ground ) : boolean
Sets want toward target [x, z] at the kind's seekSpeed and returns whether the body has arrived. Within stop metres, want is zeroed and it returns true. While the straight way passes through no solid grown by the body's radius, want points at the target and side is 0. Otherwise the body goes round the obstacle, aiming at a tangent to its edge, and keeps the side it chose in side (1 or −1) until the line comes clear. It chooses the side whose way round is shorter. A way round that has turned want a whole turn without the line coming clear is going round a pocket, so the other side is taken, which is what turned counts. Solids that touch are gone round together as one obstacle.
relative( stick, yaw ) : [x, z]
A camera-relative stick. stick is [x, z] with forward as z, so [0, 1] is W, and right as x, so [1, 0] is D. It is turned so that stick-forward lands on (sin yaw, cos yaw), the camera's look direction, and stick-right on (−cos yaw, sin yaw), the camera's right, and clamped to length 1. A rule multiplies it by its speed to make want.
groundOf( w ) : Ground
The ground the scene's mods make, with the cell mod's cell when the scene names one. A scene that names no height mod cannot be asked.
stand( numbers, ground, x, z ) : number
The stand height at (x, z): the mean height of the five feet that are within step of the highest, on the lattice's triangles when the ground has a cell.
numbers( me, w ) : Numbers
The numbers of a row's kind, after the table has been checked.
kindOf( me ) : string
The kind a row walks by: the kind its as names, or its own.
check( kinds )
Checks a table of kinds against the limits above. It throws a sentence that names the kind and the number that is not finite or out of range (an airAccel below 0 among them), a bodies that is not a list of the table's kinds, and a footprint that spreads past step.
Mods
Name them in world.walker.mods.
- terrain3d
height,depthandcell: the height of the ground under any water, the depth of the water over it, and the cell of terrain3d's finest ring, 2 ×reach/segofworld.terrain3d.rings[0]in metres, so bodies stand on the triangles terrain3d draws.heightis required.depthis optional, and with none the ground has no water.cellis optional. - scatter3d
solids: the circles of its solid instances, trunks and boulders, that a body slides round.
walker gives two mods.
- hitscan3d
bodies: name it inworld.hitscan3d.modsas{ bodies: "walker" }. A shot then meets walker's bodies: every row of a kind thatworld.walker.kindslists is one upright cylinder of its kind'sradiusandheight, standing on itspos, its feet. A hit on it names the row's kind as the part struck. - ragdoll3d
gravity: name it inworld.ragdoll3d.modsas{ gravity: "walker" }.gravity(world, kind)isworld.walker.kinds[kind].gravity, in m/s², or undefined for a kind the table does not list, so a limp body falls at the gravity it walked under.