In the previous tutorial, we covered the basics of using graphics in SFML. Chances are however your game isn’t going to be composed of simple shapes, but instead made up of many sprites. That is exactly what we are going to cover today.
As always, there is an HD version of this tutorial available here.
First off, we should start by defining what exactly a Sprite is. In the early days of computers, sprite had special meaning as there was literally sprite hardware built into early 8bit computers. A sprite is basically an image on screen that can move. That’s it. In SFML this relationship is easily demonstrated by its class hierarchy.
There is one very key concept to understand with sprites, however. A sprite in SFML represents and image or texture on screen that can be moved around. However, it does not own the texture or image! This makes the Sprite class fairly light weight, which certainly isn’t true about Texture or Image, the classes that actually contain all the data in the image. Perhaps it’s easiest to start with a simple demonstration.
First, we need an image to work with. I am using a dragon sprite from the recent Humble Indie Gamedev Bundle. The image looks like so:
Obviously you can use whatever image you want, just be sure to copy it into the working directory of your application. In Visual Studio, the working directory can be located in the project’s properties panel under Debugging called Working Directory:
The image can be any of the following formats: bmp, hdr, gif, jpg, png, pic, psd, tga. Keep in mind, not all formats are created equal. Bitmap, for example, does not support transparency encoding and are generally quite large, but lose no image details and are simple to work with. Gif has some patent issues and should generally be avoided. Png seems like genuinely a good mix between features, size, and quality and is well supported by content creation tools.
Ok, enough setup, let’s get to some code.
// Demonstrate sprite drawing in SFML #include "SFML/Graphics.hpp" int main(int argc, char ** argv){ sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "Demo Game"); sf::Event event; sf::Texture texture; texture.loadFromFile("images/dragonBig.png"); sf::Sprite sprite(texture); while (renderWindow.isOpen()){ while (renderWindow.pollEvent(event)){ if (event.type == sf::Event::EventType::Closed) renderWindow.close(); } renderWindow.clear(); renderWindow.draw(sprite); renderWindow.display(); } }
And when you run that:
As you can see, the experience is remarkably consistent to drawing using graphics primitives. The big difference here is that we create our Sprite by providing a texture, which in turn we loaded from file with a call to Texture::loadFromFile(). There exist methods to load from stream or memory if preferred. It is again important to remember that the Sprite does not own the Texture. This means if the texture goes out of scope before the Sprite, the sprite will draw a blank rectangle. This also means that several sprites can use the same texture.
Now you may have noticed that in addition to sf::Texture, there is a class called sf::Image and you may be wondering why. There is one very simple difference at play here. A Texture resides in the memory of your graphics card, while an image resides in system memory. The act of copying an image from system memory to the GPU is quite expensive, so for performance reasons you almost certainly want to use Texture. That said, Texture isn’t easily modified, so if you are working on a dynamic texture or say, creating a screen shot, Image is the better choice. There exist methods to switch between the two types, but they are also fairly heavy in performance, so do not do them on a frame by frame basis. At the end of the day
Let’s take a quick look at creating a dynamic image next. Not something you are going to do often for most games granted, but it makes sense to mention it now.
// Demonstrate creating an Image #include "SFML/Graphics.hpp" int main(int argc, char ** argv){ sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "Demo Game"); sf::Event event; sf::Image image; image.create(640, 480, sf::Color::Black); bool isBlackPixel = false; sf::Color blackPixel(0,0,0,255); sf::Color whitePixel(255, 255, 255, 255); //Loop through each vertical row of the image for (int y = 0; y < 480; y++){ //then horizontal, setting pixels to black or white in blocks of 8 for (int x = 0; x < 640; x++){ if (isBlackPixel) image.setPixel(x, y, blackPixel); else image.setPixel(x, y, whitePixel); // Every 8th flip colour if (!(x % 8)) isBlackPixel = !isBlackPixel; } // Offset again on vertical lines to create a checkerboard effect if(!(y%8)) isBlackPixel = !isBlackPixel; } sf::Texture texture; texture.loadFromImage(image); sf::Sprite sprite(texture); while (renderWindow.isOpen()){ while (renderWindow.pollEvent(event)){ if (event.type == sf::Event::EventType::Closed) renderWindow.close(); } renderWindow.clear(); renderWindow.draw(sprite); renderWindow.display(); } }
When you run this example you should see:
Here you can see we can modify the pixels directly in our sf::Image. However, to display it on screen we still need to move it to texture and populate a sprite. The difference is direct access to the pixel data. Another important capability of sf::Image is the method saveToFile which enables you to well, save to file. Obviously useful for creating screenshots and similar tasks.
You may notice depending on the resolution or composition of your source image that you texture may not look exactly like your source image. This is because there is a smoothing or antialiasing filter built in to SFML to make images look smoother. If you do not want this, perhaps going for that chunky 8bit look, you can turn it off with a call to setSmooth(false);
That is all we are going to cover today. In the next tutorial part, we will take a look at spritesheets, so we can have multiple different sprites in a single source image.