custom
11 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
21f1e3045c
|
Relationships (non-fragmenting, one-to-many) (#17398)
This adds support for one-to-many non-fragmenting relationships (with planned paths for fragmenting and non-fragmenting many-to-many relationships). "Non-fragmenting" means that entities with the same relationship type, but different relationship targets, are not forced into separate tables (which would cause "table fragmentation"). Functionally, this fills a similar niche as the current Parent/Children system. The biggest differences are: 1. Relationships have simpler internals and significantly improved performance and UX. Commands and specialized APIs are no longer necessary to keep everything in sync. Just spawn entities with the relationship components you want and everything "just works". 2. Relationships are generalized. Bevy can provide additional built in relationships, and users can define their own. **REQUEST TO REVIEWERS**: _please don't leave top level comments and instead comment on specific lines of code. That way we can take advantage of threaded discussions. Also dont leave comments simply pointing out CI failures as I can read those just fine._ ## Built on top of what we have Relationships are implemented on top of the Bevy ECS features we already have: components, immutability, and hooks. This makes them immediately compatible with all of our existing (and future) APIs for querying, spawning, removing, scenes, reflection, etc. The fewer specialized APIs we need to build, maintain, and teach, the better. ## Why focus on one-to-many non-fragmenting first? 1. This allows us to improve Parent/Children relationships immediately, in a way that is reasonably uncontroversial. Switching our hierarchy to fragmenting relationships would have significant performance implications. ~~Flecs is heavily considering a switch to non-fragmenting relations after careful considerations of the performance tradeoffs.~~ _(Correction from @SanderMertens: Flecs is implementing non-fragmenting storage specialized for asset hierarchies, where asset hierarchies are many instances of small trees that have a well defined structure)_ 2. Adding generalized one-to-many relationships is currently a priority for the [Next Generation Scene / UI effort](https://github.com/bevyengine/bevy/discussions/14437). Specifically, we're interested in building reactions and observers on top. ## The changes This PR does the following: 1. Adds a generic one-to-many Relationship system 3. Ports the existing Parent/Children system to Relationships, which now lives in `bevy_ecs::hierarchy`. The old `bevy_hierarchy` crate has been removed. 4. Adds on_despawn component hooks 5. Relationships can opt-in to "despawn descendants" behavior, meaning that the entire relationship hierarchy is despawned when `entity.despawn()` is called. The built in Parent/Children hierarchies enable this behavior, and `entity.despawn_recursive()` has been removed. 6. `world.spawn` now applies commands after spawning. This ensures that relationship bookkeeping happens immediately and removes the need to manually flush. This is in line with the equivalent behaviors recently added to the other APIs (ex: insert). 7. Removes the ValidParentCheckPlugin (system-driven / poll based) in favor of a `validate_parent_has_component` hook. ## Using Relationships The `Relationship` trait looks like this: ```rust pub trait Relationship: Component + Sized { type RelationshipSources: RelationshipSources<Relationship = Self>; fn get(&self) -> Entity; fn from(entity: Entity) -> Self; } ``` A relationship is a component that: 1. Is a simple wrapper over a "target" Entity. 2. Has a corresponding `RelationshipSources` component, which is a simple wrapper over a collection of entities. Every "target entity" targeted by a "source entity" with a `Relationship` has a `RelationshipSources` component, which contains every "source entity" that targets it. For example, the `Parent` component (as it currently exists in Bevy) is the `Relationship` component and the entity containing the Parent is the "source entity". The entity _inside_ the `Parent(Entity)` component is the "target entity". And that target entity has a `Children` component (which implements `RelationshipSources`). In practice, the Parent/Children relationship looks like this: ```rust #[derive(Relationship)] #[relationship(relationship_sources = Children)] pub struct Parent(pub Entity); #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent)] pub struct Children(Vec<Entity>); ``` The Relationship and RelationshipSources derives automatically implement Component with the relevant configuration (namely, the hooks necessary to keep everything in sync). The most direct way to add relationships is to spawn entities with relationship components: ```rust let a = world.spawn_empty().id(); let b = world.spawn(Parent(a)).id(); assert_eq!(world.entity(a).get::<Children>().unwrap(), &[b]); ``` There are also convenience APIs for spawning more than one entity with the same relationship: ```rust world.spawn_empty().with_related::<Children>(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` The existing `with_children` API is now a simpler wrapper over `with_related`. This makes this change largely non-breaking for existing spawn patterns. ```rust world.spawn_empty().with_children(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` There are also other relationship APIs, such as `add_related` and `despawn_related`. ## Automatic recursive despawn via the new on_despawn hook `RelationshipSources` can opt-in to "despawn descendants" behavior, which will despawn all related entities in the relationship hierarchy: ```rust #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent, despawn_descendants)] pub struct Children(Vec<Entity>); ``` This means that `entity.despawn_recursive()` is no longer required. Instead, just use `entity.despawn()` and the relevant related entities will also be despawned. To despawn an entity _without_ despawning its parent/child descendants, you should remove the `Children` component first, which will also remove the related `Parent` components: ```rust entity .remove::<Children>() .despawn() ``` This builds on the on_despawn hook introduced in this PR, which is fired when an entity is despawned (before other hooks). ## Relationships are the source of truth `Relationship` is the _single_ source of truth component. `RelationshipSources` is merely a reflection of what all the `Relationship` components say. By embracing this, we are able to significantly improve the performance of the system as a whole. We can rely on component lifecycles to protect us against duplicates, rather than needing to scan at runtime to ensure entities don't already exist (which results in quadratic runtime). A single source of truth gives us constant-time inserts. This does mean that we cannot directly spawn populated `Children` components (or directly add or remove entities from those components). I personally think this is a worthwhile tradeoff, both because it makes the performance much better _and_ because it means theres exactly one way to do things (which is a philosophy we try to employ for Bevy APIs). As an aside: treating both sides of the relationship as "equivalent source of truth relations" does enable building simple and flexible many-to-many relationships. But this introduces an _inherent_ need to scan (or hash) to protect against duplicates. [`evergreen_relations`](https://github.com/EvergreenNest/evergreen_relations) has a very nice implementation of the "symmetrical many-to-many" approach. Unfortunately I think the performance issues inherent to that approach make it a poor choice for Bevy's default relationship system. ## Followup Work * Discuss renaming `Parent` to `ChildOf`. I refrained from doing that in this PR to keep the diff reasonable, but I'm personally biased toward this change (and using that naming pattern generally for relationships). * [Improved spawning ergonomics](https://github.com/bevyengine/bevy/discussions/16920) * Consider adding relationship observers/triggers for "relationship targets" whenever a source is added or removed. This would replace the current "hierarchy events" system, which is unused upstream but may have existing users downstream. I think triggers are the better fit for this than a buffered event queue, and would prefer not to add that back. * Fragmenting relations: My current idea hinges on the introduction of "value components" (aka: components whose type _and_ value determines their ComponentId, via something like Hashing / PartialEq). By labeling a Relationship component such as `ChildOf(Entity)` as a "value component", `ChildOf(e1)` and `ChildOf(e2)` would be considered "different components". This makes the transition between fragmenting and non-fragmenting a single flag, and everything else continues to work as expected. * Many-to-many support * Non-fragmenting: We can expand Relationship to be a list of entities instead of a single entity. I have largely already written the code for this. * Fragmenting: With the "value component" impl mentioned above, we get many-to-many support "for free", as it would allow inserting multiple copies of a Relationship component with different target entities. Fixes #3742 (If this PR is merged, I think we should open more targeted followup issues for the work above, with a fresh tracking issue free of the large amount of less-directed historical context) Fixes #17301 Fixes #12235 Fixes #15299 Fixes #15308 ## Migration Guide * Replace `ChildBuilder` with `ChildSpawnerCommands`. * Replace calls to `.set_parent(parent_id)` with `.insert(Parent(parent_id))`. * Replace calls to `.replace_children()` with `.remove::<Children>()` followed by `.add_children()`. Note that you'll need to manually despawn any children that are not carried over. * Replace calls to `.despawn_recursive()` with `.despawn()`. * Replace calls to `.despawn_descendants()` with `.despawn_related::<Children>()`. * If you have any calls to `.despawn()` which depend on the children being preserved, you'll need to remove the `Children` component first. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
af10aa38aa
|
AnimatedField and Rework Evaluators (#16484)
# Objective Animating component fields requires too much boilerplate at the moment: ```rust #[derive(Reflect)] struct FontSizeProperty; impl AnimatableProperty for FontSizeProperty { type Component = TextFont; type Property = f32; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { Some(&mut component.font_size) } } animation_clip.add_curve_to_target( animation_target_id, AnimatableKeyframeCurve::new( [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] .into_iter() .zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]), ) .map(AnimatableCurve::<FontSizeProperty, _>::from_curve) .expect("should be able to build translation curve because we pass in valid samples"), ); ``` ## Solution This adds `AnimatedField` and an `animated_field!` macro, enabling the following: ```rust animation_clip.add_curve_to_target( animation_target_id, AnimatableCurve::new( animated_field!(TextFont::font_size), AnimatableKeyframeCurve::new( [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0] .into_iter() .zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]), ) .expect( "should be able to build translation curve because we pass in valid samples", ), ), ); ``` This required reworking the internals a bit, namely stripping out a lot of the `Reflect` usage, as that implementation was fundamentally incompatible with the `AnimatedField` pattern. `Reflect` was being used in this context just to downcast traits. But we can get downcasting behavior without the `Reflect` requirement by implementing `Downcast` for `AnimationCurveEvaluator`. This also reworks "evaluator identity" to support either a (Component / Field) pair, or a TypeId. This allows properties to reuse evaluators, even if they have different accessor methods. The "contract" here is that for a given (Component / Field) pair, the accessor will return the same value. Fields are identified by their Reflect-ed field index. The (TypeId, usize) is prehashed and cached to optimize for lookup speed. This removes the built-in hard-coded TranslationCurve / RotationCurve / ScaleCurve in favor of AnimatableField. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
015f2c69ca
|
Merge Style properties into Node. Use ComputedNode for computed properties. (#15975)
# Objective Continue improving the user experience of our UI Node API in the direction specified by [Bevy's Next Generation Scene / UI System](https://github.com/bevyengine/bevy/discussions/14437) ## Solution As specified in the document above, merge `Style` fields into `Node`, and move "computed Node fields" into `ComputedNode` (I chose this name over something like `ComputedNodeLayout` because it currently contains more than just layout info. If we want to break this up / rename these concepts, lets do that in a separate PR). `Style` has been removed. This accomplishes a number of goals: ## Ergonomics wins Specifying both `Node` and `Style` is now no longer required for non-default styles Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` ## Conceptual clarity `Style` was never a comprehensive "style sheet". It only defined "core" style properties that all `Nodes` shared. Any "styled property" that couldn't fit that mold had to be in a separate component. A "real" style system would style properties _across_ components (`Node`, `Button`, etc). We have plans to build a true style system (see the doc linked above). By moving the `Style` fields to `Node`, we fully embrace `Node` as the driving concept and remove the "style system" confusion. ## Next Steps * Consider identifying and splitting out "style properties that aren't core to Node". This should not happen for Bevy 0.15. --- ## Migration Guide Move any fields set on `Style` into `Node` and replace all `Style` component usage with `Node`. Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` For any usage of the "computed node properties" that used to live on `Node`, use `ComputedNode` instead: Before: ```rust fn system(nodes: Query<&Node>) { for node in &nodes { let computed_size = node.size(); } } ``` After: ```rust fn system(computed_nodes: Query<&ComputedNode>) { for computed_node in &computed_nodes { let computed_size = computed_node.size(); } } ``` |
||
![]() |
eb19a9ea0b
|
Migrate UI bundles to required components (#15898)
# Objective - Migrate UI bundles to required components, fixes #15889 ## Solution - deprecate `NodeBundle` in favor of `Node` - deprecate `ImageBundle` in favor of `UiImage` - deprecate `ButtonBundle` in favor of `Button` ## Testing CI. ## Migration Guide - Replace all uses of `NodeBundle` with `Node`. e.g. ```diff commands - .spawn(NodeBundle { - style: Style { + .spawn(( + Node::default(), + Style { width: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, - ..default() - }) + )) ``` - Replace all uses of `ButtonBundle` with `Button`. e.g. ```diff .spawn(( - ButtonBundle { - style: Style { - width: Val::Px(w), - height: Val::Px(h), - // horizontally center child text - justify_content: JustifyContent::Center, - // vertically center child text - align_items: AlignItems::Center, - margin: UiRect::all(Val::Px(20.0)), - ..default() - }, - image: image.clone().into(), + Button, + Style { + width: Val::Px(w), + height: Val::Px(h), + // horizontally center child text + justify_content: JustifyContent::Center, + // vertically center child text + align_items: AlignItems::Center, + margin: UiRect::all(Val::Px(20.0)), ..default() }, + UiImage::from(image.clone()), ImageScaleMode::Sliced(slicer.clone()), )) ``` - Replace all uses of `ImageBundle` with `UiImage`. e.g. ```diff - commands.spawn(ImageBundle { - image: UiImage { + commands.spawn(( + UiImage { texture: metering_mask, ..default() }, - style: Style { + Style { width: Val::Percent(100.0), height: Val::Percent(100.0), ..default() }, - ..default() - }); + )); ``` --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
![]() |
6f7d0e5725
|
split up TextStyle (#15857)
# Objective Currently text is recomputed unnecessarily on any changes to its color, which is extremely expensive. ## Solution Split up `TextStyle` into two separate components `TextFont` and `TextColor`. ## Testing I added this system to `many_buttons`: ```rust fn set_text_colors_changed(mut colors: Query<&mut TextColor>) { for mut text_color in colors.iter_mut() { text_color.set_changed(); } } ``` reports ~4fps on main, ~50fps with this PR. ## Migration Guide `TextStyle` has been renamed to `TextFont` and its `color` field has been moved to a separate component named `TextColor` which newtypes `Color`. |
||
![]() |
a6be9b4ccd
|
Rename TextBlock to TextLayout (#15797)
# Objective - Improve clarity when spawning a text block. See [this discussion](https://github.com/bevyengine/bevy/pull/15591/#discussion_r1787083571). ## Solution - Rename `TextBlock` to `TextLayout`. |
||
![]() |
c2c19e5ae4
|
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.** # Objective - Implement https://github.com/bevyengine/bevy/discussions/15014 ## Solution This implements [cart's proposal](https://github.com/bevyengine/bevy/discussions/15014#discussioncomment-10574459) faithfully except for one change. I separated `TextSpan` from `TextSpan2d` because `TextSpan` needs to require the `GhostNode` component, which is a `bevy_ui` component only usable by UI. Extra changes: - Added `EntityCommands::commands_mut` that returns a mutable reference. This is a blocker for extension methods that return something other than `self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable reference for this reason. ## Testing - [x] Text examples all work. --- ## Showcase TODO: showcase-worthy ## Migration Guide TODO: very breaking ### Accessing text spans by index Text sections are now text sections on different entities in a hierarchy, Use the new `TextReader` and `TextWriter` system parameters to access spans by index. Before: ```rust fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) { let text = query.single_mut(); text.sections[1].value = format_time(time.elapsed()); } ``` After: ```rust fn refresh_text( query: Query<Entity, With<TimeText>>, mut writer: UiTextWriter, time: Res<Time> ) { let entity = query.single(); *writer.text(entity, 1) = format_time(time.elapsed()); } ``` ### Iterating text spans Text spans are now entities in a hierarchy, so the new `UiTextReader` and `UiTextWriter` system parameters provide ways to iterate that hierarchy. The `UiTextReader::iter` method will give you a normal iterator over spans, and `UiTextWriter::for_each` lets you visit each of the spans. --------- Co-authored-by: ickshonpe <david.curthoys@googlemail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
![]() |
700123ec64
|
Replace Handle<AnimationGraph> component with a wrapper (#15742)
# Objective - Closes #15717 ## Solution - Wrap the handle in a new wrapper component: `AnimationGraphHandle`. ## Testing Searched for all instances of `AnimationGraph` in the examples and updated and tested those ## Migration Guide `Handle<AnimationGraph>` is no longer a component. Instead, use the `AnimationGraphHandle` component which contains a `Handle<AnimationGraph>`. |
||
![]() |
25bfa80e60
|
Migrate cameras to required components (#15641)
# Objective Yet another PR for migrating stuff to required components. This time, cameras! ## Solution As per the [selected proposal](https://hackmd.io/tsYID4CGRiWxzsgawzxG_g#Combined-Proposal-1-Selected), deprecate `Camera2dBundle` and `Camera3dBundle` in favor of `Camera2d` and `Camera3d`. Adding a `Camera` without `Camera2d` or `Camera3d` now logs a warning, as suggested by Cart [on Discord](https://discord.com/channels/691052431525675048/1264881140007702558/1291506402832945273). I would personally like cameras to work a bit differently and be split into a few more components, to avoid some footguns and confusing semantics, but that is more controversial, and shouldn't block this core migration. ## Testing I ran a few 2D and 3D examples, and tried cameras with and without render graphs. --- ## Migration Guide `Camera2dBundle` and `Camera3dBundle` have been deprecated in favor of `Camera2d` and `Camera3d`. Inserting them will now also insert the other components required by them automatically. |
||
![]() |
429987ebf8
|
Curve-based animation (#15434)
# Objective This PR extends and reworks the material from #15282 by allowing arbitrary curves to be used by the animation system to animate arbitrary properties. The goals of this work are to: - Allow far greater flexibility in how animations are allowed to be defined in order to be used with `bevy_animation`. - Delegate responsibility over keyframe interpolation to `bevy_math` and the `Curve` libraries and reduce reliance on keyframes in animation definitions generally. - Move away from allowing the glTF spec to completely define animations on a mechanical level. ## Solution ### Overview At a high level, curves have been incorporated into the animation system using the `AnimationCurve` trait (closely related to what was `Keyframes`). From the top down: 1. In `animate_targets`, animations are driven by `VariableCurve`, which is now a thin wrapper around a `Box<dyn AnimationCurve>`. 2. `AnimationCurve` is something built out of a `Curve`, and it tells the animation system how to use the curve's output to actually mutate component properties. The trait looks like this: ```rust /// A low-level trait that provides control over how curves are actually applied to entities /// by the animation system. /// /// Typically, this will not need to be implemented manually, since it is automatically /// implemented by [`AnimatableCurve`] and other curves used by the animation system /// (e.g. those that animate parts of transforms or morph weights). However, this can be /// implemented manually when `AnimatableCurve` is not sufficiently expressive. /// /// In many respects, this behaves like a type-erased form of [`Curve`], where the output /// type of the curve is remembered only in the components that are mutated in the /// implementation of [`apply`]. /// /// [`apply`]: AnimationCurve::apply pub trait AnimationCurve: Reflect + Debug + Send + Sync { /// Returns a boxed clone of this value. fn clone_value(&self) -> Box<dyn AnimationCurve>; /// The range of times for which this animation is defined. fn domain(&self) -> Interval; /// Write the value of sampling this curve at time `t` into `transform` or `entity`, /// as appropriate, interpolating between the existing value and the sampled value /// using the given `weight`. fn apply<'a>( &self, t: f32, transform: Option<Mut<'a, Transform>>, entity: EntityMutExcept<'a, (Transform, AnimationPlayer, Handle<AnimationGraph>)>, weight: f32, ) -> Result<(), AnimationEvaluationError>; } ``` 3. The conversion process from a `Curve` to an `AnimationCurve` involves using wrappers which communicate the intent to animate a particular property. For example, here is `TranslationCurve`, which wraps a `Curve<Vec3>` and uses it to animate `Transform::translation`: ```rust /// This type allows a curve valued in `Vec3` to become an [`AnimationCurve`] that animates /// the translation component of a transform. pub struct TranslationCurve<C>(pub C); ``` ### Animatable Properties The `AnimatableProperty` trait survives in the transition, and it can be used to allow curves to animate arbitrary component properties. The updated documentation for `AnimatableProperty` explains this process: <details> <summary>Expand AnimatableProperty example</summary An `AnimatableProperty` is a value on a component that Bevy can animate. You can implement this trait on a unit struct in order to support animating custom components other than transforms and morph weights. Use that type in conjunction with `AnimatableCurve` (and perhaps `AnimatableKeyframeCurve` to define the animation itself). For example, in order to animate font size of a text section from 24 pt. to 80 pt., you might use: ```rust #[derive(Reflect)] struct FontSizeProperty; impl AnimatableProperty for FontSizeProperty { type Component = Text; type Property = f32; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { Some(&mut component.sections.get_mut(0)?.style.font_size) } } ``` You can then create an `AnimationClip` to animate this property like so: ```rust let mut animation_clip = AnimationClip::default(); animation_clip.add_curve_to_target( animation_target_id, AnimatableKeyframeCurve::new( [ (0.0, 24.0), (1.0, 80.0), ] ) .map(AnimatableCurve::<FontSizeProperty, _>::from_curve) .expect("Failed to create font size curve") ); ``` Here, the use of `AnimatableKeyframeCurve` creates a curve out of the given keyframe time-value pairs, using the `Animatable` implementation of `f32` to interpolate between them. The invocation of `AnimatableCurve::from_curve` with `FontSizeProperty` indicates that the `f32` output from that curve is to be used to animate the font size of a `Text` component (as configured above). </details> ### glTF Loading glTF animations are now loaded into `Curve` types of various kinds, depending on what is being animated and what interpolation mode is being used. Those types get wrapped into and converted into `Box<dyn AnimationCurve>` and shoved inside of a `VariableCurve` just like everybody else. ### Morph Weights There is an `IterableCurve` abstraction which allows sampling these from a contiguous buffer without allocating. Its only reason for existing is that Rust disallows you from naming function types, otherwise we would just use `Curve` with an iterator output type. (The iterator involves `Map`, and the name of the function type would have to be able to be named, but it is not.) A `WeightsCurve` adaptor turns an `IterableCurve` into an `AnimationCurve`, so it behaves like everything else in that regard. ## Testing Tested by running existing animation examples. Interpolation logic has had additional tests added within the `Curve` API to replace the tests in `bevy_animation`. Some kinds of out-of-bounds errors have become impossible. Performance testing on `many_foxes` (`animate_targets`) suggests that performance is very similar to the existing implementation. Here are a couple trace histograms across different runs (yellow is this branch, red is main). <img width="669" alt="Screenshot 2024-09-27 at 9 41 50 AM" src="https://github.com/user-attachments/assets/5ba4e9ac-3aea-452e-aaf8-1492acc2d7fc"> <img width="673" alt="Screenshot 2024-09-27 at 9 45 18 AM" src="https://github.com/user-attachments/assets/8982538b-04cf-46b5-97b2-164c6bc8162e"> --- ## Migration Guide Most user code that does not directly deal with `AnimationClip` and `VariableCurve` will not need to be changed. On the other hand, `VariableCurve` has been completely overhauled. If you were previously defining animation curves in code using keyframes, you will need to migrate that code to use curve constructors instead. For example, a rotation animation defined using keyframes and added to an animation clip like this: ```rust animation_clip.add_curve_to_target( animation_target_id, VariableCurve { keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframes::Rotation(vec![ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ]), interpolation: Interpolation::Linear, }, ); ``` would now be added like this: ```rust animation_clip.add_curve_to_target( animation_target_id, AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([ Quat::IDENTITY, Quat::from_axis_angle(Vec3::Y, PI / 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.), Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.), Quat::IDENTITY, ])) .map(RotationCurve) .expect("Failed to build rotation curve"), ); ``` Note that the interface of `AnimationClip::add_curve_to_target` has also changed (as this example shows, if subtly), and now takes its curve input as an `impl AnimationCurve`. If you need to add a `VariableCurve` directly, a new method `add_variable_curve_to_target` accommodates that (and serves as a one-to-one migration in this regard). ### For reviewers The diff is pretty big, and the structure of some of the changes might not be super-obvious: - `keyframes.rs` became `animation_curves.rs`, and `AnimationCurve` is based heavily on `Keyframes`, with the adaptors also largely following suite. - The Curve API adaptor structs were moved from `bevy_math::curve::mod` into their own module `adaptors`. There are no functional changes to how these adaptors work; this is just to make room for the specialized reflection implementations since `mod.rs` was getting kind of cramped. - The new module `gltf_curves` holds the additional curve constructions that are needed by the glTF loader. Note that the loader uses a mix of these and off-the-shelf `bevy_math` curve stuff. - `animatable.rs` no longer holds logic related to keyframe interpolation, which is now delegated to the existing abstractions in `bevy_math::curve::cores`. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com> |
||
![]() |
8154164f1b
|
Allow animation clips to animate arbitrary properties. (#15282)
Currently, Bevy restricts animation clips to animating `Transform::translation`, `Transform::rotation`, `Transform::scale`, or `MorphWeights`, which correspond to the properties that glTF can animate. This is insufficient for many use cases such as animating UI, as the UI layout systems expect to have exclusive control over UI elements' `Transform`s and therefore the `Style` properties must be animated instead. This commit fixes this, allowing for `AnimationClip`s to animate arbitrary properties. The `Keyframes` structure has been turned into a low-level trait that can be implemented to achieve arbitrary animation behavior. Along with `Keyframes`, this patch adds a higher-level trait, `AnimatableProperty`, that simplifies the task of animating single interpolable properties. Built-in `Keyframes` implementations exist for translation, rotation, scale, and morph weights. For the most part, you can migrate by simply changing your code from `Keyframes::Translation(...)` to `TranslationKeyframes(...)`, and likewise for rotation, scale, and morph weights. An example `AnimatableProperty` implementation for the font size of a text section follows: #[derive(Reflect)] struct FontSizeProperty; impl AnimatableProperty for FontSizeProperty { type Component = Text; type Property = f32; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { Some(&mut component.sections.get_mut(0)?.style.font_size) } } In order to keep this patch relatively small, this patch doesn't include an implementation of `AnimatableProperty` on top of the reflection system. That can be a follow-up. This patch builds on top of the new `EntityMutExcept<>` type in order to widen the `AnimationTarget` query to include write access to all components. Because `EntityMutExcept<>` has some performance overhead over an explicit query, we continue to explicitly query `Transform` in order to avoid regressing the performance of skeletal animation, such as the `many_foxes` benchmark. I've measured the performance of that benchmark and have found no significant regressions. A new example, `animated_ui`, has been added. This example shows how to use Bevy's built-in animation infrastructure to animate font size and color, which wasn't possible before this patch. ## Showcase https://github.com/user-attachments/assets/1fa73492-a9ce-405a-a8f2-4aacd7f6dc97 ## Migration Guide * Animation keyframes are now an extensible trait, not an enum. Replace `Keyframes::Translation(...)`, `Keyframes::Scale(...)`, `Keyframes::Rotation(...)`, and `Keyframes::Weights(...)` with `Box::new(TranslationKeyframes(...))`, `Box::new(ScaleKeyframes(...))`, `Box::new(RotationKeyframes(...))`, and `Box::new(MorphWeightsKeyframes(...))` respectively. |