We have covered a lot on the subject of drawing images with SDL but we have yet to tie everything together into our framework so that it becomes reusable throughout our game. What we will now cover is creating a texture manager class that will have all of the functions we need to easily load and draw textures.
The texture manager will have functions that allow us to load and create an SDL_Texture
structure from an image file, draw the texture (either static or animated), and also hold a list of SDL_Texture*
, so that we can use them whenever we need to. Let's go ahead and create the TextureManager.h
file:
load
function. As parameters, the function takes the filename of the image we want to use, the ID we want to use to refer to the texture, and the renderer we want to use.bool load(std::string fileName,std::string id, SDL_Renderer* pRenderer);
draw
and drawFrame
. They will both take the ID of the texture we want to draw, the x
and y
position we want to draw to, the height and width of the frame or the image we are using, the renderer we will copy to, and an SDL_RendererFlip
value to describe how we want the image to be displayed (default is SDL_FLIP_NONE
). The drawFrame
function will take two additional parameters, the current frame we want to draw and which row it is on in the sprite sheet.// draw void draw(std::string id, int x, int y, int width, int height, SDL_Renderer* pRenderer, SDL_RendererFlip flip = SDL_FLIP_NONE); // drawframe void drawFrame(std::string id, int x, int y, int width, int height, int currentRow, int currentFrame, SDL_Renderer* pRenderer, SDL_RendererFlip flip = SDL_FLIP_NONE);
TextureManager
class will also contain std::map
of pointers to the SDL_Texture
objects, keyed using std::strings
.std::map<std::string, SDL_Texture*> m_textureMap;
TextureManager.cpp
file. Let's start with the load
function. We will take the code from our previous texture loading and use it within this load
method.bool TextureManager::load(std::string fileName, std::string id, SDL_Renderer* pRenderer) { SDL_Surface* pTempSurface = IMG_Load(fileName.c_str()); if(pTempSurface == 0) { return false; } SDL_Texture* pTexture = SDL_CreateTextureFromSurface(pRenderer, pTempSurface); SDL_FreeSurface(pTempSurface); // everything went ok, add the texture to our list if(pTexture != 0) { m_textureMap[id] = pTexture; return true; } // reaching here means something went wrong return false; }
SDL_Texture
that can be used by accessing it from the map using its ID; we will use this in our draw
functions. The draw
function can be defined as follows:void TextureManager::draw(std::string id, int x, int y, int width, int height, SDL_Renderer* pRenderer, SDL_RendererFlip flip) { SDL_Rect srcRect; SDL_Rect destRect; srcRect.x = 0; srcRect.y = 0; srcRect.w = destRect.w = width; srcRect.h = destRect.h = height; destRect.x = x; destRect.y = y; SDL_RenderCopyEx(pRenderer, m_textureMap[id], &srcRect, &destRect, 0, 0, flip); }
SDL_RenderCopyEx
using the passed in ID variable to get the SDL_Texture
object we want to draw. We also build our source and destination variables using the passed in x
, y
, width
, and height
values. Now we can move onto drawFrame
:void TextureManager::drawFrame(std::string id, int x, int y, int width, int height, int currentRow, int currentFrame, SDL_Renderer *pRenderer, SDL_RendererFlip flip) { SDL_Rect srcRect; SDL_Rect destRect; srcRect.x = width * currentFrame; srcRect.y = height * (currentRow - 1); srcRect.w = destRect.w = width; srcRect.h = destRect.h = height; destRect.x = x; destRect.y = y; SDL_RenderCopyEx(pRenderer, m_textureMap[id], &srcRect, &destRect, 0, 0, flip); }
In this function, we create a source rectangle to use the appropriate frame of the animation using the currentFrame
and currentRow
variables. The source rectangle's x
position for the current frame is the width of the source rectangle multiplied by the currentFrame
value (covered in the Animating a sprite sheet section). Its y
value is the height of the rectangle multiplied by currentRow – 1
(it sounds more natural to use the first row, rather than the zeroth row).
animated.png
image. Open up Game.h
. We will not need our texture member variables or the rectangles anymore, so delete any of the code dealing with them from the Game.h
and Game.cpp
files. We will however create two new member variables.int m_currentFrame; TextureManager m_textureManager;
m_currentFrame
variable to allow us to animate our sprite sheet and we also need an instance of our new TextureManager
class (ensure you include TextureManager.h
). We can now load a texture in the game's init
function.m_textureManager.load("assets/animate-alpha.png", "animate", m_pRenderer);
"animate"
which we can use in our draw
functions. We will start by drawing a static image at 0,0 and an animated image at 100,100. Here is the render function:void Game::render() { SDL_RenderClear(m_pRenderer); m_textureManager.draw("animate", 0,0, 128, 82, m_pRenderer); m_textureManager.drawFrame("animate", 100,100, 128, 82, 1, m_currentFrame, m_pRenderer); SDL_RenderPresent(m_pRenderer); }
m_currentFrame
member variable. We can increment this in the update
function like we did before, but we now do the calculation of the source rectangle inside the draw
function.void Game::update() { m_currentFrame = int(((SDL_GetTicks() / 100) % 6)); }
Now we can build and see our hard work in action.
Now that we have our texture manager in place we still have one problem. We want to reuse this TextureManager
throughout our game so we don't want it to be a member of our Game
class because then we would have to pass it into our draw function. A good option for us is to implement TextureManager
as a singleton. A singleton is a class that can only have one instance. This works for us, as we want to reuse the same TextureManager
throughout our game. We can make our TextureManager
a singleton by first making its constructor private.
private: TextureManager() {}
This is to ensure that it cannot be created like other objects. It can only be created and accessed using the
Instance
function, which we will declare and define.
static TextureManager* Instance() { if(s_pInstance == 0) { s_pInstance = new TextureManager(); return s_pInstance; } return s_pInstance; }
This function checks whether we already have an instance of our TextureManager
. If not, then it constructs it, otherwise it simply returns the static instance. We will also typedef
the TextureManager
.
typedef TextureManager TheTextureManager;
We must also define the static instance in TextureManager.cpp
.
TextureManager* TextureManager::s_pInstance = 0;
We can now use our TextureManager
as a singleton. We no longer have to have an instance of TextureManager
in our Game
class, we just include the header and use it as follows:
// to load if(!TheTextureManager::Instance()->load("assets/animate-alpha.png", "animate", m_pRenderer)) { return false; } // to draw TheTextureManager::Instance()->draw("animate", 0,0, 128, 82, m_pRenderer);
When we load a texture in our Game
(or any other) class we can then access it throughout our code.
3.139.86.18