Motivation
I’ve been using Bevy for a personal project and it’s been delightful. As the project has grown, I’ve turned into a level designer, animator, illustrator, along with the usual programmer.
Sometimes I hit walls, and rabbit holes around those walls are just too compelling to pass up. One such rabbit hole was figuring out sprite animation loops from a single sprite sheet. The only reason for using a single sprite-sheet is that being the dev and the illustrator is very time consuming and I wanted my workflow to remain as simple as possible.
Creating Animation Pages
Implementation
Defining animation variants in an enum
[!NOTE] Variant and Pages
I refer to the animation sections as variants, and the offset/size tuple as a page
#[derive(Clone, PartialEq)]
pub enum PlayerAnimationVariant {
Idle,
Rising,
Falling,
}
Creating an AnimationLoop
trait
The trait is straightforward, defines a function that would return the offset and size of the animation page.
pub trait AnimationLoop {
fn page(&self) -> (usize, usize);
}
Implementing the AnimationLoop
trait for the enum
This is also straightforward, we just have to match the enum variant to the appropriate animation page tuple i.e. frame offset and page size.
impl AnimationLoop for PlayerAnimationVariant {
fn page(&self) -> (usize, usize) {
match self {
// (idx_offset, loop_size) describe the animation loop
PlayerAnimationVariant::Idle => (0, 3),
PlayerAnimationVariant::Rising => (2, 2),
PlayerAnimationVariant::Falling => (4, 4),
}
}
}
Frame and Page transitions
Implementation
Implementing an animation state manager
The struct stores the animation variant to play, and the current index of the frame.
pub struct PlayerAnimationState {
pub variant: PlayerAnimationVariant,
pub idx: usize,
}
Implementing transition functions
wrapping_next_idx
increments the idx
and wraps around at the page boundary
impl PlayerAnimationState {
fn wrapping_next_idx(&mut self) -> usize {
let current_idx = self.idx;
let (offset, size) = self.variant.page();
self.idx = offset + (current_idx + 1) % size;
self.idx
}
}
transition_variant
updates the animation state manager to play the variant passed as an argument
impl PlayerAnimationState {
...
fn transition_variant(&mut self, to: PlayerAnimationVariant) {
let (offset, _) = to.page();
self.variant = to;
self.idx = offset;
}
}
This can be somewhat tedious, so I created a Rust crate that I might publish after doing some cleanup. It simplifies the process by providing:
- the traits
AnimationLoop
andAnimationTransition<T: AnimationLoop>
- a handy macro
AnimationTransitionMacro
that implements the necessary index manipulation functions for the animation state manager