DEV Community

Noah11012
Noah11012

Posted on • Updated on

Using SDL2: Spritesheets

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:

#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;
};
Enter fullscreen mode Exit fullscreen mode

spritesheet.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);
}
Enter fullscreen mode Exit fullscreen mode

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.

private:
    Spritesheet  m_spritesheet;
Enter fullscreen mode Exit fullscreen mode

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

m_spritesheet.select_sprite(0, 0);
Enter fullscreen mode Exit fullscreen mode

Compile and run.

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.

int const SPRITESHEET_UP = 0;
int const SPRITESHEET_LEFT = 1;
int const SPRITESHEET_RIGHT = 2;
int const SPRITESHEET_DOWN = 3;
Enter fullscreen mode Exit fullscreen mode

This is mainly for readability.

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

int          m_spritesheet_column;
Enter fullscreen mode Exit fullscreen mode

And now in the update() method, we change the row according to the direction.

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;
Enter fullscreen mode Exit fullscreen mode

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.

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

Latest comments (1)

Collapse
 
batk0vich profile image
Dronov Dmitrii

A really good series of tutorials! The best beginner SDL tutorials I've tried! Thank you very much!