SFML - How To Control Animation Speed

Pages: 12
I'm learning SFML at the moment. One of the things I've managed to do so far is animate through a spritesheet and move those animations around a window. The problem is that the player animation cycles through too quickly. I understand that to control how fast the animations occur, I need to use either one or both of sf::Time and sf::Clock but I don't know in what way. I've looked at the SFML tutorial on Time but it doesn't cover this issue well. Would someone be willing to help me figure out how to control the animation speed?

Here's what I have so far:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
    // Render a Window
    sf::RenderWindow window(sf::VideoMode(960, 540), "Window");
    sf::Texture spritesheet;
    sf::Sprite player;

    float pixelWidth = 0, pixelHeight = 0;
    int offset = 0, counter = 0;

    if(!spritesheet.loadFromFile("player.png"))
    {
        std::cout << "Could not load player.png.\n";
    }
    else
    {
        player.setTexture(spritesheet);

        pixelWidth = 24.5;
        pixelHeight = 32;
    }

    player.setTextureRect(sf::IntRect(pixelWidth, pixelHeight * 6, pixelWidth, pixelHeight));

    while(window.isOpen())
    {
        sf::Event event;

        while(window.pollEvent(event))
        {
            switch(event.type)
            {
                case sf::Event::Closed:
                {
                    window.close();

                    break;
                }

                case sf::Event::KeyPressed:
                {
                    if(event.key.code == sf::Keyboard::Escape)
                    {
                        window.close();
                    }
                }
            }// end switch()

            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 4, pixelWidth, pixelHeight));

                player.move(0, -5);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 6, pixelWidth, pixelHeight));

                player.move(0, 5);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 7, pixelWidth, pixelHeight));

                player.move(-5, 0);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 5, pixelWidth, pixelHeight));

                player.move(5, 0);

                offset++;
            }

            if(offset >= 3)
            {
                offset = 0;
            }

            window.clear(sf::Color::Black);

            window.draw(player);

            window.display();
        }
    }

    return 0;
}
I havent used SFMl exactly, but usually it boils down to this:
1
2
3
4
5
6
7
8
9
10
11
sf::Clock clock;
sf::Time prevTime = clock.getElapsedTime();
...
conts float playerVelocity = 50; //in pixels per second

while(window.isOpen())
    sf::Time elapsedTime = clock.getElapsedTime() - prevTime;
    prevTime += elapsedTime;

    ...
    player.move(playerVelocity*elapsedTime.asSeconds(), 0);


Thanks Kevin C. I tried what you suggested but it doesn't work. The player cycles through the animations just as quickly, except now the player no longer moves around the screen.

Here's what I changed the code to:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
    // Render a Window
    sf::RenderWindow window(sf::VideoMode(960, 540), "Window");
    sf::Texture spritesheet;
    sf::Sprite player;
    sf::Clock clock;
    sf::Time prevTime = clock.getElapsedTime();

    const float velocity = 50;

    float pixelWidth = 0, pixelHeight = 0;
    int offset = 0, counter = 0;

    if(!spritesheet.loadFromFile("player.png"))
    {
        std::cout << "Could not load player.png.\n";
    }
    else
    {
        player.setTexture(spritesheet);

        pixelWidth = 24.5;
        pixelHeight = 32;
    }

    player.setTextureRect(sf::IntRect(pixelWidth, pixelHeight * 6, pixelWidth, pixelHeight));

    while(window.isOpen())
    {
        sf::Time elapsedTime = clock.getElapsedTime() - prevTime;
        prevTime = prevTime + elapsedTime;

        sf::Event event;

        while(window.pollEvent(event))
        {
            switch(event.type)
            {
                case sf::Event::Closed:
                {
                    window.close();

                    break;
                }

                case sf::Event::KeyPressed:
                {
                    if(event.key.code == sf::Keyboard::Escape)
                    {
                        window.close();
                    }
                }
            }// end switch()

            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 4, pixelWidth, pixelHeight));

                player.move(0, -velocity*elapsedTime.asSeconds());

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 6, pixelWidth, pixelHeight));

                player.move(0, velocity*elapsedTime.asSeconds());

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 7, pixelWidth, pixelHeight));

                player.move(-velocity*elapsedTime.asSeconds(), 0);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 5, pixelWidth, pixelHeight));

                player.move(velocity*elapsedTime.asSeconds(), 0);

                offset++;
            }

            if(offset >= 3)
            {
                offset = 0;
            }

            window.clear(sf::Color::Black);

            window.draw(player);

            window.display();
        }
    }

    return 0;
}

Any ideas why this doesn't work?
Yeah. Instead of putting those three lines at the beginning of while(window.isOpen()), put them at the beginning of the innermost while loop.
It's not working. The player behaves as before, except now there are random jumps in space when pressing the directional keys individually. When the keys are held, there's no difference from how it was. I appreciate your help though Kevin and I'm sure there's a solution to this.
You now need to apply the elapsedTime to animations. So somewhere in main():

1
2
sf::Time animationTime;
int frameTime = 50; //in milliseconds, bigger number for slower animations 


instead of offset++:
1
2
3
4
5
6
7
animationTime += elapsedTime();
if (animationTime.asMilliseconds() > frameTime)
{
     int advanceFrames = animationTime.asMilliseconds() / frameTime;
     offset += advanceFrames;
     animationTime -= milliseconds(frameTime)*advanceFrames;
}



.. but the exact details depand on what you want to do.

Last edited on
And also, perhaps you should move

1
2
sf::Time elapsedTime = clock.getElapsedTime() - prevTime;
prevTime = prevTime + elapsedTime;


back to the outer while loop, and replace the inner while with an if. So:

if(window.pollEvent(event))

... that should eliminate the jumps.
http://stackoverflow.com/questions/20957630/my-sfml-c-program-is-working-as-expected-but-how-to-make-movement-slow/20958283#20958283

That should help (First Answer) deal with your problem even though the post itself relates to movement the same concept applies to animation though with a few minor tweaks that you should be able to figure out with a bit of research.
Last edited on
Guys I'm sorry but I'm lost. I can't wrap my head around the formulas you've shown me. Maybe I should let you know exactly where I'm at.

To start off with, I decided I wanted to learn SFML to mess around with making games. I've learned a few of the basics, such as rendering a window and processing events for the window, such as closing it. I can load a spritesheet onto a texture and load that texture onto a sprite. I can specify the rectangle of the spritesheet that I want to display, in this case being that of a person facing in different directions. I can make this person move around the window and finally, I have managed to make the person cycle through their walking animations as they move.

This actually makes me a little bit proud, just being able to do these simple things.

The problem I encountered, as you know, is that the person cycles through the poses far too quickly. Being dissatisfied with this, I looked up how to slow down sprite animations. I learned that the reason the sprite cycles through its animations so quickly is because the computer is flying through the animations as quickly as its processor will allow and it needs to be measured using timed intervals.

This is the point where I'm unclear on how to proceed. I read about sf::Time and sf::Clock and how they can be used to store and manipulate the amount of time that has passed, but I'm confused as to how to utilise them for my needs. I know that you've shown me some formulas, but I cannot grasp them at all. I've looked all around online and it's mostly the same, incromprehensible formulas to me, involving vectors and velocity and frames and all these things I don't understand.

I would be hugely appreciative if someone here would explain to me, from the beginning and in small steps, what must be done to slow down sprite animation in SFML, and perhaps the meaning of the terms used, ie, frames. I'd just like to say as well, thank you for your patience. I am trying and it's frustrating not being able to understand what I'm being shown. I hope someone will help me with this.
Last edited on
Those are all very simple formulas. What is it that you don't understand?

Are you sure that you have got enough programming experience to use SFML? Perhaps you jumped into it too early.
Yes I'm sure I have enough experience to use SFML. Everything up to this has been fine.

I just need someone to clearly explain from the beginning, the concept of what it is I need to do, why it is done that way and some examples to go along with it. I need explicit detail. As it is I've been shown the code but not in a coherent way and without a detailed explanation.

Please don't take this as criticism of your help so far, I'm just outlining what it is I feel I need to get a foothold on this. Hopefully you understand.
Okay here is some code out the SFML Game Development book, it shows animating a spritesheet. I'll go over what it is doing step by step.

P.S. Ignore the bad formatting ;p. Visual Studio's formatting does not play nicely with github sometimes.

https://gist.github.com/bjumbeck/5d695bfb62313fab33d5

Now this is a wrapper class to make an animated sprite, the main functionality is inside of the update() method so lets go over that.

1
2
sf::Time timePerFrame = duration / static_cast<float>(numFrames); 
elapsedTime += dt; 


The first line what we are doing is getting the amount of time we want each frame of our animation to play. We do this by taking the duration (The total time we want the animation to play for) and dividing it by the number of frames are in that animation sequence.

Next we are adding the delta time to our elapsed time. The deltatime is how many seconds has passed since the last frame, by last frame I mean the last time our main game loop has ran, not our last animation frame. The elapsed time variable is used to track how much time has passed since we last ran any of the animation updating code (The stuff inside of the while loop below).

1
2
if (currentFrame == 0) 
    textureRect = sf::IntRect(0, 0, frameSize.x, frameSize.y); 


This code should be obvious but just in case it is saying if our currentFrame variable is saying it is the first frame we will change our texture rectangle so that it is positioned on the first frame of the sprite sheet.

while (elapsedTime >= timePerFrame && (currentFrame <= numFrames || repeat))

Now this line of code is how we slow down our animation so to speak. Remember that elapsedTime is the total amount of time since we last ran our animation code (The code inside of this while loop) and timePerFrame is the the amount of time we want each frame to have.

So with that code we will only run our animation update code which will do the actual spirtesheet animation only when our elapsedTime variable is greater than or equal to timePerFrame.

The next part is to handle repeating the animation so it is looping and stopping the animation if it is not. This is pretty much what you wanted to know and will handle slowing down your animation. But I will go over the other parts briefly also.

1
2
3
4
5
6
7
textureRect.left += textureRect.width;

if (textureRect.left + textureRect.width > textureBounds.x)
{
    textureRect.left = 0;
    textureRect.top += textureRect.height;
}


Now you should be familiar with most of this code since it is just moving the texture rectangle around on the spritesheet so that different images are displayed.

The first line is basically just moving the texture rectangle one image to the right (Note this assumes that all sprites in the spritesheet are aligned in a row/column nature and have the same dimension).

The next set of code just handles moving the texture rectangle down a row if it is at the last column of the spritesheet.

elapsedTime -= timePerFrame;

Now this line is very important, remember in the beginning when I said that elapsedTime is what is in charge of keeping track of how much time has pasted since we last ran this while loop animation code? Well now that we are in the while loop and doing our animation code we need to reset that.

So what we do is minus the time it takes for one frame of our animation to run (Since we are updating our animation frame with this code).

1
2
3
4
5
6
7
8
9
10
11
if (repeat)
{
    currentFrame = (currentFrame + 1) % numFrames;

    if (currentFrame == 0)
        textureRect = sf::IntRect(0, 0, frameSize.x, frameSize.y);
}
else
{
    ++currentFrame;
}


This bit of code is just handling animation repeating/looping. It is basically saying if we have checked the repeat flag it will run through the code need to repeat the animation if we are on the last frame of the animation. Otherwise if the repeat flag is false we just go to the next frame (Other code handles the animation ending if it is past the last frame).

sprite.setTextureRect(textureRect);

And finally outside of the while loop we run this code which will set the texture rectangle of the sprite which is a member of our class to the correct image in the spritesheet.


And that is about it, you don't have to put all this in a class like the SFML book did, but at least this should show you how your problem can be solved. The main part that you want to look at is these

1) Everything above the while loop
2) The while loop condition
3) elapsedTime -= timePerFrame;

All those pertain to the problem you are facing.

Hopefully I did a decent job of explaining the code, been a bit since I have used SFML so am a bit rusty ;p. If you have any questions of don't understand anything from here just let me know.
Last edited on
First of all I would like to say thank you very much Zereo, as I can see that you put a lot of effort into that explanation. However, I'm still confused. I've studied the code you gave me and even with your explanation I still don't get it. The whole thing has me stumped.

Would it be possible for you to modify my code, without using a class, so that the time lapse works? It might help just to see it complete and in action. I had another go at getting it to work, but i can't wrap my head around it.

Here's my rubbish attempt. The comments are to try to explain what I'm doing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

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

int main()
{
    // Variables to store individual sprite dimensions
    int pixelWidth = 0;
    int pixelHeight = 0;

    // Render a window
    sf::RenderWindow window(sf::VideoMode(960, 540), "Window");

    // Instantiate Texture object to hold spritesheet
    sf::Texture spritesheet;

    // Instantiate Sprite object to hold texture
    sf::Sprite player;

    // Instantiate Time object to store elapsed time between frames
    sf::Time elapsedTime;

    // Load spritesheet onto texture
    if(!spritesheet.loadFromFile("player.png"))
    {
        std::cout << "player.png could not be loaded." << std::endl;
    }
    else
    {
        // Apply loaded texture onto sprite
        player.setTexture(spritesheet);

        // Set sprite dimensions
        pixelWidth = 32;
        pixelHeight = 32;

        // Set players starting position
        player.setTextureRect(sf::IntRect(pixelWidth, pixelHeight * 0, pixelWidth, pixelHeight));
    }

    // Start main game loop (each loop is a full frame)
    while(window.isOpen())// Beginning of new frame
    {
        // Instantiate Clock object to keep track of time passed
        sf::Clock clock;

        // Flag to determine whether or not to animate player
        bool animate = false;

        // Offset to be incremented to determine what rectangle of the spritesheet is being displayed
        int offset = 0;

        // elapsedTime will accumulate the time that has passed so far
        elapsedTime = clock.getElapsedTime();

        // Only if one second has passed
        if(elapsedTime.asSeconds() > 1)
        {
            // ...will the clock restart
            clock.restart();

            // And the srite will be able to animate
            animate = true;
        }

        // Instantiate Event object to process window events (closing, resizing...)
        sf::Event event;

        // While the window processes events
        while(window.pollEvent(event))
        {
            // Close the window
            if(event.type == sf::Event::Closed)
            {
                window.close();
            }

            if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
            {
                window.close();
            }
        }

        // After the window has processed its events

        if(animate == true)
        {
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 3, pixelWidth, pixelHeight));

                player.move(0, -5);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 0, pixelWidth, pixelHeight));

                player.move(0, 5);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 1, pixelWidth, pixelHeight));

                player.move(-5, 0);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 2, pixelWidth, pixelHeight));

                player.move(5, 0);

                offset++;
            }

            if(offset >= 3)
            {
                offset = 0;
            }

        }

        // Clear the window with the colour black
        window.clear(sf::Color::Black);

        // Draw the player to the screen
        window.draw(player);

        // Display the newly updated screen
        window.display();
    }// End of current frame

    return 0;
}
Since I couldn't make sense of how time was being implemented, I went back over sf::Time and sf::Clock outside of the context of the program, just to play around with them and get a feel for how I could work with them. While doing this, my brain must have switched on for a moment because I got an idea for slowing down the sprite animations - and it works!

Well kind of... you see the animations have indeed slowed down, which is great, but now the movement feels very jagged unless I set the timePerFrame variable to something like a sixtieth of a second. This defeats the purpose though, as then the animations also display sixty times per second.

Do you guys know how I might fix this?

Here's what I've got:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <iostream>
#include <SFML/Graphics.hpp>

int main()
{
    // Six sprite frames will be displayed per second
    const float timePerFrame = 1.0 / 6.0;

    // Variables to store individual sprite dimensions
    int pixelWidth = 0;
    int pixelHeight = 0;

    // Offset is incremented to determine what rectangle of the spritesheet is being displayed
    int offset = 0;

    // Render a window
    sf::RenderWindow window(sf::VideoMode(960, 540), "Window");

    // Instantiate Texture object to hold spritesheet
    sf::Texture spritesheet;

    // Instantiate Sprite object to hold texture
    sf::Sprite player;

    // Instantiate Time object to store elapsed time between frames,
    sf::Time elapsedTime;

    // Instantiate Clock object to keep track of time passed
    sf::Clock clock;

    // Load spritesheet onto texture
    if(!spritesheet.loadFromFile("player.png"))
    {
        std::cout << "player.png could not be loaded." << std::endl;
    }
    else
    {
        // Apply loaded texture onto sprite
        player.setTexture(spritesheet);

        // Set sprite dimensions
        pixelWidth = 32;
        pixelHeight = 32;

        // Set players starting position
        player.setTextureRect(sf::IntRect(pixelWidth, pixelHeight * 0, pixelWidth, pixelHeight));
    }

    // Start main game loop (each loop is a full frame)
    while(window.isOpen())// Beginning of new frame
    {
        // elapsedTime will accumulate the time that has passed so far
        elapsedTime = clock.getElapsedTime();

        // Instantiate Event object to process window events (closing, resizing...)
        sf::Event event;

        // While the window processes events
        while(window.pollEvent(event))
        {
            // Close the window
            if(event.type == sf::Event::Closed)
            {
                window.close();
            }

            if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
            {
                window.close();
            }
        }

        // After the window has processed its events

        if(elapsedTime.asSeconds() >= timePerFrame)
        {
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 3, pixelWidth, pixelHeight));

                player.move(0, -10);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 0, pixelWidth, pixelHeight));

                player.move(0, 10);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 1, pixelWidth, pixelHeight));

                player.move(-10, 0);

                offset++;
            }
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            {
                player.setTextureRect(sf::IntRect(pixelWidth * offset, pixelHeight * 2, pixelWidth, pixelHeight));

                player.move(10, 0);

                offset++;
            }

            if(offset >= 3)
            {
                offset = 0;
            }

            // Reset elapsedTime
            elapsedTime = clock.restart();
        }

        // Clear the window with the colour black
        window.clear(sf::Color::Black);

        // Draw the player to the screen
        window.draw(player);

        // Display the newly updated screen
        window.display();
    }// End of current frame

    return 0;
}
You are moving the player (player.move) and advancing the animation (offset++) after each timePerFrame interval.

What you should be doing is to move the player at any time, just advance the animation after each timePerFrame interval.
I'm not sure I understand. Do you mean to remove player.move() and simply animate through the spritesheet? I'm pretty sure that would stop the player from moving around the screen.

If that's not what you meant, would you please clarify?
Don't remove player.move()

What I meant was that the statement:

if(elapsedTime.asSeconds() >= timePerFrame)

is currently applied to the entire block, but it should only be applied to the statement 'offset++'
Okay I tried that, but the player now flies off the screen.
For a quick fix, use:

player.move(0.5, 0);

or some other appropriate value.

But the only correct way to calculate the move distance is by calculating it from player velocity and elapsed time.
Last edited on
Using player.move() like that is exactly what I have been doing already.

Why would you multiply the pixel value by the elapsed time and velocity? Especially, what is the point of including the elapsed time?

Just to see I tried it and it made no difference. With a low velocity, the sprite cycles through its animations just fine and everything looks smooth, but the player hardly moves at all. With a high velocity, the movement becomes very jagged.

If you know how to keep movement smooth while moving around the screen at a good speed, would you show me by modifying the code I posted?
Pages: 12