Drawing images in SFML
In order to draw an image on screen, we need to become familiar with two classes: sf::Texture
and sf::Sprite
. A texture is essentially just an image that lives on the graphics card for the purpose of making it fast to draw. Any given picture on your hard drive can be turned into a texture by loading it:
sf::Texture texture; if(!texture.loadFromFile("filename.png"){ // Handle an error. }
The loadFromFile
method returns a Boolean value, which serves as a simple way of handling loading errors, such as the file not being found. If you have a console window open along with your SFML window, you will notice some information being printed out in case the texture loading did fail:
Failed to load image "filename.png". Reason : Unable to open file
Tip
Unless a full path is specified in the loadFromFile
method, it will be interpreted as relative to the working directory. It's important to note that while the working directory is usually the same as the executable's when launching it by itself, compiling and running your application in an IDE (Microsoft Visual Studio in our case) will often set it to the project directory instead of the debug or release folders. Make sure to put the resources you're trying to load in the same directory where your .vcxproj
project file is located if you've provided a relative path.
It's also possible to load your textures from memory, custom input streams, or sf::Image
utility classes, which help store and manipulate image data as raw pixels, which will be covered more broadly in later chapters.
What is a sprite?
A sprite, much like the sf::Shape
derivatives we've worked with so far, is a sf::Drawable
object, which in this case represents a sf::Texture
and also supports a list of transformations, both physical and graphical. Think of it as a simple rectangle with a texture applied to it:
sf::Sprite
provides the means of rendering a texture, or a part of it, on screen, as well as means of transforming it, which makes the sprite dependent on the use of textures. Since sf::Texture
isn't a lightweight object, sf::Sprite
comes in for performance reasons to use the pixel data of a texture it's bound to, which means that as long as a sprite is using the texture it's bound to, the texture has to be alive in memory and can only be de-allocated once it's no longer being used. After we have our texture set up, it's really easy to set up the sprite and draw it:
sf::Sprite sprite(texture); ... window.draw(sprite);
It's optional to pass the texture by reference to the sprite constructor. The texture it's bound to can be changed at any time by using the setTexture
method:
sprite.setTexture(texture);
Since sf::Sprite
, just like sf::Shape
, inherits from sf::Transformable
, we have access to the same methods of manipulating and obtaining origin, position, scale, and rotation.
It's time to apply all the knowledge we've gained so far and write a basic application that utilizes it:
void main(int argc, char** argv[]){ sf::RenderWindow window(sf::VideoMode(640,480), "Bouncing mushroom."); sf::Texture mushroomTexture; mushroomTexture.loadFromFile("Mushroom.png"); sf::Sprite mushroom(mushroomTexture); sf::Vector2u size = mushroomTexture.getSize(); mushroom.setOrigin(size.x / 2, size.y / 2); sf::Vector2f increment(0.4f, 0.4f); while(window.isOpen()){ sf::Event event; while(window.pollEvent(event)){ if(event.type == sf::Event::Closed){ window.close(); } } if((mushroom.getPosition().x + (size.x / 2) > window.getSize().x && increment.x > 0) || (mushroom.getPosition().x - (size.x / 2) < 0 && increment.x < 0)) { // Reverse the direction on X axis. increment.x = -increment.x; } if((mushroom.getPosition().y + (size.y / 2) > window.getSize().y && increment.y > 0) || (mushroom.getPosition().y - (size.y / 2) < 0 && increment.y < 0)) { // Reverse the direction on Y axis. increment.y = -increment.y; } mushroom.setPosition(mushroom.getPosition() + increment); window.clear(sf::Color(16,16,16,255)); // Dark gray. window.draw(mushroom); // Drawing our sprite. window.display(); } }
The code above will produce a sprite bouncing around the window, reversing in direction every time it hits the window boundaries. Error checking for loading the texture is omitted in this case in order to keep the code shorter. The two if
statements after the event handling portion in the main loop are responsible for checking the current position of our sprite and updating the direction of the increment value represented by a plus or minus sign, since you can only go towards the positive or negative end on a single axis. Remember that the origin of a shape by default is its top-left corner, as shown here:
Because of this, we must either compensate for the entire width and height of a shape when checking if it's out-of-bounds on the bottom or the right side, or make sure its origin is in the middle. In this case, we do the latter and either add or subtract half of the texture's size from the mushroom's position to check if it is still within our desired space. If it's not, simply invert the sign of the increment float vector on the axis that is outside the screen and voila! We have bouncing!
For extra credit, feel free to play around with the sf::Sprite
's setColor
method, which can be used to tint a sprite with a desired color, as well as make it transparent, by adjusting the fourth argument of the sf::Color
type, which corresponds to the alpha channel:
mushroom.setColor(sf::Color(255, 0, 0, 255)); // Red tint.