1use bevy::{
4 animation::{
5 animated_field, AnimatedBy, AnimationEntityMut, AnimationEvaluationError, AnimationTargetId,
6 },
7 prelude::*,
8};
9
10use core::any::TypeId;
11use core::f32::consts::TAU;
12
13struct AnimationInfo {
15 target_name: Name,
17 target_id: AnimationTargetId,
19 graph: Handle<AnimationGraph>,
21 node_index: AnimationNodeIndex,
23}
24
25fn main() {
27 App::new()
28 .add_plugins(DefaultPlugins)
29 .add_systems(Startup, setup)
32 .run();
33}
34
35impl AnimationInfo {
36 fn create(
38 animation_graphs: &mut Assets<AnimationGraph>,
39 animation_clips: &mut Assets<AnimationClip>,
40 ) -> AnimationInfo {
41 let animation_target_name = Name::new("Text");
43 let animation_target_id = AnimationTargetId::from_name(&animation_target_name);
44
45 let mut animation_clip = AnimationClip::default();
47
48 animation_clip.add_curve_to_target(
50 animation_target_id,
51 AnimatableCurve::new(
52 animated_field!(UiTransform::scale),
53 AnimatableKeyframeCurve::new(
54 [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
55 .into_iter()
56 .zip([0.3, 1.0, 0.3, 1.0, 0.3, 1.0, 0.3].map(Vec2::splat)),
57 )
58 .expect(
59 "should be able to build translation curve because we pass in valid samples",
60 ),
61 ),
62 );
63
64 animation_clip.add_curve_to_target(
70 animation_target_id,
71 AnimatableCurve::new(
72 TextColorProperty,
73 AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
74 Srgba::RED,
75 Srgba::GREEN,
76 Srgba::BLUE,
77 Srgba::RED,
78 ]))
79 .expect(
80 "should be able to build translation curve because we pass in valid samples",
81 ),
82 ),
83 );
84
85 animation_clip.add_curve_to_target(
91 animation_target_id,
92 AnimatableCurve::new(
93 animated_field!(UiTransform::rotation),
94 AnimatableKeyframeCurve::new(
95 [0.0, 1.0, 2.0, 3.0]
96 .into_iter()
97 .zip([0., TAU / 3., TAU / 1.5, TAU].map(Rot2::radians)),
98 )
99 .expect("should be able to build rotation curve because we pass in valid samples"),
100 ),
101 );
102
103 let animation_clip_handle = animation_clips.add(animation_clip);
105
106 let (animation_graph, animation_node_index) =
108 AnimationGraph::from_clip(animation_clip_handle);
109 let animation_graph_handle = animation_graphs.add(animation_graph);
110
111 AnimationInfo {
112 target_name: animation_target_name,
113 target_id: animation_target_id,
114 graph: animation_graph_handle,
115 node_index: animation_node_index,
116 }
117 }
118}
119
120fn setup(
122 mut commands: Commands,
123 asset_server: Res<AssetServer>,
124 mut animation_graphs: ResMut<Assets<AnimationGraph>>,
125 mut animation_clips: ResMut<Assets<AnimationClip>>,
126) {
127 let AnimationInfo {
129 target_name: animation_target_name,
130 target_id: animation_target_id,
131 graph: animation_graph,
132 node_index: animation_node_index,
133 } = AnimationInfo::create(animation_graphs.as_mut(), animation_clips.as_mut());
134
135 let mut animation_player = AnimationPlayer::default();
137 animation_player.play(animation_node_index).repeat();
138
139 commands.spawn(Camera2d);
141
142 let mut entity = commands.spawn((
146 Node {
148 position_type: PositionType::Absolute,
149 top: px(0),
150 left: px(0),
151 right: px(0),
152 bottom: px(0),
153 justify_content: JustifyContent::Center,
154 align_items: AlignItems::Center,
155 ..default()
156 },
157 animation_player,
158 AnimationGraphHandle(animation_graph),
159 ));
160
161 let player = entity.id();
162 entity.with_child((
163 Text::new("Bevy"),
164 TextFont {
165 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
166 font_size: FontSize::Px(80.),
167 ..default()
168 },
169 TextColor(Color::Srgba(Srgba::RED)),
170 TextLayout::justify(Justify::Center),
171 animation_target_id,
172 AnimatedBy(player),
173 animation_target_name,
174 ));
175}
176
177#[derive(Clone)]
181struct TextColorProperty;
182
183impl AnimatableProperty for TextColorProperty {
184 type Property = Srgba;
185
186 fn evaluator_id(&self) -> EvaluatorId<'_> {
187 EvaluatorId::Type(TypeId::of::<Self>())
188 }
189
190 fn get_mut<'a>(
191 &self,
192 entity: &'a mut AnimationEntityMut,
193 ) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
194 let text_color = entity
195 .get_mut::<TextColor>()
196 .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
197 TextColor,
198 >(
199 )))?
200 .into_inner();
201 match text_color.0 {
202 Color::Srgba(ref mut color) => Ok(color),
203 _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
204 Srgba,
205 >(
206 ))),
207 }
208 }
209}