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:
--!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 = 1The 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:
--!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#
aimturnsendEffectoralone so that itsaxispoints at the target. A body's arm is one joint, so aiming is how a body points.lookAtturnsendEffectorso that its front,vec3(0, 0, -1), faces the target. With achainRoot, like the torso, the root turnsrootWeightof the way first and the end effector takes the rest.positionmoves 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
polechoosing which way the elbow bends. - One bone points at the target.
- Longer chains turn each joint in turn until the end gets there.
- A chain of two bones, like an upper arm and a forearm ending at a hand bone, is solved exactly,
with
transformdoes whatpositiondoes, 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:
-- 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.yis 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'sworldCframe. 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.
--!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.
