Creating a City Building Game with SFML Part 3: Textures and Animations

8 Daniel Mansfield Aug 8, 2014

Get started with this tutorial series here!

Before we can draw any graphics, we need to discuss the difference between sprites, textures, and images. In SFML, an image (sf::Image) is exactly how it sounds; a collection of pixels in a 2D array. They can be easily manipulated on a per-pixel level, but they can't be drawn to the screen. A texture (sf::Texture) is an image that can be drawn, and lives in the graphics card instead of in RAM. Textures are not efficiently mutable however, and cannot have their pixels accessed individually. As such they are best created once and drawn repeatedly. They do not have any kind of position however, and so cannot be drawn without the help of another class, the sprite (sf::Sprite).

Sprites are both drawable and transformable, so they can be drawn to the screen. Thus, to create our background, we'll need both a texture, and a sprite. We could create both of these in GameStateStart and maintain them there, however if we want to reuse the background in another game state we would have to load the texture and create the sprite all over again! Instead we will use a texture manager to store our textures, which will be part of the Game class. Create a texture_manager.hpp file:

#ifndef TEXTURE_MANAGER_HPP
#define TEXTURE_MANAGER_HPP

#include <SFML/Graphics.hpp>
#include <string>
#include <map>

class TextureManager
{
    private:

    /* Array of textures used */
    std::map<std::string, sf::Texture> textures;

    public:

    /* Add a texture from a file */
    void loadTexture(const std::string& name, const std::string &filename);

    /* Translate an id into a reference */
    sf::Texture& getRef(const std::string& texture);

    /* Constructor */
    TextureManager()
    {
    }
};

#endif /* TEXTURE_MANAGER_HPP */

Our texture manager will work by storing an std::map that maps strings to textures; when we want a texture we will call the getRef function with the name of the texture we want and the manager will return a reference to it. This way our textures will not be destroyed unless our manager is, we don't have to use a bunch of pointers storing each individual texture, and our pointers will have easy to remember names instead of indices in an array. This method is not the best way to handle textures, but it is a simple way of doing it. In larger games (where the VRAM space is actually an issue) you would require ways of unloading textures so that only the required textures were loaded at any one time. For our purposes though, this will do fine. In a texture_manager.cpp file, place

#include <SFML/Graphics.hpp>
#include <map>
#include <string>

#include "texture_manager.hpp"

void TextureManager::loadTexture(const std::string& name, const std::string& filename)
{
    /* Load the texture */
    sf::Texture tex;
    tex.loadFromFile(filename);

    /* Add it to the list of textures */
    this->textures[name] = tex;

    return;
}

sf::Texture& TextureManager::getRef(const std::string& texture)
{
    return this->textures.at(texture);
}

In loadTexture we take the name to give the texture and the path of the file it's stored in. We then load the texture from that file and add it to the map with the name given. In getRef we return a reference to the texture whose name is passed to the function. If you are not familiar, std::map can be accessed like an array (as we did to add the texture) or by using the at member function. The at function provides bounds checking, and will throw an exception (error, in other words) if the specified element does not exist. As such, it's only useful for reading or writing to existing elements, and not for creating new ones. That's all for our texture manager! And unlike our state manager, we won't be going back to it. Speaking of the state manager, it's time to change game.hpp and add our new manager!

#include "texture_manager.hpp"

class GameState;

class Game
{
    private:

    void loadTextures();

    public:

    std::stack<GameState*> states;

    sf::RenderWindow window;
    TextureManager texmgr;
    sf::Sprite background;

    void pushState(GameState* state);

As you can see we've added our texture manager, texmgr, and we've declared a new private function called loadTextures. loadTextures will fill texmgr with the textures we need. We've also finally created background! Inside game.cpp we will define the loadTextures function

#include "game.hpp"
#include "game_state.hpp"
#include "texture_manager.hpp"

void Game::loadTextures()
{
    texmgr.loadTexture("background", "media/background.png");
}

Using the loadTexture function that we created, we add a new texture called "background" from the media/background.png file. Lastly we need to change the Game constructor to call the loadTextures function and set the texture of our background

Game::Game()
{
    this->loadTextures();

    this->window.create(sf::VideoMode(800, 600), "City Builder");
    this->window.setFramerateLimit(60);

    this->background.setTexture(this->texmgr.getRef("background"));
}

Using the getRef function we set to the texture of the background to the "background" texture we created in loadTextures. Finally, we can compile the code! If everything has gone well you should have a window with a lovely background, that can be resized and, even better, properly closed!

We now have most of the game engine structure out of the way, there's just one or two things left. The first of which being the AnimationHandler class. We'll use this class to provide animation support to any sprites we create, by including an AnimationHandler as a member variable in the class that contains the sprite. We could create a new AnimatedSprite class using inheritance instead, but I prefer this method.

Before we begin, let's discuss how the handler will work. We're going to keep this simple, so each sprite will have its own texture file that will contain all the animation stages for its different animations. It would be more efficient (for the computer, not us!) to store multiple sprites in the same file, but that makes the code and asset creation more complex so we won't do that. Anyway, we'll split the texture file into a grid, where each frame of the same animation extends to the right, and each animation extends downwards.

Here's the water tile sprite as an example

The handler will have an update function that takes a timestep dt and moves the animation to the next frame if necessary. First let's create an individual Animation class, which the handler will store an array of. In animation_handler.hpp...

#ifndef ANIMATION_HANDLER_HPP
#define ANIMATION_HANDLER_HPP

#include <SFML/Graphics.hpp>
#include <vector>

class Animation
{
    public:

    unsigned int startFrame;
    unsigned int endFrame;

    float duration;

    Animation(unsigned int startFrame, unsigned int endFrame, float duration)
    {
        this->startFrame = startFrame;
        this->endFrame = endFrame;
        this->duration = duration;
    }

    unsigned int getLength() { return endFrame - startFrame + 1; }
};

#endif /* ANIMATION_HANDLER_HPP */

It's such a simple class we could have used a struct if not for the getLength function. startFrame and endFrame are the zero-based indices of the start and stop frame in the grid, and duration is the amount of time one frame should last for. I find that this is nicer to work with than a frequency or frames-per-second value. And now we create the handler itself (underneath Animation and before that header guard).

class AnimationHandler
{
    private:

    /* Array of animations */
    std::vector<Animation> animations;

    /* Current time since the animation loop started */
    float t;

    int currentAnim;

    public:

    /* Add a new animation */
    void addAnim(Animation& anim);

    /* Update the current frame of animation. dt is the time since
     * the update was last called (i.e. the time for one frame to be
     * executed) */
    void update(const float dt);

    /* Change the animation, resetting t in the process */
    void changeAnim(unsigned int animNum);

    /* Current section of the texture that should be displayed */
    sf::IntRect bounds;

    /* Pixel dimensions of each individual frame */
    sf::IntRect frameSize;

    /* Constructor */
    AnimationHandler()
    {
        this->t = 0.0f;
        this->currentAnim = -1;
    }
    AnimationHandler(const sf::IntRect& frameSize)
    {
        this->frameSize = frameSize;

        this->t = 0.0f;
        this->currentAnim = -1;
    }
};

#endif /* ANIMATION_HANDLER_HPP */

First up we have that std::vector of Animations we mentioned. t is the elapsed time since the animation started or looped, and is used to determine when the next frame should occur. Every time update is called we will increase t by dt to keep track of the time. currentAnim is the (vertical) index in the grid or the (horizontal) index in the std::vector used to keep track of which animation is running. The next three functions are self-explanatory (or are with the comments), but then we have some SFML code.

An sf::IntRect is a rectangle with integers for its start coordinate, its width, and its height. Since we're using a single texture for all of the animations, we use an sf::IntRect to keep track of which section of the texture the sprite should show. Later we'll tell the sprite to use that section of the texture by using sf::Sprite's setTextureRect member function. The constructors are rather straightforward, so we'll move on to writing the algorithms for the handler to use. In animation_handler.cpp we first create the update function

#include <SFML/Graphics.hpp>
#include <vector>

#include "animation_handler.hpp"

void AnimationHandler::update(const float dt)
{
    if(currentAnim >= this->animations.size() || currentAnim < 0) return;

    float duration = this->animations[currentAnim].duration;

    /* Check if the animation has progessed to a new frame and if so
     * change to the next frame */
    if(int((t + dt) / duration) > int(t / duration))
    {
        /* Calculate the frame number */
        int frame = int((t + dt) / duration);

        /* Adjust for looping */
        frame %= this->animations[currentAnim].getLength();

        /* Set the sprite to the new frame */
        sf::IntRect rect = this->frameSize;
        rect.left = rect.width * frame;
        rect.top = rect.height * this->currentAnim;
        this->bounds = rect;
    }

    /* Increment the time elapsed */
    this->t += dt;
    /* Adjust for looping */
    if(this->t > duration * this->animations[currentAnim].getLength())
    {
        this->t = 0.0f;
    }

    return;
}

Firstly, we don't update if the current animation does not exist. The next line just saves us some writing every time we want to know the animation's duration. If each animation takes duration seconds, then from t between 0 and duration, we're on frame 0, t between duration and 2*duration we are on frame 1, t between 2*duration and 3*duration we are on frame 2, and so on.

If we divide t by duration them frame 0 is between 0 and 1, frame 1 is between 1 and 2, and so on. Thus if we round t / duration down (i.e. cast it to an integer) we get what frame of the animation is playing. So if the new time t+dt gives a different answer using that formula to t we need to advance the animation to the next frame. Can you see when this wouldn't work? So long as dt < duration, we'll only ever advance one frame at a time, but if dt is too large we should skip a frame of animation! That's why we calculate the new frame on the next line, instead of just incrementing the frame number.

And here's the water sprite animated!

This brings us to another problem, however. If we have a 4 frame animation (0-3) then when we are on frame 3 we should not go to frame 4 or 5 or higher (depending on dt), we should loop back to frame 0, 1 or whatever is next! To do this we simply take the modulus of the frame with the number of frames to ensure that the animation loops correctly. This shows a limitation of our animation handler: every animation loops, and nothing happens at the end.

We then compute the rectangle of the texture that the sprite should show using the pattern mentioned before (frames move to the right). Regardless of whether the frame has changed or not we advance the elapsed time by the timestep and then ensure that the elapsed time resets round to 0 if it is greater than the duration of the animation. By setting it to 0 instead of taking the modulus (which is awkward with a floating point value) we may introduce a slight jitter at the end of the animation, but this is only noticeable if multiple frames are being jumped at once. Adding the last two functions,

void AnimationHandler::addAnim(Animation& anim)
{
    this->animations.push_back(anim);

    return;
}

void AnimationHandler::changeAnim(unsigned int animID)
{
    /* Do not change the animation if the animation is currently active or
     * the new animation does not exist */
    if(this->currentAnim == animID || animID >= this->animations.size() ||
        animID < 0) return;

    /* Set the current animation */
    this->currentAnim = animID;
    /* Update the animation bounds */
    sf::IntRect rect = this->frameSize;
    rect.top = rect.height * animID;
    this->bounds = rect;
    this->t = 0.0;

    return;
}

addAnim just adds the specified animation to the animation std::vector, and changeAnim sets the current animation to the new one (if the new one is valid) before setting the bounds rectangle to the first frame and resetting the elapsed time. And with that our game engine is essentially complete! In part four, we'll move back and add some more to GameStateEditor before moving on to the Tiles.

Source code for this section

8 comments


Or enter your name and Email
  • M MichaƂ 1 year ago
    I'm using SFML 2.4 After compiling and running program i get " 'std::out_of_range' what(): map::at " and then program just crashes. What should i do ?
    • N Nikolay 1 year ago
      You are passing in "at" invalid name of texture
  • JH James Horton 2 years ago
    Using Ubuntu, I did everything as you said up to the point when you say we can compile to get a nice window. I am using Ubuntu 15.04 and SFML 2.3 I successfuly compiled the files into a single game.o file using: "g++ -c -std=c++11 game.cpp" but when I try to continue compilation with: "g++ game.o -o sfml-app -lsfml-graphics -lsfml-window -lsfml-system -std=c++11" then I encounter these errors: /usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crt1.o: In function `_start': /build/buildd/glibc-2.21/csu/../sysdeps/x86_64/start.S:114: undefined reference to `main' game.o: In function `Game::loadTextures()': game.cpp:(.text+0x11c): undefined reference to `TextureManager::loadTexture(std::string const&, std::string const&)' game.o: In function `Game::Game()': game.cpp:(.text+0x550): undefined reference to `TextureManager::getRef(std::string const&)' collect2: error: ld returned 1 exit status What am I doing wrong?
    • K kishore 6 months ago
      you are compiling just one file , you have to compile the dependencies as multiple files .
  • A Andrew 3 years ago
    Structs can actually have functions in C++, they're really just syntactical sugar for classes whose members default to public instead of private. So Animation could have been a struct regardless. :D
  • JM John Peter McGrath 3 years ago
    I am attempting to run the program using Windows Visual Studio Express 2013 on Windows 8 after completing the changes in Game constructor in game.cpp. However, I got compiler errors because of getSize(). I happen to be using SFML 2.1 instead of 1.6 Is there an equivalent to to getsize for 2.1 or later?
    • JM John Peter McGrath 3 years ago
      I found out that Texture has not been added until 2.0. I will have to try out 2.0 to see if it solves the problem
    • JM John Peter McGrath 3 years ago
      Never mind, I forgot the () at the end of 'getTexture' (which should have been getTexture() ) I have to always remember that when using get and set member functions