forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new example: sprite animation in response to an event (bevyengine#12996)
# Objective - animating a sprite in response to an event is a [common beginner problem](https://www.reddit.com/r/bevy/comments/13xx4v7/sprite_animation_in_bevy/) ## Solution - provide a simple example to show how to animate a sprite in response to an event --------- Co-authored-by: François Mockers <[email protected]>
- Loading branch information
Showing
5 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
//! Animates a sprite in response to a keyboard event. | ||
//! | ||
//! See `sprite_sheet.rs` for an example where the sprite animation loops indefinitely. | ||
use std::time::Duration; | ||
|
||
use bevy::input::common_conditions::input_just_pressed; | ||
use bevy::prelude::*; | ||
|
||
fn main() { | ||
App::new() | ||
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites | ||
.add_systems(Startup, setup) | ||
.add_systems(Update, execute_animations) | ||
.add_systems( | ||
Update, | ||
( | ||
// press the right arrow key to animate the right sprite | ||
trigger_animation::<RightSprite>.run_if(input_just_pressed(KeyCode::ArrowRight)), | ||
// press the left arrow key to animate the left sprite | ||
trigger_animation::<LeftSprite>.run_if(input_just_pressed(KeyCode::ArrowLeft)), | ||
), | ||
) | ||
.run(); | ||
} | ||
|
||
// This system runs when the user clicks the left arrow key or right arrow key | ||
fn trigger_animation<S: Component>(mut query: Query<&mut AnimationConfig, With<S>>) { | ||
// we expect the Component of type S to be used as a marker Component by only a single entity | ||
let mut animation = query.single_mut(); | ||
// we create a new timer when the animation is triggered | ||
animation.frame_timer = AnimationConfig::timer_from_fps(animation.fps); | ||
} | ||
|
||
#[derive(Component)] | ||
struct AnimationConfig { | ||
first_sprite_index: usize, | ||
last_sprite_index: usize, | ||
fps: u8, | ||
frame_timer: Timer, | ||
} | ||
|
||
impl AnimationConfig { | ||
fn new(first: usize, last: usize, fps: u8) -> Self { | ||
Self { | ||
first_sprite_index: first, | ||
last_sprite_index: last, | ||
fps, | ||
frame_timer: Self::timer_from_fps(fps), | ||
} | ||
} | ||
|
||
fn timer_from_fps(fps: u8) -> Timer { | ||
Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once) | ||
} | ||
} | ||
|
||
// This system loops through all the sprites in the `TextureAtlas`, from `first_sprite_index` to | ||
// `last_sprite_index` (both defined in `AnimationConfig`). | ||
fn execute_animations( | ||
time: Res<Time>, | ||
mut query: Query<(&mut AnimationConfig, &mut TextureAtlas)>, | ||
) { | ||
for (mut config, mut atlas) in &mut query { | ||
// we track how long the current sprite has been displayed for | ||
config.frame_timer.tick(time.delta()); | ||
|
||
// If it has been displayed for the user-defined amount of time (fps)... | ||
if config.frame_timer.just_finished() { | ||
if atlas.index == config.last_sprite_index { | ||
// ...and it IS the last frame, then we move back to the first frame and stop. | ||
atlas.index = config.first_sprite_index; | ||
} else { | ||
// ...and it is NOT the last frame, then we move to the next frame... | ||
atlas.index += 1; | ||
// ...and reset the frame timer to start counting all over again | ||
config.frame_timer = AnimationConfig::timer_from_fps(config.fps); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Component)] | ||
struct LeftSprite; | ||
|
||
#[derive(Component)] | ||
struct RightSprite; | ||
|
||
fn setup( | ||
mut commands: Commands, | ||
asset_server: Res<AssetServer>, | ||
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>, | ||
) { | ||
commands.spawn(Camera2dBundle::default()); | ||
|
||
// load the sprite sheet using the `AssetServer` | ||
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); | ||
|
||
// the sprite sheet has 7 sprites arranged in a row, and they are all 24px x 24px | ||
let layout = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None); | ||
let texture_atlas_layout = texture_atlas_layouts.add(layout); | ||
|
||
// the first (left-hand) sprite runs at 10 FPS | ||
let animation_config_1 = AnimationConfig::new(1, 6, 10); | ||
|
||
// create the first (left-hand) sprite | ||
commands.spawn(( | ||
SpriteBundle { | ||
transform: Transform::from_scale(Vec3::splat(6.0)) | ||
.with_translation(Vec3::new(-50.0, 0.0, 0.0)), | ||
texture: texture.clone(), | ||
..default() | ||
}, | ||
TextureAtlas { | ||
layout: texture_atlas_layout.clone(), | ||
index: animation_config_1.first_sprite_index, | ||
}, | ||
LeftSprite, | ||
animation_config_1, | ||
)); | ||
|
||
// the second (right-hand) sprite runs at 20 FPS | ||
let animation_config_2 = AnimationConfig::new(1, 6, 20); | ||
|
||
// create the second (right-hand) sprite | ||
commands.spawn(( | ||
SpriteBundle { | ||
transform: Transform::from_scale(Vec3::splat(6.0)) | ||
.with_translation(Vec3::new(50.0, 0.0, 0.0)), | ||
texture: texture.clone(), | ||
..default() | ||
}, | ||
TextureAtlas { | ||
layout: texture_atlas_layout.clone(), | ||
index: animation_config_2.first_sprite_index, | ||
}, | ||
RightSprite, | ||
animation_config_2, | ||
)); | ||
|
||
// create a minimal UI explaining how to interact with the example | ||
commands.spawn(TextBundle { | ||
text: Text::from_section( | ||
"Left Arrow Key: Animate Left Sprite\nRight Arrow Key: Animate Right Sprite", | ||
TextStyle { | ||
font_size: 20.0, | ||
..default() | ||
}, | ||
), | ||
style: Style { | ||
position_type: PositionType::Absolute, | ||
top: Val::Px(12.0), | ||
left: Val::Px(12.0), | ||
..default() | ||
}, | ||
..default() | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters