Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Animating Sprites with different numbers of frames efficiently.  (Read 4190 times)

0 Members and 1 Guest are viewing this topic.

Bogdan

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Animating Sprites with different numbers of frames efficiently.
« on: September 08, 2016, 08:19:47 pm »
Hi guys,
my question is about how to animate sprites with different numbers of frames efficiently. (in one loop and with less variables than in my example)
Let's say I've got object1 (300x100 pixels) with 3 frames and object2 (400*100 pixels) with 4 frames.
The animation should work as follows: (go 1 frame forward per second until end an then start over again)
object 1: 1 2 3 1 2 3 1 2 3 1 2 3....
object 2: 1 2 3 4 1 2 3 4 1 2 3 4....
Is it possible to rewrite the code, that I won't need multiple clocks and so many variables for it to work properly.

#include <SFML/Graphics.hpp>

int main()
{
        std::vector<sf::Sprite> AnimatedObjectsVector;

        sf::Texture textureanim;
        textureanim.loadFromFile("object1.png");
        sf::Sprite spriteanim(textureanim);
        spriteanim.setPosition(200,200);
        AnimatedObjectsVector.push_back(spriteanim);

        sf::Texture textureanim2;
        textureanim2.loadFromFile("object2.png");
        sf::Sprite spriteanim2(textureanim2);
        spriteanim2.setPosition(300,200);
        AnimatedObjectsVector.push_back(spriteanim2);

    sf::RenderWindow window(sf::VideoMode(1200,900), "Map", sf::Style::Close);                 
        window.setFramerateLimit(30);
       
        sf::Time time;
        sf::Clock clock;
        sf::Clock clock2;

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

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

                int sec  = clock.getElapsedTime().asSeconds();
                int sec2 = clock2.getElapsedTime().asSeconds();
                int animator = sec*100;
                int animator2 = sec2*100;
                                                                               
                if(animator>200)
                {
                        animator = 0;
                        clock.restart();
                }

                if(animator2>300)
                {
                        animator2 = 0;
                        clock2.restart();
                }

                for (auto& it = AnimatedObjectsVector.begin(); it != AnimatedObjectsVector.end(); ++it)
                {
                        if(it->getTexture()==&textureanim)
                        {
                                it->setTextureRect(sf::IntRect(animator, 0, 100, 100));
                        }
                        if(it->getTexture()==&textureanim2)
                        {
                                it->setTextureRect(sf::IntRect(animator2, 0, 100, 100));
                        }
                }
                window.clear();
                for (auto& it = AnimatedObjectsVector.rbegin(); it != AnimatedObjectsVector.rend(); ++it)
               {
                        window.draw(*it);
               }
               window.display();
       }
       return 0;
}
 
« Last Edit: September 08, 2016, 08:25:50 pm by Bogdan »

Bogdan

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: Animating Sprites with different numbers of frames efficiently.
« Reply #1 on: September 08, 2016, 09:42:17 pm »
Ok, I've found a method to make it slightly shorter. Is there any way to make it even shorter?

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

struct AnimObjectsStruct
{
        sf::Sprite spriteanimobject;
        int individualanimator;
        int maximalframe;
        int individualsec;
        sf::Clock individualclock;
};

int main()
{
        sf::Time time;
        sf::Clock clocksuper;

        std::vector<AnimObjectsStruct> AnimatedObjectsVector;

        sf::Texture textureanim;
        textureanim.loadFromFile("object1.png");
        sf::Sprite spriteanim(textureanim);
        spriteanim.setPosition(200,200);

        AnimObjectsStruct AnimObjectsStructMaker1 = {spriteanim,0,2,0,clocksuper};
        AnimatedObjectsVector.push_back(AnimObjectsStructMaker1);

        sf::Texture textureanim2;
        textureanim2.loadFromFile("object2.png");
        sf::Sprite spriteanim2(textureanim2);
        spriteanim2.setPosition(300,200);

        AnimObjectsStruct AnimObjectsStructMaker2 = {spriteanim2,0,3,0,clocksuper};
        AnimatedObjectsVector.push_back(AnimObjectsStructMaker2);

    sf::RenderWindow window(sf::VideoMode(1200,900), "Map", sf::Style::Close);                 
        window.setFramerateLimit(30);
       
    while (window.isOpen())
    {
        sf::Event event;

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

                for (auto& it = AnimatedObjectsVector.begin(); it != AnimatedObjectsVector.end(); ++it)
                {
                        it->individualsec = it->individualclock.getElapsedTime().asSeconds();
                        it->individualanimator = it->individualsec*100;

                        if(it->individualanimator>it->maximalframe*100)
                        {
                                it->individualanimator = 0;
                                it->individualclock.restart();
                        }

                        it->spriteanimobject.setTextureRect(sf::IntRect(it->individualanimator, 0, 100, 100));
                }

        window.clear();
                for (auto& it = AnimatedObjectsVector.rbegin(); it != AnimatedObjectsVector.rend(); ++it)
        {
                        window.draw(it->spriteanimobject);
        }
        window.display();
    }
    return 0;
}

Hapax

  • Hero Member
  • *****
  • Posts: 3353
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Animating Sprites with different numbers of frames efficiently.
« Reply #2 on: September 08, 2016, 09:48:11 pm »
You don't need a clock for each animation. You can just store and use the times for each animation using the same clock.

Loose example code:
sf::Clock clock;
sf::Time animation1FrameStartTime{ sf::Time::Zero };
sf::Time animation2FrameStartTime{ sf::Time::Zero };
sf::Time animation1FrameDuration{ sf::seconds(1.f / 12.f) }; // 12 frames per second
sf::Time animation2FrameDuration{ sf::seconds(1.f / 15.f) }; // 15 frames per second
// ...
    // main loop
    // ...
    currentTime = clock.getElapsedTime();
    if (currentTime - animation1FrameStartTime >= animation1FrameDuration)
    {
        increaseAnimation1Frame;
        animation1FrameStartTime = currentTime;
    }
    if (currentTime - animation2FrameStartTime >= animation2FrameDuration)
    {
        increaseAnimation2Frame;
        animation2FrameStartTime = currentTime;
    }
This way, the clock can be left to continue, the frame durations are how long each frame should be shown (you might decide to not use different values for each animation), and the frame start times keep track of when the current frame was started so you need one for each animation.

You may want to allow for times that pass the animation duration as that amount is currently ignored. You could use an accumulator or just increase the frame start time by the frame duration for each increase of the frame. Remember that it's possible that multiple animation frames may need to processed in just one loop cycle.

I'm not sure that "too many variables" is a bad thing. It's just keeping track of things that you need. You can tidy up things by grouping them into structs/classes.
Similarly, "shorter" isn't necessarily better either.



Since the frames in your example are consecutive frame numbers, you can calculate the frame by using the remainder from a division. Assuming that you still want to show one frame per second:
animation1frameNumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds()) % 3 + 1; // 3 because there are 3 frames.
animation2frameNumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds()) % 4 + 1; // 3 because there are 3 frames.
Just leave the clock running and that will calculate the frame number. Note that this only works with consecutive frame numbers. Other sequences will need other calculations. Note also the addition of 1; this is because the remainder will start from 0 but the frame numbers start from 1.

Short enough for you?  ;)

You can, of course, add other speed here by multiplying the time:
animation1frameNumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds() * 12.f) % 3 + 1; // 12 frames per second
animation2frameNumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds() * 15.f) % 4 + 1; // 15 frames per second

Note that this method assumes that the animation is linked to the clock and its position in the sequence will always be linked to the clock's time. You can, of course, use a pausable clock or keep track of time separately and use that instead in the calculations.




I probably should share this too:
If you use Plinth (in particular, Plinth's SFML addition), you can use its "Frame Sequence" to animate these sorts of things automatically. Each frame of the animation is a unique key frame that can have the "frame ID" (number of which frame to display) as well as individual offsets, rotations etc.. Each frame of the animation can have its own duration to allow irregular frame rates and delays. Even the same frame ID used multiple times can have its own offsets etc.. In addition to this, all sequences can be set to not loop, loop in a cycle (which matches your examples), or loop in a ping pong fashion - as well as play in reverse. The current position of the sequence can be set by frame position or time, which would simplify working with clocks :)
« Last Edit: September 09, 2016, 12:34:01 am by Hapax »
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Bogdan

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: Animating Sprites with different numbers of frames efficiently.
« Reply #3 on: September 09, 2016, 10:02:58 am »
Ok, many thanks for your help and inspiration.
My code looks now very "clean". :-)

#include <SFML/Graphics.hpp>

int main()
{
        sf::Time time;
        sf::Clock clock;

        std::vector<sf::Sprite> AnimatedObjectsVector;
       
        sf::Texture textureanim1;
        textureanim1.loadFromFile("object1.png");
        sf::Sprite spriteanim1(textureanim1);
        spriteanim1.setPosition(200,200);
       
        AnimatedObjectsVector.push_back(spriteanim1);
       
        sf::Texture textureanim2;
        textureanim2.loadFromFile("object2.png");
        sf::Sprite spriteanim2(textureanim2);
        spriteanim2.setPosition(300,200);

        AnimatedObjectsVector.push_back(spriteanim2);

    sf::RenderWindow window(sf::VideoMode(1200,900), "Map", sf::Style::Close);                 
        window.setFramerateLimit(30);
       
    while (window.isOpen())
    {
        sf::Event event;

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

                int currenttimeint = clock.getElapsedTime().asSeconds();
                int animation1framenumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds()) % 3;
                int animation2framenumber = static_cast<unsigned int>(clock.getElapsedTime().asSeconds()) % 4;

                for (auto& it = AnimatedObjectsVector.begin(); it != AnimatedObjectsVector.end(); ++it)
                {
                        if(it->getTexture()==&textureanim1)
                        {
                                it->setTextureRect(sf::IntRect(animation1framenumber*100, 0, 100, 100));
                        }
                        if(it->getTexture()==&textureanim2)
                        {
                                it->setTextureRect(sf::IntRect(animation2framenumber*100, 0, 100, 100));
                        }
                }

        window.clear();
                for (auto& it = AnimatedObjectsVector.rbegin(); it != AnimatedObjectsVector.rend(); ++it)
        {
                        window.draw(*it);
        }
        window.display();
    }
    return 0;
}

Hapax

  • Hero Member
  • *****
  • Posts: 3353
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Animating Sprites with different numbers of frames efficiently.
« Reply #4 on: September 09, 2016, 03:11:40 pm »
You don't need to get the clock's elapsed time more than once. You can just use "currenttimeint" each time for the frame calculations.

And, you're welcome :)

Note, though, that your code will only use columns for that animation frame. You may want to use rows too ;)
You may find Selba Ward's Gallery Sprite useful; it stores all the texture rects and can then change which one it uses by index ;)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Bogdan

  • Jr. Member
  • **
  • Posts: 93
    • View Profile
Re: Animating Sprites with different numbers of frames efficiently.
« Reply #5 on: September 12, 2016, 01:22:06 pm »
Ok, thanks for the hints.
Now I only need to draw some nice sprites :-)

 

anything