//! Create and play an animation defined by code that operates on the //! `Transform` component. use std::f32::consts::{FRAC_PI_2, PI}; use bevy::prelude::*; use keyframe_animate::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(KeyframeAnimationPlugin) .insert_resource(AmbientLight { color: Color::WHITE, brightness: 1.0, }) .add_startup_system(setup) .run(); } fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { // Camera commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); // The animation API uses the `Name` component to target entities let planet = Name::new("planet"); let orbit_controller = Name::new("orbit_controller"); let satellite = Name::new("satellite"); // Creating the animation let mut animation = KeyframeAnimationClip::default(); // A curve can modify a single part of a transform, here the translation animation.add_curve_to_path( KeyframeEntityPath { parts: vec![planet.clone()], }, KeyframeVariableCurve { options: Some(vec!["translation".to_string()]), keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframe::translation(vec![ Vec3::new(1.0, 0.0, 1.0), Vec3::new(-1.0, 0.0, 1.0), Vec3::new(-1.0, 0.0, -1.0), Vec3::new(1.0, 0.0, -1.0), // in case seamless looping is wanted, the last keyframe should // be the same as the first one Vec3::new(1.0, 0.0, 1.0), ]), }, ); // Or it can modify the rotation of the transform. // To find the entity to modify, the hierarchy will be traversed looking for // an entity with the right name at each level animation.add_curve_to_path( KeyframeEntityPath { parts: vec![planet.clone(), orbit_controller.clone()], }, KeyframeVariableCurve { options: Some(vec!["rotation".to_string()]), keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframe::rotation(vec![ Quat::from_axis_angle(Vec3::Y, 0.0), Quat::from_axis_angle(Vec3::Y, FRAC_PI_2), Quat::from_axis_angle(Vec3::Y, PI), Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2), Quat::from_axis_angle(Vec3::Y, 0.0), ]), }, ); // If a curve in an animation is shorter than the other, it will not repeat // until all other curves are finished. In that case, another animation should // be created for each part that would have a different duration / period animation.add_curve_to_path( KeyframeEntityPath { parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()], }, KeyframeVariableCurve { options: Some(vec!["scale".to_string()]), keyframe_timestamps: vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], keyframes: Keyframe::scale(vec![ Vec3::splat(0.8), Vec3::splat(1.2), Vec3::splat(0.8), Vec3::splat(1.2), Vec3::splat(0.8), Vec3::splat(1.2), Vec3::splat(0.8), Vec3::splat(1.2), Vec3::splat(0.8), ]), }, ); // There can be more than one curve targeting the same entity path animation.add_curve_to_path( KeyframeEntityPath { parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()], }, KeyframeVariableCurve { options: Some(vec!["rotation".to_string()]), keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0], keyframes: Keyframe::rotation(vec![ Quat::from_axis_angle(Vec3::Y, 0.0), Quat::from_axis_angle(Vec3::Y, FRAC_PI_2), Quat::from_axis_angle(Vec3::Y, PI), Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2), Quat::from_axis_angle(Vec3::Y, 0.0), ]), }, ); // Create the animation player, and set it to repeat let mut player = KeyframeAnimationPlayer::new(animation); player.repeat(); // Create the scene that will be animated // First entity is the planet commands .spawn_bundle(PbrBundle { mesh: meshes.add(Mesh::from(shape::Icosphere::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), ..default() }) // Add the Name component, and the animation player .insert_bundle((planet, player)) .with_children(|p| { // This entity is just used for animation, but doesn't display anything p.spawn_bundle(TransformBundle { ..default() }) // Add the Name component .insert(orbit_controller) .with_children(|p| { // The satellite, placed at a distance of the planet p.spawn_bundle(PbrBundle { transform: Transform::from_xyz(1.5, 0.0, 0.0), mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()), ..default() }) // Add the Name component .insert(satellite); }); }); }