Live Demo

Repository

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.

Sprite sheet

Animated

Creating Animation Pages

Defining animation loop in an array

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

looping

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 and AnimationTransition<T: AnimationLoop>
  • a handy macro AnimationTransitionMacro that implements the necessary index manipulation functions for the animation state manager