Jiggle transforms add secondary motion to any Transform hierarchy: hair, tails, capes, antennae, foliage, chains and bones on a rig. You pick a root Transform and the components below it follow with springy, damped motion, driven by Verlet integration. There is a 3D form and a 2D form that run the same simulation, and an optional Entities form for DOTS projects.
Components #
VerletTransforms/VerletTransforms2Dhold a list of trees and update them inLateUpdate, after animation.VerletTransformTree/VerletTransformTree2Dare one jiggle hierarchy: arootTransform, asettingsasset and anignoreList. The 2D tree derives from the 3D one and runs the same simulation in the XY plane.VerletTransformTreeSettingsis a ScriptableObject holding the curves and values that shape the motion. Create it from the Assets, Create menu and share it between trees.VerletTransformNodeis one bone: its Transform and simulation state. Onlytargetis serialised, the rest is rebuilt when the tree registers.
Setting up #
- Put the bones in their rest pose.
- Add
VerletTransforms(orVerletTransforms2D) to a GameObject, usually the rig or a parent of the chain. - Add a tree to its list, set
rootto the first bone, and assign aVerletTransformTreeSettingsasset. - Press Play. Bones below the root now jiggle.
If you change the hierarchy at runtime, call RegisterTrees() again so the new rest pose is read.
How it behaves #
- Rest pose is captured on registration. The trees read the hierarchy at
Start. Bones move around that pose, so set it before the scene starts. - The root is not simulated. It follows its own animation and the bones below it are driven entirely by the simulation, so a tree should be the only thing animating those bones.
- Fixed time step. The simulation runs on a fixed clock of scaled game time,
simulationRatesteps per second (60 by default). The root is sampled as timestamped poses and the bones are interpolated between steps, so the motion is the same at any frame rate. Frame spikes, a slow start or losing focus do not disturb it, and a time scale of zero moves nothing. - Substeps. Each step is divided into
substepssmaller steps (4 by default) that solve the spring, the length and the contact constraints. More substeps make the bones stiffer and the motion truer, and cost more. The result converges as the count grows. - Real units. Mass is in kilograms, elasticity is a spring stiffness in newtons per metre, acceleration is in metres per second squared and
maxSpeedis in metres per second. A node of mass m held against gravity g by a spring of stiffness k sags by m times g over k. Gravity is an acceleration and does not depend on mass. - Contact is solved inside the step. Collision planes are found once per step and held last in every substep, for each node and for three sample points along each bone, so there is no jitter and no visible penetration. Collision only runs where the collision radius is above zero and
collisionLayersis not empty. - Pre-warm. By default a tree simulates with its root held still until the motion settles, before the first frame, so chains do not start out wagging. See below.
- One shared simulation. The 2D and 3D forms use the same core, so tuning carries across. 2D trees keep Z on the plane of the root and turn bones about Z, and collide with 2D physics.
Tree settings #
Most settings are curves read along the tree. A node’s weight is 0 at the root and 1 at the furthest node, and the curve is evaluated at that weight and multiplied by the curve’s scale. A scale of 0 turns that effect off.
| Setting | Default scale | What it does |
|---|---|---|
constantAcceleration | (0, -9.81, 0) | Acceleration on every node in metres per second squared, gravity by default. In 2D the Z component is ignored. |
mass | 0.01 | The mass of a node in kilograms. A heavier node sags further against the same spring, answers the spring and the root more slowly, and takes a smaller share of every correction. |
damping | 0.02 | How much motion is lost, 0 to 1, per sixtieth of a second. Applied per substep. |
elasticity | 1 | The stiffness of the spring that pulls a node back to its rest position, in newtons per metre. Higher is springier. |
stiffness | 0.01 | How much a node resists being stretched away from its rest position, 0 to 1. |
collision | 0 (off) | The collision radius of each node in world units. Zero means no collision at that point of the tree. |
collisionLayers | Nothing | The layers the nodes collide with. Nothing also skips the collision cost. |
angle | 0 (no limit) | The most a node may swing away from its rest direction, in degrees. |
maxSpeed | 100 | The fastest a node may move, in metres per second. Zero for no limit. |
| Simulation setting | Default | What it does |
|---|---|---|
simulationRate | 60 | Steps per second, 10 to 480. The step is fixed whatever the frame rate. Higher is smoother and costs more. |
substeps | 4 | Smaller steps inside each step, 1 to 16. |
maxStepsPerFrame | 120 | A cost limit for very long frames, not part of the physics. A frame that needs more steps skips the earliest ones and carries the tree along with its root over the skipped time, as a teleport would. |
maxMillisecondsPerFrame | 6 | A budget shared by all trees in Play Mode, in milliseconds per frame. When it runs out the simulation slows down instead of the frame rate collapsing. Zero for no limit. |
Curves are ScaledAnimationCurve values: an animation curve with a scale multiplier, so you shape the curve once and scale the strength separately. Settings are cached per tree. Inspector edits and scale changes are picked up automatically, but if you reshape a curve by code while trees are running, call settings.MarkChanged().
Forces #
Both components and each tree take forces as accelerations in metres per second squared.
| Member | What it does |
|---|---|
AddForce(force) | Adds a force to every node of every tree, applied on the next step. Use it every frame for wind. |
AddForce(treeIndex, force) | The same for one tree. |
AddForceAtPosition(forceMagnitude, position, radius = 1) | Pushes the nodes near a point away from it, strongest at the point and fading to nothing at the radius. Use it for impacts and explosions. |
RegisterTrees() | Reads the hierarchy of every tree again, taking the current pose as the rest pose. |
ResetTrees() | Puts every tree back at its rest pose and clears its motion. |
GetTree(index) | Returns a tree by index, or null with a warning when the index is out of range. |
The 2D forms take float2 forces. While showGizmo is on the bones and collision radii are drawn in the scene view.
Teleporting #
Moving the root instantly, such as a respawn or a level transition, reads as a very fast movement and would whip the bones along behind it. Call Teleport() right after the move, on the component or on a tree, and the shape and motion are kept. Teleport(position, rotation) on a tree moves the root and teleports in one call, and the 2D tree takes Teleport(Vector2 position, float degrees).
Pre-warm #
prewarm (on by default) and prewarmSeconds (10 by default) on VerletTransforms and VerletTransforms2D make each tree settle at Start: it simulates with the root held still until it has been below a small speed for a moment, or until the time limit passes. WarmTrees() on the component, or Warm(maxSeconds, settleSpeed) on a tree, settles on demand. It is a simulate-until-calm pass, run in chunks, not a one-shot static solve, so a limit that is too short can leave a long chain still moving.
Ignoring parts of a hierarchy #
Add Transforms to a tree’s ignoreList to leave them, and everything under them, out of the simulation. Use it for props held in a hand or bones that must stay animated.
Entities #
When the Entities package (1.0.0 or later) is installed, the Verlet Motion Toolkit also compiles an Entities layer in the Heathen.Verlet.Entities assembly. Each chain is one entity with a VerletChain component and VerletNode, VerletBonePose and VerletBone buffers. A solve system and an apply system run in the simulation group before the transform systems, with the same fixed clock, substeps, contact and pre-warm as the managed trees. Bakers convert existing VerletTransforms and VerletTransforms2D components, VerletChainBuilder.Create builds a chain from code, and VerletFieldAuthoring (shared wind) and VerletGroundAuthoring (a ground plane at the object’s world Y) bake the shared settings. Ropes are not on Entities yet.
Samples #
The Demo Scene shows hair, grass and links on many bones, the Skinned Mesh Sample shows jiggle on a skinned character, and the 2D Example shows two chains in 2D, one swinging into a 2D collider. See the Overview for the import path.