Tying it into the framework
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.
Creating the texture manager
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:
- First we declare our
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);
- We will create two draw functions,
draw
anddrawFrame
. They will both take the ID of the texture we want to draw, thex
andy
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 anSDL_RendererFlip
value to describe how we want the image to be displayed (default isSDL_FLIP_NONE
). ThedrawFrame
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);
- The
TextureManager
class will also containstd::map
of pointers to theSDL_Texture
objects, keyed usingstd::strings
.std::map<std::string, SDL_Texture*> m_textureMap;
- We now must define these functions in a
TextureManager.cpp
file. Let's start with theload
function. We will take the code from our previous texture loading and use it within thisload
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; }
- When we call this function we will then have
SDL_Texture
that can be used by accessing it from the map using its ID; we will use this in ourdraw
functions. Thedraw
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); }
- We again use
SDL_RenderCopyEx
using the passed in ID variable to get theSDL_Texture
object we want to draw. We also build our source and destination variables using the passed inx
,y
,width
, andheight
values. Now we can move ontodrawFrame
: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
andcurrentRow
variables. The source rectangle'sx
position for the current frame is the width of the source rectangle multiplied by thecurrentFrame
value (covered in the Animating a sprite sheet section). Itsy
value is the height of the rectangle multiplied bycurrentRow – 1
(it sounds more natural to use the first row, rather than the zeroth row). - We now have everything we need to easily load and draw textures throughout our game. Let's go ahead and test it out using the
animated.png
image. Open upGame.h
. We will not need our texture member variables or the rectangles anymore, so delete any of the code dealing with them from theGame.h
andGame.cpp
files. We will however create two new member variables.int m_currentFrame; TextureManager m_textureManager;
- We will use the
m_currentFrame
variable to allow us to animate our sprite sheet and we also need an instance of our newTextureManager
class (ensure you includeTextureManager.h
). We can now load a texture in the game'sinit
function.m_textureManager.load("assets/animate-alpha.png", "animate", m_pRenderer);
- We have given this texture an ID of
"animate"
which we can use in ourdraw
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); }
- The drawFrame function uses our
m_currentFrame
member variable. We can increment this in theupdate
function like we did before, but we now do the calculation of the source rectangle inside thedraw
function.void Game::update() { m_currentFrame = int(((SDL_GetTicks() / 100) % 6)); }
Now we can build and see our hard work in action.
Using texture manager as a singleton
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.