About bevy-animation
A reference skill for implementing animation in Bevy 0.19, aimed at developers working with clips, graphs, events, and procedural motion. It includes verified patterns and guidance on blending, glTF imports, performance, and applying the 12 animation principles.
Key features
- 01Builds blend and additive animation graphs
- 02Plays and cross-fades animation clips
- 03Triggers events from clip timelines
- 04Creates procedural easing and curves
- 05Guidance on glTF import and performance
Use cases
- 01Blend idle, walk, and run animations
- 02Trigger footsteps or effects from a clip
- 03Tween transforms without an AnimationClip
- 04Troubleshoot Blender-to-glTF bone animation
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-animationAfter installing, type/bevy-animationin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/animation-events.mdanimation-graph.mdcurves-and-tweening.mdgltf-import.mdperformance.mdprinciples-authoring.mdprinciples-easing-and-timing.mdprinciples-graph-layering.mdprinciples-transform-keyframes.mdprocedural-animation.mdstate-machines.mdDetails
From the project READMEBevy 0.19 — Animation (graphs, blending, events, 12 principles)
When to use this skill
- Loading a glTF clip and playing it on a character:
AnimationPlayer+AnimationGraphHandle. - Cross-fading between idle / walk / run via
AnimationTransitions::play(player, node, Duration). - Building an
AnimationGraphin code: blend nodes, additive nodes, weights,AnimationMask. - Triggering gameplay events from a clip timeline (footsteps, hitbox activation, VFX cues) with
#[derive(AnimationEvent)]+clip.add_event_to_target(...)+On<MyEvent>observers. - Procedural tweening without an
AnimationClip— sampling aCurveper frame viaEasingCurve+EaseFunctionorCubicSegment::new_bezier_easing. - Applying the 12 Basic Principles of Animation (Thomas & Johnston, 1981) to a Bevy character.
- Importing a Blender rig and finding bones aren't animated — usually the
Namerequirement forAnimationTargetId::from_name.
Canonical end-to-end pattern
Verified against bevy = "0.19" — cargo check clean in bevy-skills-tester/skill-snippets/examples/bevy_animation.rs.
use bevy::{
animation::{
animated_field,
animation_curves::{AnimatableCurve, AnimatableKeyframeCurve},
AnimationEvent, AnimationTargetId,
},
prelude::*,
};
use core::time::Duration;
#[derive(AnimationEvent, Clone)]
struct FootstepEvent { foot: u8 }
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut clips: ResMut<Assets<AnimationClip>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
// 1. Load a glTF clip
let walk: Handle<AnimationClip> =
asset_server.load("models/character.glb#Animation0");
// 2. Build a tiny procedural clip with a sample curve + an event
let bone = AnimationTargetId::from_name(&Name::new("Hips"));
let tween = AnimatableKeyframeCurve::new([
(0.0_f32, Vec3::ZERO),
(0.5, Vec3::new(0.0, 1.0, 0.0)),
(1.0, Vec3::ZERO),
]).expect("strictly-increasing times");
let curve = AnimatableCurve::new(animated_field!(Transform::translation), tween);
let mut proc = AnimationClip::default();
proc.add_curve_to_target(bone, curve);
proc.add_event(0.5, FootstepEvent { foot: 0 });
let proc = clips.add(proc);
// 3. Compose a graph: root → walk + additive(proc, mask=group 0 excluded)
const MASK_GROUP_0_BIT: u64 = 1 << 0;
let mut graph = AnimationGraph::new();
let root = graph.root;
let _walk_node = graph.add_clip(walk, 1.0, root);
let additive = graph.add_additive_blend(0.5, root);
let _proc_node = graph.add_clip_with_mask(proc, MASK_GROUP_0_BIT, 1.0, additive);
// 4. Spawn the player entity (bones come from the loaded glTF scene)
commands.spawn((
Name::new("AnimationRoot"),
AnimationPlayer::default(),
AnimationGraphHandle(graphs.add(graph)),
AnimationTransitions::new(),
));
}
fn start(mut q: Query<(&mut AnimationTransitions, &mut AnimationPlayer), Added<AnimationPlayer>>) {
use bevy::animation::{graph::AnimationNodeIndex, RepeatAnimation};
for (mut tx, mut player) in &mut q {
tx.play(&mut player, AnimationNodeIndex::new(1), Duration::from_millis(250))
.set_repeat(RepeatAnimation::Forever);
}
}
fn on_footstep(trigger: On<FootstepEvent>) {
let foot = trigger.foot; // On<E> derefs to &E
let _entity = trigger.trigger().target; // AnimationEventTrigger::target
let _ = foot;
}
fn main() {
App::new()
.add_plugins(DefaultPlugins) // gltf_animation is a default feature
.add_systems(Startup, setup)
.add_systems(Update, start)
.add_observer(on_footstep)
.run();
}
Topics
| Topic | Reference |
|---|---|
Building blend / additive graphs, AnimationMask, hot-swap | references/animation-graph.md |
Blender → glTF → Bevy import: Name, axis, multi-clip, unsupported extensions | references/gltf-import.md |
Procedural animation in Update vs FixedUpdate, root-motion extraction | references/procedural-animation.md |
Idle / walk / run blending; AnimationTransitions::play + Bevy States integration | references/state-machines.md |
#[derive(AnimationEvent)], add_event_to_target, On<E> observer wiring | references/animation-events.md |
AnimatableCurve, EasingCurve, EaseFunction, cubic-bezier, UnevenSampleAutoCurve | references/curves-and-tweening.md |
AnimationPlayer cost, skin buffers, graph complexity, runtime graph swaps | references/performance.md |
| Principles — Slow In/Out, Timing, Anticipation → easing + duration | references/principles-easing-and-timing.md |
Principles — Squash & Stretch, Arc, Exaggeration → Transform keyframes | references/principles-transform-keyframes.md |
| Principles — Staging, Follow Through, Secondary Action → graph layering + masks | references/principles-graph-layering.md |
| Principles — Straight-Ahead vs Pose-to-Pose, Solid Drawing, Appeal → upstream authoring | references/principles-authoring.md |
The 12 Basic Principles of Animation
The four principles-*.md references group Thomas & Johnston's twelve principles by which Bevy primitive they actually exercise. Quick map:
- Easing & Timing — Slow In/Out, Timing, Anticipation map to
EaseFunction/EasingCurve/CubicSegment::new_bezier_easingand clip duration +ActiveAnimation::set_speed. - Transform keyframes — Squash & Stretch, Arc, Exaggeration are
AnimatableKeyframeCurveoverTransform::scale/::translationviaanimated_field!. - Graph layering — Staging, Follow Through & Overlapping Action, Secondary Action are
AnimationGraph::add_additive_blend+AnimationMaskgroup isolation. - Authoring — Straight-Ahead vs Pose-to-Pose, Solid Drawing, Appeal live in your DCC tool (Blender, Maya). Bevy plays whatever you authored.
Gotchas
- Target components are split.
AnimationTarget { id, player }no longer exists. Bone entities useAnimationTargetId(Uuid)plusAnimatedBy(Entity); the glTF loader supplies both. - Regenerate persisted target IDs. Bevy 0.19 changed the
AnimationTargetIdcalculation algorithm. Serialized target IDs from 0.18 must be recalculated from the hierarchy/name path. gltf_animationis on by default. It's already inbevy = "0.19"'s default features — no opt-in needed unless you randefault-features = false.AnimationTransitions::play_with_transitiondoes NOT exist. The real and only method isplay(&mut self, player, node, Duration) -> &mut ActiveAnimation. Chain.set_repeat(RepeatAnimation::Forever)/.set_speed(f32)on the returned value.animated_field!andAnimatableCurveare not in the prelude. Import explicitly frombevy::animation::{animated_field, animation_curves::{AnimatableCurve, AnimatableKeyframeCurve}}.On<AnimationEvent>is not anEntityEventobserver.On<E>derefs to&E(access event fields directly). The firing entity is attrigger.trigger().target(theAnimationEventTrigger::targetfield renamed fromanimation_playerin 0.18)..target()is not available — that's forEntityEvents.Nameis required on bone entities forAnimationTargetId::from_name(&Name)to resolve. The glTF loader sets this; hand-built skeletons must too.AnimationMaskbit polarity. A bit set in a node'su64mask means that node will not animate targets in that group. Register a bone into a group viagraph.add_target_to_mask_group(target_id, group_u32). To restrict a node to a single group, set every bit except that group's.- Schedule. Bevy's animation systems run in
PostUpdate, chained.before(TransformSystems::Propagate). Procedural systems that also writeTransformshould run inUpdateor order explicitly relative toAnimationSystemsinPostUpdate. - Out of scope in core animation. No built-in IK, particle/FX systems are
separate, and the glTF loader does not implement
KHR_animation_pointer— seereferences/gltf-import.md.
See also
bevy-cameras— camera animation (panning, dolly, focus follow) overlaps with this skill; use the sameEaseFunction/EasingCurvetoolkit.bevy-core-concepts—UpdatevsFixedUpdatechoice matters for procedural animation; seeprocedural-animation.md.bevy-ecs-systems— observer wiring patterns (On<E>,add_observer) used forAnimationEvents.bevy-pbr-materials— material-parameter animation viaAnimatableCurvetargetingStandardMaterialfields (emissive flicker, alpha fades).bevy-custom-assets—AssetLoaderpatterns if you serialiseAnimationGraphto RON (.animgraph.ron) and load it as an asset.bevy-migration-0-18-to-0-19— persisted target-ID and morph-target asset changes.
Comments
Markdown supported