Using SDL2: Spritesheets
Noah11012
Posted on November 2, 2018
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;
};
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);
}
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;
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);
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;
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;
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;
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:
Posted on November 2, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.