August 12, 2020

Running Animation

The content covered here builds on the code explored in Creating a Simple Spritesheet Animation with Amethyst. This part will be extending the previous implementation for an “idle” sprite animation by adding a “running” animation in response to user input. In the previous post, we implemented an AnimationSystem responsible for telling the game what sprite image to draw to the screen during each iteration of the game loop.

Now we will be introducing another system called MovePlayerSystem, which will implement the logic for moving our sprite image on the screen based on user input.


Implementing the “running” animation involves the following tasks:

  1. Add texture data for “running” sprite frames to the RON file.
  2. Adding a new configuration file for capturing user input.
  3. Adding an ActionStatus component
  4. Extending AnimationSystem
  5. Implement MovePlayerSystem

1. Add new sprite frames to RON file

In Creating a Simple Spritesheet Animation with Amethyst, a configuration file for telling the Loader resource where to locate the sprite images on the texture was created. For the new animation, we will need to provide the necessary coordinate and size data for the sprite images involved.

// texture.ron
        // Sprite frame data for "running" animation.
            x: 192,
            y: 164,
            width: 16,
            height: 28,
            x: 208,
            y: 164,
            width: 16,
            height: 28,
            x: 224,
            y: 164,
            width: 16,
            height: 28,
            x: 240,
            y: 164,
            width: 16,
            height: 28,

For reference, the first sprite image for the “running” animation will be located at the fourth index in the configuration file’s sprites array. To see what the whole file should look like, see this commit.

2. Configuration for user input

A configuration file is needed to capture user with Amethyst. Like the configuration file containing sprite data, this file will be represented using RON. The contents of the file will look like this:

// bindings.ron

  axes: {
    "player_move_x": Emulated(pos: Key(Right), neg: Key(Left)),
    "player_move_y": Emulated(pos: Key(Up), neg: Key(Down)),
  actions: {},

This file allows us to create input mappings to named variables we can reference in our code. In particular, inputs are created to move the player along the horizontal axis, "player_move_x", and vertical-axis, "player_move_y".

Now that we the RON file containing our input settings, we should tell the game what inputs to capture based on the configuration provided in bindings.ron. This is done by creating an InputBundle and adding it to the game object’s data.


use amethyst::{
    input::{InputBundle, StringBindings},

let bindings_path = config_dir.join("bindings.ron");
let input_bundle = InputBundle::<StringBindings>::new()

let game_data = GameDataBuilder::default()
    // ...

For full context on registering the InputBundler to the game, see this commit.

The InputBundler also contains a InputHandler resource, which is responsible for mapping the configured inputs to their axes and capturing user input. The InputHandler will be read into MovePlayerSystem, which we will implement later.

3. Define the ActionStatus component

The game needs a way to differentiate between which sequence of sprite images the game should be drawing in response to user input. In Creating a Simple Spritesheet Animation with Amethyst, we implemented an “idle” animation that consisted of four images. The game should be drawing these four images sequentially when there is no user input. Now that the game can take user input, there should be a different sequence of sprite images drawn anytime user input is captured. This new sequence of sprite images was defined in the first task of this post. To determine whether the player’s sprite is “idle” or “running”, we can characterize between the two actions of the player’s sprite with values Idle or Run.

enum Action {

Next we can add a component called ActionStatus to the player object. This component describes the type of action the player is performing.

struct ActionStatus {
  action_type: Action,

impl ActionStatus {
  fn set_action_type(&mut self, action: Action) {
    self.action_type = action;

impl Component for ActionStatus {
  type Storage = DenseVecStorage<Self>;

ActionStatus also has a method called set_action_type, which is responsible for setting the action type of the player object in response to user input. This method will be used when we implement MovePlayerSystem. Another way to think about this component is to think of it as a place on the player object for MovePlayerSystem to write to, whereas the AnimationSystem will use ActionStatus as place to read from.

In the next task, AnimationSystem will be extended to read from an ActionStatus component to determine which sequence of sprite images it should be drawing from.

4. Extending AnimationSystem

Now that the game has a way to update the player’s action type, the AnimationSystem can be extended to read from the player’s ActionStatus component to update which sprite sequence should be drawn to the screen.

The first thing we need to do is update the SystemData type to contain the ReadStorage type for ActionStatus. This tells the implementation for AnimationSystem that the game should also read from the ActionStatus storage.

impl<'s> System<'s> for AnimationSystem {
  type SystemData = (
    ReadStorage<'s, Animation>,
    ReadStorage<'s, ActionStatus>,
    WriteStorage<'s, SpriteRender>,
    Read<'s, Time>,

The system is now reading from the component storage for ActionStatus. We can update AnimationSystem to read from the ActionStatus component associated with the player object.

for (animation, action_status, sprite) in (&animations, &action_statuses, &mut sprite_renders).join() {
      let elapsed_time = time.frame_number();
      let frame = (elapsed_time / animation.frame_duration) as i32 % animation.frames;

      // Read from action_status...

The use of join returns an iterator containing all the specific components for that entity, which is the player object in our case. action_status is an immutable referencee to the player object’s associated ActionStatus component, which we will use to match on its action_type property:

match action_status.action_type {
    Action::Idle => {
      sprite.sprite_number = animation.first_sprite_index + frame as usize;
    Action::Run => {
      // The first running animation is the fourth indice from the beginning of the
      // animation s.
      let starting_run_frame = animation.first_sprite_index + 4 as usize;
      sprite.sprite_number = starting_run_frame + frame as usize;
    _ => {},

The first matches on the Action::Idle enum value, which we defined in task three. The code here does not change from our initial implementation for the “idle” animation in Creating a Simple Spritesheet Animation with Amethyst. It will calculate which frame sequence to use starting from the first image’s index defined in the texture.ron file.

The second match on Action::Run will get the first sprite image for the player’s running animation sequence. Since the idle animation as four frames in its animation sequence, the starting image for the running animation will be at the fourth index. This index, also the frame number, will be used as the start position for cycling through the sequence of sprite images for the running animation.

Now that AnimationSystem knows how to draw a specific set of sprite images depending on the player’s action state using the ActionStatus component, we can implement the MovePlayerSystem to modify the action_type field in response to user input.

5. Implement MovePlayerSystem

There are number of things that will go into impementing a system moving the player based on user input. In the end, we want the MovePlayerSystem to modify the player object’s associated ActionStatus component’s action_type field.

The first thing to do is define data types the system will be operating on with SystemData.

pub struct MovePlayerSystem;

impl <'s> System<'s> for MovePlayerSystem {
  type SystemData = (
    WriteStorage<'s, ActionStatus>,
    WriteStorage<'s, Transform>,
    Read<'s, InputHandler<StringBindings>>,

In the system’s run method, iterate over all entites with an ActionStatus and Transform component added to it. This will give access to that entity’s associated components for the system to modify.

fn run(&mut self, (mut statuses, mut transforms, input): Self::SystemData) {
    for (status, transform) in (&mut statuses, &mut transforms).join() {
      // modify `status` and `transform` based on `input`...

For each entity, read from the InputHandler resource to capture any input values made by the user. If there is a value for either InputHandler axes then update the player’s associated Transform and ActionStatus components. Otherwise, just set the action_type value to Action::Idle.

let movement_x: f32 = input.axis_value("player_move_x").unwrap();
let movement_y: f32 = input.axis_value("player_move_y").unwrap();

if movement_x != 0.0 {
  let scaled_amount = 0.65 * movement_x;
  let x_pos = transform.translation().x;
  // Direction the player is facing. If west, then rotate 180.
  let rotation = if movement_x < 0.0 { 3.14 } else { 0.0 };

  // Modify the object's transform and status components.
    (x_pos + scaled_amount)
        .min(CAMERA_WIDTH - PLAYER_WIDTH * 0.5)
        .max(PLAYER_WIDTH * 0.5),
} else if movement_y != 0.0 {
  let scaled_amount = 0.65 * movement_y;
  let y_pos = transform.translation().y;

    (y_pos + scaled_amount)
        .min(CAMERA_HEIGHT - PLAYER_HEIGHT * 0.5)
        .max(PLAYER_HEIGHT * 0.5),
} else {

A few things to note is that these constants CAMERA_WIDTH/CAMERA_HEIGHT and PLAYER_WIDTH/PLAYER_HEIGHT make it so that the player’s sprite cannot move off the edge of the game’s window. This is a solution also demonstrated in the Pong Clone tutorial provided in the Amethyst documentation.

The full context for MovePlayerSystem can be found at my GitHub repo for sprite animations here.


I’m happy I was able to get this posted despite the first part being posted back in March. Finding the time and motivation has been difficult this year and it’s a relief to have this finished. Being able to revisit this implementation has helped refresh some concepts I’ve forgotten since then. When I find time, there are a few things I wanted to write about:

  • Attack animation. Here we’d add to the user input configuration to capture “actions” that will be interpreted as the player performing an “attack” action.
  • CameraFollowSystem. I had this implemented in another project of mine.

Thanks for reading! For full context of my implementation of the running animation, please visit this GitHub commit on my sprite animations project.

© Micah Tigley 2020

Powered by Hugo & Kiss.