Moud

IK and springs#

Two instances bend a rig on top of its animation. An IKControl bends joints so that one of them reaches, points or looks at a target. A JointSpring makes a joint lag behind and swing, like a cape or a tail.

Both run on each client every tick, after the tracks and the engine's walk, so they bend the animated pose rather than replace it. IK runs first, then springs.

IK and springs run once per tick, like the rest of posing, and are drawn smoothly between ticks. A transform a LocalScript writes on a joint that nothing animates is where they start from.

Pointing at something#

A body pointing its right arm at a part:

luau
--!strict
local body = world:find("npc") :: Character
local joints = body:find("joints") :: Folder
local point = body:add("IKControl", {
    type = "aim",
    endEffector = joints:find("rightArm"),
    target = world:find("lamp"),
}) :: IKControl
point.weight = 1

The server can create a control, and it replicates like any instance. Put it anywhere, usually inside the rig.

Reaching for something#

A two-bone arm on a model reaching for a handle, bending outwards:

luau
--!strict
local mesh = model:find("mesh") :: MeshPart
local shoulder = mesh:find("RightArm") :: Bone
local hand = shoulder:findFirstDescendant("RightHand") :: Bone
model:add("IKControl", {
    type = "position",
    chainRoot = shoulder,
    endEffector = hand,
    target = world:find("handle"),
    pole = world:find("elbowHint"),
})

The chain is the joints from chainRoot down to endEffector. For bones that means parent bones. For motors it means each motor whose part0 is the part the next one holds. A body's motors all hang off its root, so a body's chain is one joint long, and on a body a control points the limb at the target rather than bending it.

IK types#

  • aim turns endEffector alone so that its axis points at the target. A body's arm is one joint, so aiming is how a body points.
  • lookAt turns endEffector so that its front, vec3(0, 0, -1), faces the target. With a chainRoot, like the torso, the root turns rootWeight of the way first and the end effector takes the rest.
  • position moves the end of the chain onto the target. The end effector's own turn is left alone.
    • A chain of two bones, like an upper arm and a forearm ending at a hand bone, is solved exactly, with pole choosing which way the elbow bends.
    • One bone points at the target.
    • Longer chains turn each joint in turn until the end gets there.
  • transform does what position does, and also turns the end effector to the target's rotation.

Weight and turning it off#

Turning enabled off, or bringing weight to 0, gives the joints back to the tracks on the next tick. A weight in between leaves the joint that far between its animated pose and the solved one, and it stays there tick after tick.

IKControl properties#

Property
type "aim", "lookAt", "position" or "transform"; "position" by default
chainRoot the first joint of the chain, like an upper arm. Empty uses endEffector alone
endEffector the joint that reaches, a Bone or a Motor
target an instance to reach for: a part, an attachment, a bone or a joint
targetCframe the world frame to reach for when target is empty
offset moves the goal in the target's own space
pole an instance the middle joint of a two-bone chain bends toward, like the way an elbow points
axis for aim, the joint's own axis that points at the target. vec3(0, -1, 0) by default, which is down a Minecraft limb
weight 0 leaves the pose alone, 1 solves all the way
rootWeight for lookAt, how much of the turn the chain root takes. 0.3 by default
smoothTime seconds the goal takes to catch up with a moving target. 0.05 by default, 0 snaps
priority lower priorities are solved first
enabled off leaves the joints to the tracks
animatedCframe read only: where the end effector was in the world this tick before the control bent it, written on each client

Feet on uneven ground#

Foot and hand IK is an IKControl per limb that a script moves each frame. For a foot, the target is the ground under where the walk puts the foot. animatedCframe is that spot: where the end effector was this tick, before the control bent the chain.

A model with two-bone legs, kept on the ground by a LocalScript:

luau
-- client
local world = game.world
local model = world:find("strider") :: Model
local skip = { exclude = { model } }

local function leg(side: string): IKControl
    return model:add("IKControl", {
        type = "position",
        chainRoot = model:findFirstDescendant(side .. "Thigh"),
        endEffector = model:findFirstDescendant(side .. "Foot"),
        smoothTime = 0.08,
        weight = 0,
    }) :: IKControl
end

local feet = { leg("left"), leg("right") }

game.renderStepped:connect(function()
    local base = model.worldCframe.position
    local _, floor = world:raycast(base + vec3(0, 1, 0), vec3(0, -3, 0), 3, skip)
    for _, ik in feet do
        local foot = ik.animatedCframe.position
        local _, ground = world:raycast(vec3(foot.x, base.y + 1, foot.z), vec3(0, -3, 0), 3, skip)
        if floor and ground then
            ik.targetCframe = cframe(ground + vec3(0, foot.y - floor.y, 0))
            ik.weight = 1
        else
            ik.weight = 0
        end
    end
end)
  • The script makes the controls itself, so they are this player's own and it may write them. A client cannot write a control the server made unless it owns the model.
  • foot.y - floor.y is how high the walk lifts the foot off the floor under the model. Adding it to the ground under the foot keeps the steps, and a foot on a step lands on the step.
  • Read animatedCframe, not the foot bone's worldCframe. The bone's frame already holds last tick's solved foot, so a target worked out from it pins the foot where it first landed.
  • A leg reaches as far as it is long. Ground lower than that leaves the foot hanging at full stretch, so the weight goes to 0 when either ray misses.
  • Use "transform" instead of "position" to also turn the foot to the target's rotation.

Hands work the same way: a chain from the upper arm to the hand, with target set to an attachment on what the hand holds, as in reaching with IK in first person.

JointSpring#

A JointSpring makes a joint lag behind and swing, like a cape, a tail or an ear. It follows a tip point a little way out along the joint, simulated in the world. The tip is pulled back toward where the animation puts it and pulled down by gravity, and the joint is turned to point at the tip.

luau
--!strict
local tail = mesh:findFirstDescendant("tail") :: Bone
tail:add("JointSpring", {
    axis = vec3(0, 0, 1),
    length = 0.5,
    stiffness = 40,
    damping = 6,
})

The tail sticks out backwards, so its axis is +z. When the model runs forward the tail trails behind, and when it stops the tail swings back and settles.

  • Put a spring on each bone of a chain and the chain swings as a whole. Parents are solved before their children.
  • A rig that jumps more than a few blocks in one tick starts its springs over instead of flinging them.
Property
joint the Bone or Motor that swings. Empty uses the spring's parent
stiffness how hard the tip is pulled back to the animated pose. 60 by default
damping how quickly the swinging dies down. 8 by default
gravity the pull on the tip, in blocks per second squared. vec3(0, -9.8, 0) by default
axis the joint's own direction from its pivot to the tip. Down by default
length how far the tip is from the pivot, in blocks
maxAngle the furthest the joint swings from its animated pose, in degrees
weight how much of the swing is used, 0 to 1
enabled on or off

In the editor#

The animation editor draws a control's target and pole, and a spring's axis, as handles you can drag. See IK and spring handles.