How to animate sprite for character movement?

I’ve managed to get a sprite to load on my screen, and now I’m hooked :slight_smile:. I’m pondering the best way to go about animating the sprite however. For example, I have a sprite sheet that is 128x16 for a character. It contains 8 separate sprites:

  1. walk_forward_1
  2. walk_forward_2
  3. walk_left_1
  4. walk_left_2
  5. walk_right_1
  6. walk_right_2
  7. walk_away_1
  8. walk_away_2

Each action (walk_forward, walk_left, walk_right, walk_away) has two sprites which compose the animation for that action. How do I go about displaying the correct sprite when a button is pressed, and how do I go about switching between the two sprites to show the animation?

Also, side question… walk_right_1 and walk_right_2 are mirrors of walk_left_1 and walk_left_2. Is there a way to mirror a sprite so I do not have to include the data for these sprites in this character’s byte array?

As you might have guessed, I’m pretty green to working with sprites. Any assistance is greatly appreciated!

1 Like

Some of this will depend on how you choose to draw your sprites. There’s the Arduboy2 library’s drawBitmap() and drawCompressed() functions, and the various methods in the library’s Sprites class.

There’s also the ArdBitmap library which can handle both compressed and uncompressed sprites and can scale and mirror them.

@MLXXXp is right, there are a number of different options in the Arduboy world to display sprites.

I am not sure if this is a learning exercise or the start of a game, but its worth considering whether your character will be walking over the top of other graphics. If so, you might need to consider masks or transparencies to achieve the results you want.

There is an article in the Arduboy Magazine that describes the ‘standard’ Arduboy functions and masking (Volume 7, Page 33) and a sample app here > https://github.com/filmote/Pipes_Article1_SpritesDemo

1 Like

And note that if you do require masks, you’re not limited to using the Sprites class. Although the Spites class has masking built into the functions, there are ways to mask with all the other functions/libraries I mentioned, as well.

Too true. In my latest games, I have been taking advantage of the compressed image format with and without masks. I have extended the class to add the ability to draw the image mirrored horizontally. You can dig into my Karateka or Choplifter code and have a look at the Arduboy2Ext code.

1 Like

The Sprites class has a sort of ‘spritesheet’ functionality that would make accessing the sprites easier.

You have to bundle the image data into a single array, like so:

And then you can render it using an index specifying the correct image, a bit like this:

This is a bit of a complex example because I’m using an enum class for direction (see here), basically north (up) is 0, east is 1, south is 2 and west is 3, so in your case it’d be something like:

Sprites::drawOverwrite(player.x, player.y, PlayerImages, static_cast<uint8_t>(player.direction));

(The static_cast<uint8_t> isn’t needed if you’re just using bare numbers, but I recommend enum classes because they’re scoped and strongly typed.)


To answer your actual questions directly:

Store the player’s direction somewhere.
Make the button presses alter the direction.
Pick a sprite to render based on the player’s direction.

That’s a little bit more complicated because you have to track the animation state and it would depend exactly how you want to handle the animations.

If you want to do something Pokemon-like (which I’m guessing might be the case given your choice of sprites) then you’d need to only show the second walk frame while the character is actually walking, which could be complicated.

If you’re happy with looping the animations then you could just have another walkFrame variable and pick your frame based on both direction and walkFrame.

(There are a couple of ways you could ‘cheat’ as well, by abusing the actual bit pattern of the frame indexes.)

@filmote’s solution is the only one I’ve seen used.

Thank you to everyone for the replies. I managed to get this working, below is the code.

#include <Arduboy2.h>

const byte PROGMEM walk_towards[] =
{
  16, 16,

  0x00, 0xa8, 0x7a, 0x7a, 0xde, 0x3d, 0xdf, 0x3f, 0x1f, 0xce, 0x1f, 0xfc, 0x6e, 0x50, 0xd0, 0x20, 
  0xf, 0xf, 0x13, 0x92, 0xfd, 0xca, 0xd5, 0xf4, 0x14, 0x15, 0xca, 0xfd, 0x86, 0x9, 0x9, 0x7, 
  0x00, 0xa8, 0x7a, 0x7a, 0xde, 0x3d, 0xdf, 0x3f, 0x1f, 0xce, 0x1f, 0xfc, 0x6e, 0x50, 0xd0, 0x20, 
  0x7, 0x9, 0x9, 0x86, 0xfd, 0xca, 0x15, 0x14, 0xf4, 0xd5, 0xca, 0xfd, 0x92, 0x13, 0xf, 0xf
  
};

const byte PROGMEM walk_away[] =
{
  16, 16,

  0x00, 0x80, 0x40, 0x5e, 0x6c, 0xdf, 0x5e, 0xbf, 0xbf, 0x5f, 0xdd, 0x6e, 0x7a, 0x5a, 0x88, 0x00, 
  0x1c, 0x23, 0x21, 0x99, 0xfe, 0xc7, 0xcb, 0x7b, 0x1b, 0xb, 0x4f, 0xff, 0x86, 0x9, 0x9, 0x6, 
  0x00, 0x80, 0x40, 0x5e, 0x6c, 0xdf, 0x5e, 0xbf, 0xbf, 0x5f, 0xdd, 0x6e, 0x7a, 0x5a, 0x88, 0x00,
  0x6, 0x9, 0x9, 0x86, 0xff, 0x4f, 0xb, 0x1b, 0x7b, 0xcb, 0xc7, 0xfe, 0x99, 0x21, 0x23, 0x1c
  
};

const byte PROGMEM walk_left[] =
{
  16, 16,

  0x10, 0x68, 0x3d, 0x3f, 0xee, 0xf, 0xdf, 0x2f, 0x6e, 0xbf, 0x6e, 0x6e, 0xf8, 0x50, 0xa0, 0xc0, 
  0x00, 0x00, 0x00, 0x00, 0x1, 0xc6, 0xba, 0x9e, 0x93, 0x92, 0xff, 0xf, 0x7, 0x00, 0x00, 0x00, 
  0x18, 0xc, 0x7d, 0x3f, 0xee, 0xf, 0xdf, 0x2f, 0x6f, 0xbf, 0x6e, 0x6e, 0xd8, 0x50, 0x28, 0x18, 
  0x00, 0x00, 0x00, 0xa0, 0xdd, 0x92, 0x8e, 0xda, 0xb3, 0xea, 0x8c, 0x9f, 0xd3, 0xf3, 0x8c, 0x00 
  
};

const byte PROGMEM walk_right[] =
{
  16, 16,

  0xc0, 0xa0, 0x50, 0xf8, 0x6e, 0x6e, 0xbf, 0x6e, 0x2f, 0xdf, 0xf, 0xee, 0x3f, 0x3d, 0x68, 0x10, 
  0x00, 0x00, 0x00, 0x7, 0xf, 0xff, 0x92, 0x93, 0x9e, 0xba, 0xc6, 0x1, 0x00, 0x00, 0x00, 0x00, 
  0x18, 0x28, 0x50, 0xd8, 0x6e, 0x6e, 0xbf, 0x6f, 0x2f, 0xdf, 0xf, 0xee, 0x3f, 0x7d, 0xc, 0x18, 
  0x00, 0x8c, 0xf3, 0xd3, 0x9f, 0x8c, 0xea, 0xb3, 0xda, 0x8e, 0x92, 0xdd, 0xa0, 0x00, 0x00, 0x00,  
  
};


Arduboy2 arduboy;
Sprites sprites;
byte frame = 0;
byte x = 0;
byte y = 0;
char cdir = 'T'; // T, L, R, A

#define CHAR_WIDTH 16
#define CHAR_HEIGHT 16
#define X_MAX (WIDTH - CHAR_WIDTH)
#define Y_MAX (HEIGHT - CHAR_HEIGHT)

void setup()
{
  
  arduboy.begin();
  arduboy.clear();
  arduboy.setFrameRate(30);

  
}
void loop() {
  if (!(arduboy.nextFrame())){
    return;
  }
  
  if(arduboy.pressed(RIGHT_BUTTON) && (x < X_MAX)){
    x++;
    cdir = 'R';
  }
  if(arduboy.pressed(LEFT_BUTTON) && (x > 0)){
    x--;
    cdir = 'L';
  }
  if(arduboy.pressed(UP_BUTTON) && (y > 0)){
    y--;
    cdir = 'A';
  }
  if(arduboy.pressed(DOWN_BUTTON) && (y < Y_MAX)){
    y++;
    cdir = 'T';
  }

  arduboy.clear();
  
  arduboy.setCursor(x, y);

  if(cdir == 'T'){
    sprites.drawSelfMasked(x, y, walk_towards, frame);
  }
  else if(cdir == 'L'){
    sprites.drawSelfMasked(x, y, walk_left, frame);
  }
  else if(cdir == 'R'){
    sprites.drawSelfMasked(x, y, walk_right, frame);
  }
  else if(cdir == 'A'){
    sprites.drawSelfMasked(x, y, walk_away, frame);
  }
  
  
  if (arduboy.everyXFrames(8)) frame++;
  
  if (frame > 1) frame = 0;
  
  arduboy.display();
  
}
2 Likes

Looks like @filmote forgot to link to the article he mentioned.

Volume 7 of the magazine can be read online for free here:

The rest of the issues can be found here:

The magazine has many useful tutorials contributed by forum users.


I’d also recommend reading @filmote’s article on Scoped Enumerations from Issue 9, page 29 onwards.

This is the technique I used for the (unfinished) game I used examples from. It’s spiritually similar to what you’re doing with char cdir, but much easier to read and somewhat self-documenting.

Awesome! Thanks for the links :smiley:

1 Like

Its unlike me to miss a chance to plug my own work.

So here goes … there is a tutorial on this site that walks you through building a sideways-scrolling game based on ‘Stevet the Jumping Dinsoaur’ (sometimes referered to as The Chrome Dinosaur). It covers a number of topics such as simple animation, collision detection and so on. You may find bits of it useful.

2 Likes

This is perfect! I had sat down with the intention of adding a background image and getting a character to walk over it, noticed I had a notification, came here, BAM exactly what I was after. :slight_smile:

3 Likes