-
Notifications
You must be signed in to change notification settings - Fork 27
Tutorial: 'Gallery Sprite' Basics
- Introduction | | Starting Point | | First Steps | | Texture / Font | | Drawing | | Exhibits | | Adding Exhibits | | Setting The Exhibit | | Animating | | Floor | | Anchors | | The Final Program
Gallery Sprite is a C++ class for displaying a sprite to a render window.
It works similarly to a standard SFML sprite but has some extra functionality that allows switching amongst different images by storing texture rectangles and linked anchor points internally.
Note that this tutorial makes use of C++11 features and Gallery Sprite itself uses C++11 features so a C++11 compatible compiler is required.
We're going to need a basic program to use as a starting point. The following program includes SFML and then uses it to create a window with a loop that continues until the window is closed, also processing the window's events:
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "Gallery Sprite Basics Tutorial");
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed:
window.close();
break;
}
}
window.clear();
window.display();
}
return EXIT_SUCCESS;
}
This is a simple, working SFML render window used for graphical displays and is the minimal required. If you use SFML, you should already be aware of this "skeleton" program.
The first things we need to do are include Gallery Sprite and create a Gallery Sprite object.
To include Gallery Sprite, we simply add:
#include <SelbaWard/GallerySprite.hpp>
To create an instance of a Gallery Sprite object, we simply use:
sw::GallerySprite sprite;
and "sprite" becomes the name of our new Gallery Sprite object.
Gallery Sprite requires an sf::Texture to be useful. It uses different parts of this texture to show the different images.
For this tutorial, we're going to use "Person Walk Animation SpriteSheet.png", which is provided in Selba Ward's example/resources/ folder:
Note that the image shown here was scaled up so it can be seen clearly.
We create and load the texture as normal:
sf::Texture texture;
if (!texture.loadFromFile("PATH/Person Walk Animation SpriteSheet.png"))
return EXIT_FAILURE;
Note that "PATH" is the absolute or relative directory in which you stored the image file.
Now that we have a texture, we need to tell the Gallery Sprite to use it:
sprite.setTexture(texture);
Drawing the sprite now will show the entire texture.
As with a standard SFML sprite, a Gallery Sprite can take an sf::Texture as a parameter to its constructor. This is a shortcut for a default construction along with settings its texture to that sf::Texture. In the final code for this tutorial, you may notice that we have used this shortcut instead.
To see that screen, it needs to be drawn to the window. A Gallery Sprite can be drawn like any standard SFML drawable:
window.draw(sprite);
If you run the program so far, you can see see a small image (similar to that one above) of all the frames of the animation at the top-left of the window. Gallery Sprite shows textures at their original size by default.
We are going to scale it so it's easier to see:
sprite.setScale({ 10.f, 10.f });
This is identical to the way standard SFML drawables are transformed. That is, you can scale, rotate, position the sprite in the same way; this includes setting the origin, although what the origin represents depends on its anchor, as we will see later.
Exhibits are the essence of Gallery Sprite.
Each Gallery Sprite can contain any number of Exhibits and the Exhibit used can be changed at any time, resulting in a different image showing.
IMPORTANT: Exhibits are specified by their index but note that they start at one, not zero. e.g. the first exhibit has an index of 1 and the eighth exhibit has an index of 8.
An Exhibit is two things:
- a rectangle (sf::FloatRect) representing which part of the texture to show
- and an anchor (sf::Vector2f) representing which part of the rectangle should be considered the origin.
The walk animation image we are using for this example has eight frames so we need to add eight exhibits:
sprite.addExhibit({ { 1.f, 1.f, 12.f, 21.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 14.f, 1.f, 9.f, 20.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 24.f, 1.f, 6.f, 21.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 31.f, 1.f, 9.f, 22.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 41.f, 1.f, 12.f, 21.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 54.f, 1.f, 9.f, 20.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 64.f, 1.f, 6.f, 21.f }, { 0.f, 0.f } });
sprite.addExhibit({ { 71.f, 1.f, 9.f, 22.f }, { 0.f, 0.f } });
The outer set of braces/curly brackets contain the Exhibit, the first inner set (before the comma) contains the sf::FloatRect values (x, y, width, height) for the rectangle and the second inner set (after the comma) contains the sf::Vector2f values (x, y) for the anchor. We have set the anchor to (0, 0) explicitly here to show how they are added. We will be changing them later. If you don't need the anchor to be anything other than (0, 0), you can omit it here and just provide the rectangle. Note that the final program will omit these anchors as they will be set separately.
To choose which exhibit that the Gallery Sprite should show, you use the Gallery Sprite's set method.
To set the sprite to use the first exhibit, we use:
sprite.set(1);
You can also set the exhibit index by assigned the index directly to the sprite e.g.
sprite = 1;
(from v1.2 only)
If you run the program now, you will see the first frame of the animation displayed tight in the top-left corner of the window.
You can change the exhibit by setting an index directly or by increase or decreasing the index using operators directly on the Gallery Sprite object. For example, you can use
++
to increase (++sprite
) and--
to decrease (--sprite
) the exhibit index by one or you can increase or decrease by any number using+=
or-=
with the amount of offset.
In this example, however, we will be setting the index directly.
To animate through the exhibits, we can simply set which exhibit to show based on a clock; we can use an sf::Clock for this:
sf::Clock clock;
The clock, as with all the code so far apart from the draw call, should be created outside and before the window loop (
while (window.isOpen())
)
Now, inside the main window loop (but, of course, outside of the event loop), we update the sprite based on the clock. We do this before the window is updated (clear, draw, display).
First we set up a value representing how long - in milliseconds - each frame should last:
constexpr unsigned int numberOfMillisecondsPerFrame{ 500u };
This value of 500 means that each frame will be shown for half of a second before the next one is shown. This is slow for an animation but it useful for seeing each frame clearly.
Then, we calculate which exhibit to use and set it for sprite:
sprite.set((clock.getElapsedTime().asMilliseconds() / numberOfMillisecondsPerFrame) % sprite.getNumberOfExhibits + 1);
Using the % operator here allows us to cycle through the number of exhibits. Note also the +1 that modifies the index to begin at one instead of zero. If an exhibit index of zero is used, the entire texture is shown.
Note also that since we're setting the exhibit here for every cycle of the loop, we no longer need the exhibit setting earlier on so the
sprite.set(1);
from earlier can be removed.
Run the program now and you can see the animation.
You may notice, however, that it doesn't animate that smoothly; that is because each frame is shown in the top-left of the window but the exhibits are different sizes and need to be moved so that they align with each other. That is the job for the anchor!
Before we fix our animation's anchors, we can add a 'floor' to the scene. That is, we are going to add a large rectangle onto which the person can walk.
We are going to use an sf::RectangleShape to represent the floor and it will cover the bottom half of the window:
sf::RectangleShape floor;
floor.setFillColor(sf::Color(64, 64, 64));
floor.setSize(sf::Vector2f(window.getSize()));
floor.setScale({ 1.f, 0.5f });
floor.setPosition({ 0.f, window.getSize().y / 2.f });
That code can go before the creation of the Gallery Sprite object but must be after the creation of the window. The floor will be a darker grey than the background.
Don't also forget to draw the floor (before the sprite):
window.draw(floor);
If you draw the floor after the sprite, you won't be able to see the effects of the next part of the tutorial!
Run the program and make sure the floor is as expected.
Anchors are offsets within an exhibit's rectangle and the location of an exhibit's anchor is what is shown at a sprite's origin. Anchors are used to align exhibits and/or reposition the main point of exhibits.
Exhibits' anchors are, by default, all set to (0, 0). In fact, earlier, we actually explicitly set them all to (0, 0) anyway. However, before we change the anchors, we should reposition the sprite so we see its effects.
Move it to the centre of the window:
sprite.setPosition(sf::Vector2f(window.getSize() / 2u));
Run the program again and notice that the same odd animation that was in the corner is now near the centre of the window.
It's not in the centre because the anchors of the exhibits are, by default, at its top-left corner. This means that, currently, the origin is always at the top-left of the sprite, which, in turn, is in the centre of the window.
Okay, good.
After noticing that the entire animation fits under the floor's top, we can set the anchors to the correct position - the point in each exhibit that should be on the floor directly below the centre of the body:
sprite.setAnchor(1, { 6.f, 21.f });
sprite.setAnchor(2, { 5.f, 20.f });
sprite.setAnchor(3, { 3.f, 21.f });
sprite.setAnchor(4, { 5.f, 22.f });
sprite.setAnchor(5, { 6.f, 21.f });
sprite.setAnchor(6, { 5.f, 20.f });
sprite.setAnchor(7, { 3.f, 21.f });
sprite.setAnchor(8, { 5.f, 22.f });
This code must go after adding the exhibits.
After adding this magical piece of code, change the numberOfMillisecondsPerFrame to 100 (from 500) and then run the code again.
Perfect! Now the animation is smooth with all of the exhibits aligned perfectly and the animation is above the floor with its feet perfectly planting on its top surface!
Magical.
That concludes the tutorial on the basics.
#include <SFML/Graphics.hpp>
#include <SelbaWard/GallerySprite.hpp>
int main()
{
sf::Texture texture;
if (!texture.loadFromFile("PATH/Person Walk Animation SpriteSheet.png"))
return EXIT_FAILURE;
sf::RenderWindow window(sf::VideoMode(800, 600), "Gallery Sprite Basics Tutorial");
sf::RectangleShape floor;
floor.setFillColor(sf::Color(64, 64, 64));
floor.setSize(sf::Vector2f(window.getSize()));
floor.setScale({ 1.f, 0.5f });
floor.setPosition({ 0.f, window.getSize().y / 2.f });
sw::GallerySprite sprite(texture);
sprite.setScale({ 10.f, 10.f });
sprite.setPosition(sf::Vector2f(window.getSize() / 2u));
sprite.addExhibit({ { 1.f, 1.f, 12.f, 21.f } });
sprite.addExhibit({ { 14.f, 1.f, 9.f, 20.f } });
sprite.addExhibit({ { 24.f, 1.f, 6.f, 21.f } });
sprite.addExhibit({ { 31.f, 1.f, 9.f, 22.f } });
sprite.addExhibit({ { 41.f, 1.f, 12.f, 21.f } });
sprite.addExhibit({ { 54.f, 1.f, 9.f, 20.f } });
sprite.addExhibit({ { 64.f, 1.f, 6.f, 21.f } });
sprite.addExhibit({ { 71.f, 1.f, 9.f, 22.f } });
sprite.setAnchor(1, { 6.f, 21.f });
sprite.setAnchor(2, { 5.f, 20.f });
sprite.setAnchor(3, { 3.f, 21.f });
sprite.setAnchor(4, { 5.f, 22.f });
sprite.setAnchor(5, { 6.f, 21.f });
sprite.setAnchor(6, { 5.f, 20.f });
sprite.setAnchor(7, { 3.f, 21.f });
sprite.setAnchor(8, { 5.f, 22.f });
sf::Clock clock;
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed:
window.close();
break;
}
}
constexpr unsigned int numberOfMillisecondsPerFrame{ 100u };
sprite.set((clock.getElapsedTime().asMilliseconds() / numberOfMillisecondsPerFrame) % sprite.getNumberOfExhibits() + 1);
window.clear(sf::Color(128, 128, 128));
window.draw(floor);
window.draw(sprite);
window.display();
}
return EXIT_SUCCESS;
}