Layers and masks
Open the live demo · Read the source · View on GitHub
A crossfade moves the whole skeleton from one clip to another. Sometimes only
part of it should change: an arm waving while the body keeps walking. That is
what a layer is for, and AnimationMask is how it says which joints it may
touch.
Step 1: Two clips that both mention the arm #
The base clip, walk, animates the body and the arm both. The layer clip,
wave, only has a track for the arm. Both tracks address the same node index,
so they compete for the same joint.
final AnimationClip walk = AnimationClip(
name: 'walk',
tracks: <AnimationTrack>[
_rotationTrack(nodeIndex: 0, axis: Vector3(0, 0, 1), peak: 0.12),
_rotationTrack(nodeIndex: 1, axis: Vector3(1, 0, 0), peak: 0.5),
],
);
_waveTrack = _rotationTrack(
nodeIndex: 1,
axis: Vector3(0, 0, 1),
peak: 1.1,
);
final AnimationClip wave = AnimationClip(
name: 'wave',
tracks: <AnimationTrack>[_waveTrack],
);
Step 2: A layer masked to one joint #
playLayer starts the wave clip as a layer over the base. The mask names
node 1, the arm, and nothing else: the body keeps playing whatever the base
clip says regardless of what the layer does.
_player = AnimationPlayer(
clips: <AnimationClip>[walk, wave],
targets: <AnimationTarget?>[bodyMesh, armMesh],
)..play(0);
_wave = _player.playLayer(
1,
mask: AnimationMask(<int>[1]),
wrap: AnimationWrap.loop,
fadeIn: 0.0,
);
Step 3: Advance the player #
A layer has its own playhead, but it only moves when the player is asked to.
_player.update(dt);
What to look at #
The body keeps its own small tilt from the walk clip the whole time: the mask
never lets the layer near it. The arm, though, follows the wave clip and
nothing of the walk clip's own arm swing shows through, because a layer with
full weight replaces the base's value for the joints it covers rather than
blending with it.