-
Notifications
You must be signed in to change notification settings - Fork 30
Textures and Sprites
This chapter introduces how to load an image file and display it - or a part of it - on the screen. It will also go into frame animation.
The Texture class represents "images" that are stored in the graphics card's memory. From there, they can be accessed for quick rendering, but that also means that they cannot be modified directly (this applies to the Image class, which will be discussed later).
The following code sample loads the JSFML logo texture and prints its dimensions.
//Create a Texture instance
Texture jsfmlLogoTexture = new Texture();
try {
//Try to load the texture from file "jsfml.png"
jsfmlLogoTexture.loadFromFile(Paths.get("jsfml.png"));
//Texture was loaded successfully - retrieve and print size
Vector2i size = jsfmlLogoTexture.getSize();
System.out.println("The texture is " + size.x + "x" + size.y);
} catch(IOException ex) {
//Ouch! something went wrong
ex.printStackTrace();
}
If all goes well, the output should be 367x65
.
The most common use case to get a texture will be loading it from a file. This is done using the [loadFromFile](http://jsfml.org/javadoc/org/jsfml/graphics/Texture.html#loadFromFile(java.nio.file.Path\)) method. Note that it takes a Path object instead of a string containing the file name. Optionally, the method takes an IntRect that determines the part of the texture (in pixels) that should be loaded.
The [getSize](http://jsfml.org/javadoc/org/jsfml/graphics/Texture.html#getSize(\)) method returns the size, in pixels, of the texture (or the part of it) that was loaded.
The following image formats are supported: bmp, dds, jpg, png, tga, psd
A lot of things can go wrong when trying to load a texture from a file. The easiest example would be that the file does not exist or that it is not a supported image file format.
In such a case (unlike SFML), JSFML throws an IOException. If you are not familiar with exceptions and exception handling, there is [a nice article on exceptions] (http://www.akadia.com/services/java_exceptions.html) by Akadia AG.
Textures that are stored in the video memory aren't of much help if you cannot display them in some way - this is where the Sprite class comes in. You can think of a sprite as an "instance" of a texture in the scene. Let's say you have a texture of a coin for a Mario-style game. There will be many coin sprites displayed - but they all use the same texture.
Sprites are Drawables, so they can be drawn directly by a Window object. They are also Transformable, so you can position, rotate and scale them.
The following example will display the JSFML logo (which we loaded above) in the center of the screen and rotate it.
// ... (create window and load jsfmlLogoTexture)
//Create a sprite and make it use the logo texture
Sprite jsfmlLogo = new Sprite(jsfmlLogoTexture);
//Set its origin to its center and put it at the center of the screen
jsfmlLogo.setOrigin(Vector2f.div(new Vector2f(jsfmlLogoTexture.getSize()), 2));
jsfmlLogo.setPosition(320, 240);
//Create a frame clock for rotation
Clock frameClock = new Clock();
//Main loop
while(window.isOpen()) {
window.clear();
window.draw(jsfmlLogo);
window.display();
// ... (event handling)
//Get the frame time (dt stands for "delta time" - time difference since last frame)
float dt = frameClock.restart().asSeconds();
//Rotate the sprite 45 degrees clock-wise per second
jsfmlLogo.rotate(dt * 45);
}
Even though vectors have already been introduced, let us reiterate that one line:
Vector2f.div(new Vector2f(jsfmlLogoTexture.getSize()), 2)
To understand this, you need to recognize that there are two operations going on:
- We get the size of the texture (which is Vector2i) and convert it to a Vector2f using its [conversion constructor](http://jsfml.org/javadoc/org/jsfml/system/Vector2f.html#Vector2f(org.jsfml.system.Vector2i\)).
- We divide the vector coordinates by 2 using [div](http://jsfml.org/javadoc/org/jsfml/system/Vector2f.html#div(org.jsfml.system.Vector2f, float)).
So essentially, we pick half the size of the texture - its center.
When you saw the sprite rotating, you might have noticed how it has very rough edges. This is because when textures are rotated, by default, their pixels will be interpolated, and that's ugly. This will also happen if you scale the sprite or put it at non-integer positions (e.g. (14.7f, 3.5f)
).
This can be changed by activating smoothing on the texture. After the texture has been loaded, use the [setSmooth](http://jsfml.org/javadoc/org/jsfml/graphics/Texture.html#setSmooth(boolean\)) method to activate smoothing for it:
jsfmlLogoTexture.setSmooth(true);
Voilà, rotation now looks good!
Even though smoothing comes at barely any cost, don't use it for static textures that you will not scale, rotate and move smoothly. A good example for this is a tile map, as used in many platformers: smoothing every single tile will cause artifacts to occur between them.
One important feature is using a texture source rectangle. The idea is to have a large texture that contains different images, but only one of those images will be displayed by a sprite at a time.
There are many use cases for this: sprite sheets, tile sets, or simple collections of various HUD icons (which is a good idea, because using a lot of small textures is inherently slower than using few large textures).
In the example below, we will use this simple bomb explosion made by qubodup and bring it to life by making a simple frame animation.
//Create and try to load the explosion texture
Texture explosionTex = new Texture();
try {
explosionTex.loadFromFile(Paths.get("explosion.png"));
} catch(IOException ex) {
ex.printStackTrace();
}
//Create the explosion sprite and make it show the first frame only
Sprite explosion = new Sprite(explosionTex);
explosion.setTextureRect(new IntRect(0, 0, 64, 64));
//Keep a frame counter that tells us what frame we are currently displaying (we start counting at zero)
int frame = 0;
//Create a clock for an animation timer
Clock animClock = new Clock();
//Main loop
while(window.isOpen()) {
// ... (drawing and displaying)
// ... (event handling)
//Change the frame every 50 milliseconds
if(animClock.getElapsedTime.asMilliseconds() >= 50) {
//Restart the clock
animClock.restart();
//Increase the frame counter by one
frame++;
//The animation has 32 frames, when we surpass that, repeat
if(frame > 31) frame = 0;
//Calculate the position of the new frame and set it on the sprite
int frameRow = frame / 8;
int frameCol = frame % 8;
explosion.setTextureRect(new IntRect(frameCol * 64, frameRow * 64, 64, 64));
}
}
The [setTextureRect](http://jsfml.org/javadoc/org/jsfml/graphics/Sprite.html#setTextureRect(org.jsfml.graphics.IntRect\)) method sets the part of the texture that the sprite will display. The idea of "frame animation" is to change that rectangle in a short fixed interval (here: every 50 milliseconds) so it looks like an animation.
The explosion texture consists of 8x4 frames (32 total) that are 64x64 pixels each. This information will help us to understand these 3 lines of code:
int frameRow = frame / 8;
int frameCol = frame % 8;
explosion.setTextureRect(new IntRect(frameCol * 64, frameRow * 64, 64, 64));
Our texture is divided into 4 rows with 8 columns each. To find out in which row the current frame lies, we divide the frame number by the number of columns. The rest of the division (→ %
operator - modulo) is the column.
In the end, we need to multiply the row and column by the height / width of the frame - which is 64.
Shapes also provide the setTexture and setTextureRect methods. Instead of just filling them with a constant color, you can have them filled by a texture.