I don't really understand the new animation graph, it feels even more cumbersome than the old animation API.
I want to play basic animations I prepared in a gltf where every object only has a single animation for itself.
Should I put all clips in one big animation graph and then remember the number (that is also pretty cumbersome, why do I have to use indices? can't I just get an AnimationClipHandle or something back from inserting in the render graph?) and just play this clip on the correct object of the scene?
Will this then evaluate all clips for every object even if it only needs one?
Or should every object get its own single-clip Animation Graph?
#Understanding the Animation Graph
28 messages · Page 1 of 1 (latest)
I'm going to be upgrading to 0.14 soon for my project and I'm wondering the same thing. I'm guessing the answer is just create an animation graph using AnimationGraph::from_clip for each object?
However, like @deep sky mentioned this seems awkward if you want to store the animation graph for a single animation, since you can't even play the animation without also storing the new AnimationIndex IIUC.
// Create a new `AnimationGraph` and add the animation handle to it.
let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation));
let mut player = AnimationPlayer::default();
// Play the animation index, not the handle.
player.play(animation_index);
In essence, the new API does feel way more cumbersome 😢 .
Also @deep sky I was looking at setup_scene_once_loaded from https://bevyengine.org/examples/stress-tests/many-foxes/ but it didn't really answer any questions in terms of better ergonomics for handling GLTF files.
Getting to the bottom of animation stuff too as I migrate towards 0.14.
It's gotten more cumbersome, but that's also because it's much more powerful
If you haven't done so yet I recommend running the animated fox example on 0.14, and seeing how smooth the transitions between animations are
Very cool
I ran the example and I do get that it's a cool thing to have when you need it, but for simple animation use cases like mine, it's even worse than it was before and I was hoping to get a simpler API, yet now I don't even fully understand it.
something like an Animator trait which is both implemented on a single animation clip and a graph would be ok, but now it's always the graph but when I understood it right this time it's just for blending animations together? And the whole Animation on scene elements is still unclear to me, I always play all animation clips for every animation player, but that's not a way I like
Yes now you can blend animations together. You can put all your animations in a tree, and by following the tree from the root the animations are weighted down the branches
The way e.g. animated fox works now is:
graph
\_ animation 0, weight 1.0, leaf node
\_ animation 1, weight 1.0, leaf node
\_ animation 2, weight 1.0, leaf node
so since these animations don't have any children in the graph, there's no blending
which is probably similar to the requirements you have now, no blending needed
If you never want to blend animations, but have >1 animations on a model you can still benefit from this by using AnimationTransitions which allows smoothly transitioning into a new animation
Still, how should gltf animations on specific "entities"(?) be handled then?
I label different animations in blender, "Walking", "Running" etc.
I load the gltf, and then I iterate over the gltf.named_animations field. Iterating gives name (key) and animation clip handles (value).
I build the AnimationGraph with .add_clip (gives AnimationNodeIndex) in that loop, while at the same time building a HashMap<String, AnimationNodeIndex>. The key is the animation name and the value lets me lookup the animation within the graph.
So from then on I don't need to worry about much since I can lookup animations via their name, Walking etc. and feed that into the .play() API
If you were talking about locating the entity which is supposed to play the animation, that's a bit awkward because GLTF hierarchies can be arbitrarily complex, but I know that it is a child somewhere under my GLTF root
So I mark my GLTF when I spawn it with a marker e.g. MyGltfAnimatedTurtle, and then I iter_descendants on that until I find an AnimationPlayer
I 100% agree this is not very ergonomic, but it does make sense at least 😅
Sorry if this is going off topic, posting it in this thread in case this also helps clear things up for you @deep sky.
@ebon delta So right now in 0.13.2 I have:
#[derive(Resource, Reflect, Default)]
pub struct Animations {
pub foo_idle: Handle<AnimationClip>,
pub foo_walk: Handle<AnimationClip>,
pub foo_run: Handle<AnimationClip>,
pub bar_idle: Handle<AnimationClip>,
pub bar_walk: Handle<AnimationClip>,
pub bar_run: Handle<AnimationClip>,
// more animations here
In my current resource each foo_ bar_ prefix corresponds to one set of animations from a loaded GLTF.
I load things into the resource like so:
foo_idle: asset_server.load("models/character/name.glb#Animation0")
Then, when I want to run an animation against any entity I can just query the AnimationPlayer and play that clip directly.
It seems with the new animation system in 0.14 there would need to be an additional setup system. Where each time an entity with an AnimationPlayer spawns, I have to insert an AnimationGraph and AnimationTransitions component. After that is run, then I could play the animation clips, assuming the resource is also updated to store the graph + animation indexes from that graph.
And my resource would become:
pub struct Animations {
pub foo_graph: Handle<AnimationGraph>,
pub foo_idle: AnimationNodeIndex,
pub foo_walk: AnimationNodeIndex,
pub foo_run: AnimationNodeIndex,
pub bar_graph: Handle<AnimationGraph>,
pub bar_idle: AnimationNodeIndex,
pub bar_walk: AnimationNodeIndex,
pub bar_run: AnimationNodeIndex,
// more animations here
Does that sound correct or am I misunderstanding?
I've only just started experimenting myself but I think you are correct.
You could store the Handle<AnimationGraph> directly on the AnimationPlayer entity I believe, so you don't have to keep it in the Animations res unless you want to
Also I believe AnimationTransitions is optional and makes transitions smooth, but if you don't need to transition then I think AnimationPlayer should work directly
That looks exactly right to me. For the old style usage of having a single animation and blending from old add an AnimationTransition next to your AnimationPlayer in a fixup step when you also add the Handle<AnimationGraph>.
One interesting thing Bevy could do is add a generated default animation graph asset for every loaded GLTF so that by default things “just work”.
I implemented that in the gltf loader as a test while 0.14 was being hardened and it seemed fine though you still end up needing “something” which holds a mapping from something you can understand (an enum or strings) to the AnimationNodeIndexes of each animation in the graph (your Animations structure above). And that table needs creating at the same time as the graph asset so you end up pulling in assets in order to try and do anything useful.
I sort of wish AnimationTransition could optionally store a mapping from name string (from the GLTF…) to AnimationNodeIndex such that you could just play by name. It wouldn’t be great because each instance of AnimationTransition would have an identical table. I think there’s a way of getting round that though with an extra system which would do the transform for you.
The more I think about it the more I think when loading from a GLTF it would be very easy for Bevy to set up the scene such that you have everything you need for simple usage and can just replace with a more complex graph if you want.
I'm essentially writing something like this for my game now just to get the default weights of 1.0 for each animation since I don't plan on blending animations.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct Animations {
pub registry: HashMap<Model, ModelAnimations>,
}
#[derive(Default)]
pub struct ModelAnimations {
pub graph: Handle<AnimationGraph>,
pub named_animations: HashMap<Box<str>, AnimationNodeIndex>,
}
pub fn setup_animations_system(
mut animations: ResMut<Animations>,
mut animation_graphs: ResMut<Assets<AnimationGraph>>,
models: Res<Models>,
gltf_assets: ResMut<Assets<Gltf>>,
) {
// Initialize animations for each GLTF file that contains animations.
for (&model, gltf_handle) in models.gltf.iter() {
let Some(gltf) = gltf_assets.get(gltf_handle) else {
continue;
};
if gltf.named_animations.is_empty() {
continue;
}
let mut model_animations = animations.entry(model).or_default();
let mut animation_graph = AnimationGraph::new();
for (&animation_name, &clip) in gltf.named_animations.iter() {
let node_index = animation_graph.add_clip(clip, 1.0, animation_graph.root);
model_animations
.named_animations
.insert(animation_name, node_index);
}
model_animations.graph = animation_graphs.add(animation_graph);
}
}
I'm also puzzled by the new implementation. I don't understand how i manage animations per entity. Thankfuly i only have main character in my world, so i don't have to think about that 🙂 In the file is how I've migrated my code. I hope that it can help others, since Bevy examples do not show how to implement it for a character. My Player characters has two animations idle, and run. I also use bevy_asset_loader. My character has component CharacterMovement which has is_moving() but this could be easily changed to be used with keyboard input.
Whats the point of weight on nodes that arent blend nodes ?
Will weight still make them blend ?
I think this a pretty good explainer:
https://github.com/bevyengine/bevy/blob/26fc4c71988cac5bed90ab1005bdea2517808ba7/crates/bevy_animation/src/graph.rs#L16-L64
If I understand correctly: if you had two clip nodes with weights 1.0 and a root node and then played both clips at the same time it would blend them equally so the weights of those clip nodes matter even without a blend node. Where as a blend node lets you apply weights to all descendants and create hierarchies.
Okay im just not gonna use blend nodes for now 🤔😂. I was able to make custom functions and components last night that are super cool for this — like looping through all my animation clips, adding them to an animgraph , and then registering their nodeIndices to a hashmap in a resource