Using SDL2: Spritesheets
Tags: sdl, cpp, spritesheets
Published: 2018-11-02 01:44:33

Let's recap on what we've accomplished so far in this series:

Our journey started simple. A black window appeared on the screen with an image of our choice. Then, we moved the image across the screen. Eventually being bored that we couldn't interact with the program, we took control of our stick figure and moved it where ever we wanted.

The next installment that I feel is the logical step to take this series is sprite sheets.

I'm sure at least some of you know what a sprite sheet is, but for the rest of you, it's a collection of sprites collected in one image. This gives us the advantage of only using one image but can be treated as multiple individual sub-images.

I'll assume most of you will download a sprite sheet from the internet as I did. If the provider of the sprite sheet didn't supply measurements of the dimensions of the individual sprites you'll have to do your own measurements.

## Sprite sheet class

As you might have predicted, we'll need a class that manages the sprite sheet.
I've made it simple that will work perfectly for our needs.

spritesheet.hpp:

```cpp
#pragma once

#include <SDL2/SDL.h>
#include "utilities.hpp"

class Spritesheet
{
public:
    Spritesheet(char const *path, int row, int column);
    ~Spritesheet();

    void select_sprite(int x, int y);
    void draw_selected_sprite(SDL_Surface *window_surface, SDL_Rect *position);

private:
    SDL_Rect     m_clip;
    SDL_Surface *m_spritesheet_image;
};
```

spritesheet.cpp:

```cpp
#include "spritesheet.hpp"

Spritesheet::Spritesheet(char const *path, int row, int column)
{
    m_spritesheet_image = load_bmp(path);

    m_clip.w = m_spritesheet_image->w / column;
    m_clip.h = m_spritesheet_image->h / row;
}

Spritesheet::~Spritesheet()
{
    SDL_FreeSurface(m_spritesheet_image);
}

void Spritesheet::select_sprite(int x, int y)
{
    m_clip.x = x * m_clip.w;
    m_clip.y = y * m_clip.h;
}

void Spritesheet::draw_selected_sprite(SDL_Surface *window_surface, SDL_Rect *position)
{
    SDL_BlitSurface(m_spritesheet_image, &m_clip, window_surface, position);
}
```

The `Spritesheet` class takes a path to a sprite sheet image and the rows and columns. In the constructor, we load the image and calculate the width and height of a sprite. Your sprite sheet should be divided up evenly. One sprite's dimensions should be equal to the rest of the sprites. We can calculate a sprite's dimensions by the dividing image's width and height by the rows and columns, respectively.

In the method `selection_sprite()` takes an x-offset and a y-offset. We multiply the x-offset and y-offset by the width and height of a sprite respectively.

Drawing the sprite is done by calling `draw_selected_sprite()`. It takes a pointer to an `SDL_Surface` for the destination surface and a pointer to an `SDL_Rect` for the position of the sprite on the target surface.

## Animation

In the `StickFigure` class, we'll remove the `SDL_Surface` we were using last time and replace it with our new `Spritesheet` class.

```cpp
private:
    Spritesheet  m_spritesheet;
```

Now in the constructor, we can pick any of the sprites we want. We'll use the first sprite as the default one.

```cpp
m_spritesheet.select_sprite(0, 0);
```

Compile and run.

![](https://thepracticaldev.s3.amazonaws.com/i/uqoj79aa677lyx8pev2q.png)

Oh, and another change I made was to fill the window with white as the stick figures I'm using are black.

Now, let's get the animation working. I defined constants in stick_figure.cpp file for the offsets of the rows.

```cpp
int const SPRITESHEET_UP = 0;
int const SPRITESHEET_LEFT = 1;
int const SPRITESHEET_RIGHT = 2;
int const SPRITESHEET_DOWN = 3;
```

This is mainly for readability.

We also added another variable in the `StickFigure` class for the offset of the column of any row.

```cpp
int          m_spritesheet_column;
```
And now in the `update()` method, we change the row according to the direction.

```cpp
switch(m_direction)
{
    case Direction::NONE:
        m_x += 0.0;
        m_y += 0.0;
        m_spritesheet.select_sprite(0, 0);
        break;
    case Direction::UP:
        m_y = m_y - (500.0 * delta_time);
        m_spritesheet.select_sprite(m_spritesheet_column, SPRITESHEET_UP);
        break;
    case Direction::DOWN:
        m_y = m_y + (500.0 * delta_time);
        m_spritesheet.select_sprite(m_spritesheet_column, SPRITESHEET_DOWN);
        break;
    case Direction::LEFT:
        m_x = m_x - (500.0 * delta_time);
        m_spritesheet.select_sprite(m_spritesheet_column, SPRITESHEET_LEFT);
        break;
    case Direction::RIGHT:
        m_x = m_x + (500.0 * delta_time);
        m_spritesheet.select_sprite(m_spritesheet_column, SPRITESHEET_RIGHT);
        break;
}

m_position.x = m_x;
m_position.y = m_y;

m_spritesheet_column++;

if(m_spritesheet_column > 8)
    m_spritesheet_column = 1;
```

We also loop back to the second image and not the first because how this sprite is made it's just better this way. Your sprite may be different.

In the `draw()` method we draw the sprite and delay for 100 milliseconds or 1/10th of a second. Because we're pausing for a small amount, we need to increase the number of pixels we move per second. Originally, it was 5 pixels but now we do it at 500 pixels.

Compile and run.

![](https://thepracticaldev.s3.amazonaws.com/i/zu7250bfimv6fpvszmuo.png)

If you have any questions, I'll be happy to answer them.

## What's next

We'll be learning how to open different images like PNGs and how to optimize surface blitting.

All source code for the series can be found at my Github repository:

https://github.com/Noah11012/sdl2-tutorial-code