Skip to content

Commit

Permalink
Support basic spritesheet animations
Browse files Browse the repository at this point in the history
  • Loading branch information
benfrankel committed Jul 21, 2024
1 parent 02a1cb9 commit de58fd8
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 28 deletions.
8 changes: 7 additions & 1 deletion assets/config/actor.ron
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
(
actors: {
"lucy": Actor(
name: "Lucy",
display_name: "Lucy",
texture_path: "image/lucy.png",
texture_atlas_grid: TextureAtlasGrid(
tile_size: UVec2(8, 8),
columns: 5,
rows: 1,
),
sprite_animation: SpriteAnimation(
frames: [
SpriteAnimationFrame(index: 2, steps: 2),
SpriteAnimationFrame(index: 3, steps: 2),
]
)
),
},
player: "lucy",
Expand Down
5 changes: 3 additions & 2 deletions src/game.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Game mechanics and content

pub mod actor;
mod step;
pub mod sprite;
pub mod step;

use bevy::prelude::*;

Expand All @@ -10,7 +11,7 @@ use crate::util::prelude::*;
pub(super) fn plugin(app: &mut App) {
app.configure::<GameRoot>();

app.add_plugins((actor::plugin, step::plugin));
app.add_plugins((actor::plugin, sprite::plugin, step::plugin));
}

#[derive(Resource, Reflect)]
Expand Down
14 changes: 10 additions & 4 deletions src/game/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bevy::utils::HashMap;
use serde::Deserialize;
use serde::Serialize;

use crate::game::sprite::SpriteAnimation;
use crate::util::prelude::*;

pub(super) fn plugin(app: &mut App) {
Expand All @@ -30,6 +31,7 @@ impl Config for ActorConfig {
for actor in self.actors.values_mut() {
actor.texture = asset_server.load(&actor.texture_path);
actor.texture_atlas_layout = layouts.add(&actor.texture_atlas_grid);
actor.sprite_animation.calculate_total_steps();
}
}

Expand All @@ -42,13 +44,16 @@ impl Config for ActorConfig {

#[derive(Reflect, Serialize, Deserialize)]
pub struct Actor {
pub name: String,
pub display_name: String,

pub texture_path: String,
#[serde(skip)]
pub texture: Handle<Image>,
pub texture_atlas_grid: TextureAtlasGrid,
#[serde(skip)]
pub texture_atlas_layout: Handle<TextureAtlasLayout>,
// TODO: Multiple animations per actor: HashMap<String, SpriteAnimation>?
pub sprite_animation: SpriteAnimation,
}

fn actor_helper(mut entity: EntityWorldMut, key: Option<String>) {
Expand All @@ -60,15 +65,16 @@ fn actor_helper(mut entity: EntityWorldMut, key: Option<String>) {
let actor = r!(config.actors.get(key.as_ref().unwrap_or(&config.player)));

entity.insert((
Name::new(actor.name.clone()),
Name::new(actor.display_name.clone()),
SpriteBundle {
texture: actor.texture.clone_weak(),
texture: actor.texture.clone(),
..default()
},
TextureAtlas {
layout: actor.texture_atlas_layout.clone_weak(),
layout: actor.texture_atlas_layout.clone(),
index: 0,
},
actor.sprite_animation.clone(),
));
}

Expand Down
67 changes: 58 additions & 9 deletions src/game/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,66 @@
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;
use serde::Deserialize;
use serde::Serialize;

use crate::core::UpdateSet;
use crate::game::step::on_step;
use crate::game::step::Step;
use crate::util::prelude::*;

pub(super) fn plugin(app: &mut App) {}
pub(super) fn plugin(app: &mut App) {
app.configure::<SpriteAnimation>();
}

#[derive(Reflect, Serialize, Deserialize, Copy, Clone)]
pub struct SpriteAnimationFrame {
pub index: usize,
pub steps: usize,
}

#[derive(Component, Reflect, Serialize, Deserialize, Clone)]
#[reflect(Component)]
pub struct SpriteAnimation {
pub frames: Vec<SpriteAnimationFrame>,
#[serde(skip)]
pub total_steps: usize,
}

impl Configure for SpriteAnimation {
fn configure(app: &mut App) {
app.register_type::<Self>();
app.add_systems(
Update,
update_sprite_animation
.in_set(UpdateSet::Update)
.run_if(on_step(1)),
);
}
}

impl SpriteAnimation {
// TODO: Does serde support syncing a value after deserialization?
pub fn calculate_total_steps(&mut self) {
self.total_steps = self.frames.iter().map(|x| x.steps).sum();
}

/// Calculate the texture atlas index of the animation after `steps` steps.
fn index(&self, steps: usize) -> usize {
let mut steps = steps % self.total_steps;
let mut i = 0;
while steps >= self.frames[i].steps {
steps -= self.frames[i].steps;
i += 1;
}

struct SpriteAnimationFrame {
index: usize,
steps: usize,
self.frames[i].index
}
}

struct SpriteAnimation {
texture: Handle<Image>,
layout: Handle<TextureAtlasLayout>,
frames: Vec<SpriteAnimationFrame>,
fn update_sprite_animation(
step: Res<Step>,
mut anim_query: Query<(&SpriteAnimation, &mut TextureAtlas)>,
) {
for (anim, mut atlas) in &mut anim_query {
atlas.index = anim.index(step.total);
}
}
22 changes: 10 additions & 12 deletions src/game/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,29 @@ fn tick_step_timer(time: Res<Time>, mut step_timer: ResMut<StepTimer>) {

#[derive(Resource, Reflect, Default)]
#[reflect(Resource)]
pub struct Step(pub usize);
pub struct Step {
pub total: usize,
pub this_tick: usize,
}

impl Configure for Step {
fn configure(app: &mut App) {
app.register_type::<Self>();
app.init_resource::<Self>();
app.add_systems(Update, update_step.in_set(UpdateSet::SyncEarly));
app.add_systems(
Update,
(|step: Res<Step>| println!("Step: {}", step.0))
.in_set(UpdateSet::Update)
.run_if(on_step(2)),
);
}
}

fn update_step(step_timer: Res<StepTimer>, mut step: ResMut<Step>) {
step.0 += step_timer.0.times_finished_this_tick() as usize;
step.this_tick = step_timer.0.times_finished_this_tick() as usize;
step.total += step.this_tick;
}

/// A run condition to run a system every `n` steps.
pub fn on_step(n: usize) -> impl Fn(Res<StepTimer>, Res<Step>) -> bool {
move |step_timer, step| {
let hi = step.0;
let lo = hi - step_timer.0.times_finished_this_tick() as usize;
pub fn on_step(n: usize) -> impl Fn(Res<Step>) -> bool {
move |step| {
let hi = step.total;
let lo = hi - step.this_tick;
hi / n > lo / n
}
}

0 comments on commit de58fd8

Please sign in to comment.