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

Author Topic: Can't get animation to work  (Read 4991 times)

0 Members and 1 Guest are viewing this topic.

Omikron

  • Newbie
  • *
  • Posts: 8
    • View Profile
Can't get animation to work
« on: August 30, 2013, 05:49:52 pm »
I wrote an animation class which inherits the sf::Sprite class.  But it only displays the first frame of the animation.  It's probably something small I've missed, but I can't figure out why it doesn't work.

Animation.h
#pragma once
#include <SFML/Graphics.hpp>

class Animation : public sf::Sprite
{
private:
        std::vector<sf::Texture> frames;
        sf::Time frameTime;
        int currentFrame;
        sf::Time time;
        bool playing;

public:
        Animation(const std::string frames[], sf::Time frameTime);

        void play();
        void pause();
        int getWidth();
        int getHeight();
        void flipX();
        void flipY();

        void update(sf::Time delta);
};
 
Animation.cpp
#include "Animation.h"

Animation::Animation(const std::string frames[], sf::Time frameTime) : frameTime(frameTime),
                                                                                                                                           currentFrame(-1),
                                                                                                                                           time(sf::Time::Zero),
                                                                                                                                           playing(false)
{
        for (int i = 0; i < sizeof(frames); ++i)
        {
                sf::Texture texture;
                texture.loadFromFile(frames[i]);

                this->frames.push_back(texture);
        }

        setTexture(this->frames.at(0));
}

void Animation::play()
{
        playing = true;
}

void Animation::pause()
{
        playing = false;
}

int Animation::getWidth()
{
        return getTexture()->getSize().x;
}

int Animation::getHeight()
{
        return getTexture()->getSize().y;
}

void Animation::flipX()
{
        setTextureRect(sf::IntRect(getWidth(), 0, -getWidth(), getHeight()));
}

void Animation::flipY()
{
        setTextureRect(sf::IntRect(getWidth(), 0, getWidth(), -getHeight()));
}

void Animation::update(sf::Time delta)
{
        if (playing)
        {
                time += delta;

                if (time >= frameTime)
                {
                        time = sf::Time::Zero;
                        currentFrame ++;

                        if (currentFrame = frames.size())
                                currentFrame = 0;

                        setTexture(frames.at(currentFrame));
                }
        }
}
 
PlayState.cpp (The class that uses the animation)
#include "PlayState.h"
#include "resources.h"

;PlayState PlayState::instance;

void PlayState::activate()
{
        player = new Animation(FILE_PLAYER, sf::milliseconds(30));
        player->play();
}

void PlayState::input()
{

}

void PlayState::update(sf::Time delta)
{
        player->update(delta);
}

void PlayState::render(sf::RenderWindow &window)
{
        window.draw(*player);
}

void PlayState::deactivate()
{
        delete player;
}
 
FILE_PLAYER is defined as the following
const std::string FILE_PLAYER[8] = { "data/player/player_01.png", "data/player/player_02.png", "data/player/player_03.png", "data/player/player_04.png",
                                                                         "data/player/player_05.png", "data/player/player_06.png", "data/player/player_07.png", "data/player/player_08.png" };
 

Any help is appreciated :)

G.

  • Hero Member
  • *****
  • Posts: 1593
    • View Profile
Re: Can't get animation to work
« Reply #1 on: August 30, 2013, 06:26:14 pm »
In C++, the comparison operator for equality is == not = ;)

Gobbles

  • Full Member
  • ***
  • Posts: 132
    • View Profile
    • Email
Re: Can't get animation to work
« Reply #2 on: August 30, 2013, 06:51:29 pm »
as G. said

void Animation::update(sf::Time delta)
{
    if (playing)
    {
        time += delta;

        if (time >= frameTime)
        {
            time = sf::Time::Zero;
            currentFrame ++;

            if (currentFrame = frames.size())
                currentFrame = 0;

            setTexture(frames.at(currentFrame));
        }
 

currentFrame = frames.size(), which sets currentFrame to the size, so it's true, then it will set currentFrame to 0.

Omikron

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re: Can't get animation to work
« Reply #3 on: August 30, 2013, 07:44:23 pm »
It animates now, thanks for spotting that mistake.  The bad news is, only 4 frames actually show, then it returns to the starting frame.  Any idea why this might happen?

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Can't get animation to work
« Reply #4 on: August 30, 2013, 07:46:17 pm »
Please start to use a debugger. This kind of problems can usually be solved within a few minutes, given the right tools.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Gobbles

  • Full Member
  • ***
  • Posts: 132
    • View Profile
    • Email
Re: Can't get animation to work
« Reply #5 on: August 30, 2013, 08:22:24 pm »
also I noticed you are using a vector of Textures. Do you have each image in a completely separate file? if this is the case, look into texture atlases.

Edit: I noticed you actually do load in 8 separate files, which is a lot more expensive then having all the images on 1 file. I would definitely look into a texture atlas (spritesheet)
« Last Edit: August 30, 2013, 08:28:28 pm by Gobbles »

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: Can't get animation to work
« Reply #6 on: August 30, 2013, 08:46:51 pm »
I also wrote an animation class. It's free and on the wiki. If you want you can use it directly or as a inspiration if you want.

I also noticed something weird in your code. In the constructor of Animtion you set currentFrame to -1 and then you set the texture to the first element in the texture vector. In the update() method you increment the currentFrame variable (in this case to 0) and then you set the texture to the vector element with that index. So in the first run you see the first frame twice.

And like Gobbles said you should look into spritesheets (i.e. having all frames of one animation in a single file), because texture switching is a costly operation on the GPU.

edit: Also another important thing: sizeof does NOT return the number of element in an array!!! (In your case you actually fill the vector with 192 textures!!!) It shows how much memory the given variable or type uses in byte. There is a way to determin the number of elements (sizeof(FILE_PLAYER) / sizeof(FILE_PLAYER[0])), but this is C style and you should really rethink the way you load the textures... (for example the return value of loadFromFile isn't checked either...)
« Last Edit: August 30, 2013, 09:05:25 pm by Foaly »

Omikron

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re: Can't get animation to work
« Reply #7 on: August 30, 2013, 09:18:54 pm »
I also wrote an animation class. It's free and on the wiki. If you want you can use it directly or as a inspiration if you want.

I also noticed something weird in your code. In the constructor of Animtion you set currentFrame to -1 and then you set the texture to the first element in the texture vector. In the update() method you increment the currentFrame variable (in this case to 0) and then you set the texture to the vector element with that index. So in the first run you see the first frame twice.

And like Gobbles said you should look into spritesheets (i.e. having all frames of one animation in a single file), because texture switching is a costly operation on the GPU.

edit: Also another important thing: sizeof does NOT return the number of element in an array!!! (In your case you actually fill the vector with 192 textures!!!) It shows how much memory the given variable or type uses in byte. There is a way to determin the number of elements (sizeof(FILE_PLAYER) / sizeof(FILE_PLAYER[0])), but this is C style and you should really rethink the way you load the textures... (for example the return value of loadFromFile isn't checked either...)
Ah, thanks.  If I replace sizeof(frames) with 8 (which is how many frames I actually have), the animation fully loops.  I will definitely switch to using a spritesheet though, and I might use your animation class, at least for now :)

edit: I tried to use your class, but it doesn't work :(  The game just instantly crashes.
Code: [Select]
sf::Texture texture;
texture.loadFromFile(FILE_PLAYER);

Animation animation;
animation.setSpriteSheet(texture);
for (int i = 0; i < 8; ++i)
{
int width = 16;
int height = texture.getSize().y;
animation.addFrame(sf::IntRect(width * i, 0, width, height));
}

player = new AnimatedSprite(sf::milliseconds(30));
player->setAnimation(animation);
« Last Edit: August 30, 2013, 09:38:46 pm by Omikron »

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: Can't get animation to work
« Reply #8 on: August 31, 2013, 03:08:22 am »
I am always happy to see my class being useful and to see it in action.
Please post a complet and minimal code of what is not working. Without that I can't help you…
Have you checked the return value of loadFromFile?

Omikron

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re: Can't get animation to work
« Reply #9 on: August 31, 2013, 09:25:22 am »
I am always happy to see my class being useful and to see it in action.
Please post a complet and minimal code of what is not working. Without that I can't help you…
Have you checked the return value of loadFromFile?
I made a simple animation test project using your class and the same sprite sheet, and it works, but trying to use it in my game does not work.  Running the game results of one of 3 results:
1)
Quote
Unhandled exception at 0x003013EF in Game.exe: 0xC0000005: Access violation reading location 0x0000004F.
It points to this line in AnimatedSprite.cpp, in the setFrame function:
sf::IntRect rect = m_animation->getFrame(m_currentFrame);

2)
Quote
Unhandled exception at 0x590765CE (sfml-graphics-2.dll) in Game.exe: 0xC0000094: Integer division by zero.
It points to this line in AnimatedSprite.cpp, in the draw function:
target.draw(m_vertices, 4, sf::Quads, states);

3) The screen flashes black and white, occasionally settling at black.

These results happen randomly, with 3) being the most common.

This is the file that implements the animation:
#include "PlayState.h"
#include "resources.h"

;PlayState PlayState::instance;

void PlayState::activate()
{      
        sf::Texture texture;
        if (!texture.loadFromFile(FILE_PLAYER))
                throw std::runtime_error("Could not open " + FILE_PLAYER);

        Animation animation;
        animation.setSpriteSheet(texture);

        for (int i = 0; i < 8; ++i)
        {
                animation.addFrame(sf::IntRect(i * 16, 0, 16, 48));
        }

        player = AnimatedSprite(sf::milliseconds(30));
        player.setAnimation(animation);
}

void PlayState::input()
{

}

void PlayState::update(sf::Time delta)
{
        player.update(delta);
}

void PlayState::render(sf::RenderWindow &window)
{
        window.draw(player);
}

void PlayState::deactivate()
{
       
}
 

I can't understand how it would work in the test, but not here :O
Just in case, here's the code I used to test it:
#include <SFML/Graphics.hpp>
#include "AnimatedSprite.h"

int main()
{
        sf::RenderWindow window(sf::VideoMode(800, 600), "Animation Test");
       
        sf::Texture texture;
        if (!texture.loadFromFile("spritesheet.png"))
                return 1;

        Animation animation;
        animation.setSpriteSheet(texture);
       
        for (int i = 0; i < 8; ++i)
        {
                animation.addFrame(sf::IntRect(i * 16, 0, 16, 48));
        }

        AnimatedSprite sprite(sf::milliseconds(100));
        sprite.setAnimation(animation);

        sf::Clock clock;
        while (window.isOpen())
        {
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }

                sprite.update(clock.restart());

                window.clear();
                window.draw(sprite);
                window.display();
        }

        return 0;
}
 
« Last Edit: August 31, 2013, 09:27:18 am by Omikron »

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: Can't get animation to work
« Reply #10 on: August 31, 2013, 01:47:02 pm »
I'm suprised your code doesn't crash instantly...
I think the problem is that your texture and the animation go out of scope at the end of your activate method. AnimatedSprite only takes references to both the texture and the animation, so both have to stay alive for as long as you want to use the AnimatedSprite. This behavior is similar to sf::Sprite. There is also a notice on the wiki page explaining this...
So the easiest fix would be to make texture and animation members of your class.

Omikron

  • Newbie
  • *
  • Posts: 8
    • View Profile
Re: Can't get animation to work
« Reply #11 on: August 31, 2013, 02:53:42 pm »
I'm suprised your code doesn't crash instantly...
I think the problem is that your texture and the animation go out of scope at the end of your activate method. AnimatedSprite only takes references to both the texture and the animation, so both have to stay alive for as long as you want to use the AnimatedSprite. This behavior is similar to sf::Sprite. There is also a notice on the wiki page explaining this...
So the easiest fix would be to make texture and animation members of your class.
Thanks, unfortunately the wiki is down at the moment, but I made texture and animation both members of the class, and now the animation works :)  I noticed that, in your Animated Sprite class, you declare a "setFrameTime" function in the header file, but never define it in the source file.  As I used this function, it caused a linker error, so I added it in:
void AnimatedSprite::setFrameTime(sf::Time time)
{
        m_frameTime = time;
}
Also, on first build several warnings were generated.  Although warnings don't really matter, they are annoying.  This was simply a case of modifying the following line:
explicit AnimatedSprite(sf::Time frameTime = sf::seconds(0.2f), bool paused = false, bool looped = true);
As you can see, I typed an "f" after "0.2", which makes it a float and not a double.  Similarly:
float left = static_cast<float>(rect.left) + 0.0001f;
0.0001 suggested a double without an "f" on the end.
Also these following lines:
m_vertices[1].position = sf::Vector2f(0, (float)rect.height);
m_vertices[2].position = sf::Vector2f((float)rect.width, (float)rect.height);
m_vertices[3].position = sf::Vector2f((float)rect.width, 0);
"rect.width" and "rect.height" return ints, so they should be casted to floats to show that you mean to convert them.  Anyway, thanks for the class :D

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: Can't get animation to work
« Reply #12 on: August 31, 2013, 04:59:50 pm »
Quote
Anyway, thanks for the class :D
You are very welcome! I glad to hear that everything is working now.

Thank you for the tips! I wrote this class a while back and now that I had a look at it again I noticed there are a couple other things wrong too... I'll write an update soon! Thanks fo the help.

Also on my end the wiki works fine.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Can't get animation to work
« Reply #13 on: August 31, 2013, 09:26:57 pm »
1) Warnings definitely do matter, because even if they don't stop compilation they very often indicate logic errors you're going to have to go fix anyway.  Never ignore them without first understanding exactly what they mean (which may involve googling) and convincing yourself it's really not a problem in your specific case.

2) These days, it's considered bad practice to use C-style casts like (float)rect.width.  Use one of the C++-style casts--probably static_cast<float>rect.width--to specify (and self-document) what kind of casting you're after.

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: Can't get animation to work
« Reply #14 on: September 02, 2013, 10:31:46 am »
I updated the AnimatedSprite code on the wiki. If you take a look at it you'll see that I used static_cast<float>. But thanks for pointing it out.
And of course it is important to fix warnings, as they point out things that are potentially wrong. Among a couple other things they are now fixed too.