use std::time::Duration; use bevy::{color::palettes::css::*, prelude::*}; use bevy_tweening::{lens::*, *}; mod utils; fn main() { App::default() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Sequence".to_string(), resolution: bevy::window::WindowResolution::new(600, 600), present_mode: bevy::window::PresentMode::Fifo, // vsync ..default() }), ..default() })) .add_systems(Update, utils::close_on_esc) .add_plugins(TweeningPlugin) .add_systems(Startup, setup) .add_systems(Update, update_text) .run(); } #[derive(Component)] struct RedProgress; #[derive(Component)] struct BlueProgress; #[derive(Component)] struct RedAnimMarker; #[derive(Component)] struct BlueAnimMarker; #[derive(Component)] struct ProgressValue; fn setup(mut commands: Commands, asset_server: Res) -> Result<()> { commands.spawn(Camera2d::default()); let font = asset_server.load("fonts/FiraMono-Regular.ttf"); let text_font = TextFont { font: font.into(), font_size: 50.0.into(), ..default() }; let text_color_red = TextColor(RED.into()); let text_color_blue = TextColor(AQUA.into()); let justify = Justify::Center; // Text with the index of the active tween in the sequence commands .spawn(( Text2d::default(), TextLayout { justify, ..default() }, Transform::from_translation(Vec3::new(0., 40., 0.)), RedProgress, )) .with_children(|children| { children.spawn(( TextSpan::new("progress: "), text_font.clone(), text_color_red.clone(), )); children.spawn(( TextSpan::new("0%"), text_font.clone(), text_color_red.clone(), ProgressValue, )); }); // Text with progress of the active tween in the sequence commands .spawn(( Text2d::default(), TextLayout { justify, ..default() }, Transform::from_translation(Vec3::new(0., -40., 0.)), BlueProgress, )) .with_children(|children| { children.spawn(( TextSpan::new("progress: "), text_font.clone(), text_color_blue.clone(), )); children.spawn(( TextSpan::new("0%"), text_font.clone(), text_color_blue.clone(), ProgressValue, )); }); let size = 25.; let margin = 40.; let screen_x = 600.; let screen_y = 600.; let center = Vec3::new(screen_x / 2., screen_y / 2., 0.); // Run around the window from corner to corner let dests = &[ Vec3::new(margin, margin, 0.), Vec3::new(screen_x - margin, margin, 0.), Vec3::new(screen_x - margin, screen_y - margin, 0.), Vec3::new(margin, screen_y - margin, 0.), Vec3::new(margin, margin, 0.), ]; // Red sprite { let entity = commands .spawn(Sprite { color: RED.into(), custom_size: Some(Vec2::new(size, size)), ..default() }) // Insert a rotation animation via the commands extension, to rotate over self, forever. // This will continue even after the move along path animation added below finished. .rotate_z(Duration::from_secs(2)) .id(); // Build a sequence from an iterator over a Tweenable let anim_move_along_path = Sequence::new(dests.windows(2).map(|pair| { Tween::new( EaseFunction::QuadraticInOut, Duration::from_secs(1), TransformPositionLens { start: pair[0] - center, end: pair[1] - center, }, ) })); commands.spawn(( RedAnimMarker, TweenAnim::new(anim_move_along_path), AnimTarget::component::(entity), )); } // Blue sprite { let entity = commands .spawn(Sprite { color: AQUA.into(), custom_size: Some(Vec2::new(size * 3., size)), ..default() }) .id(); // First move from left to right, then rotate around self 180 degrees while // scaling size at the same time. // In previous versions of bevy_tweening, this could be accomplished with a // Tracks, which allowed to run in parallel animations of different duration. // That interface was confusing and had too many corner cases, so was removed. // // Instead, we have 2 solutions: // 1. Insert one sequence which moves then rotates, and another which waits // (Delay tweenable) then starts to scale at the same time the first sequence // starts to rotate. In most cases this is the simplest, but requires // controlling the timings of the animations. // 2. Insert a single sequence which moves then {rotates+scales}, using a custom // Lens which can apply both the rotation and scale with a single Tween. This // guarantees perfect timing alignment, and doesn't require knowing the // duration of the first (move) animation. A minor drawback is that we have // to write a custom Lens. // // Here we show how option 1. is implemented, which is often the simplest. let move_duration = Duration::from_secs(1); let tween_move = Tween::new( EaseFunction::QuadraticInOut, move_duration, TransformPositionLens { start: Vec3::new(-200., 100., 0.), end: Vec3::new(200., 100., 0.), }, ); let tween_delay = Delay::new(move_duration); let tween_rotate = Tween::new( EaseFunction::QuadraticInOut, Duration::from_secs(1), TransformRotationLens { start: Quat::IDENTITY, end: Quat::from_rotation_z(180_f32.to_radians()), }, ); let tween_scale = Tween::new( EaseFunction::QuadraticInOut, Duration::from_secs(1), TransformScaleLens { start: Vec3::ONE, end: Vec3::splat(2.0), }, ); // Build a sequence from an heterogeneous list of tweenables by casting them // manually to a BoxedTweenable. This is only to demonstrate how it's done; in // general prefer using then() as below. let seq1 = Sequence::new([Box::new(tween_move) as BoxedTweenable, tween_rotate.into()]); let seq2 = tween_delay.then(tween_scale); // Because we want to monitor the progress of the animations, we need to fetch // their Entity. This requires inserting them manually in the TweenAnimator // resource, instead of using the extensions of EntityCommands. commands.spawn(( BlueAnimMarker, TweenAnim::new(seq1), AnimTarget::component::(entity), )); commands.spawn(( TweenAnim::new(seq2), AnimTarget::component::(entity), )); } Ok(()) } fn update_text( red_text_children: Single<&Children, With>, blue_text_children: Single<&Children, With>, mut q_textspans: Query<&mut TextSpan, With>, q_anim_red: Single, With>, q_anim_blue: Single, With>, mut q_event_completed: MessageReader, ) { let anim_red = *q_anim_red; let progress_red = if let Some(anim) = anim_red { anim.tweenable().cycle_fraction() } else { 1. }; let anim_blue = *q_anim_blue; let progress_blue = if let Some(anim) = anim_blue { anim.tweenable().cycle_fraction() } else { 1. }; let mut red_text = q_textspans.get_mut(red_text_children[1]).unwrap(); red_text.0 = format!("{:5.1}%", progress_red * 100.); let mut blue_text = q_textspans.get_mut(blue_text_children[1]).unwrap(); blue_text.0 = format!("{:5.1}%", progress_blue * 100.); for ev in q_event_completed.read() { println!( "Event: AnimCompletedEvent anim_entity={:?} target={:?}", ev.anim_entity, ev.target ); } }