Hello all!
After a long hiatus, I decided to continue with SFML, and since I needed a simple animation system, I decided to use this one (http://www.sfml-dev.org/wiki/en/sources/anisprite) from the wiki. I made some minor modifications to get it to work with SFML2, but I don't think that this is the problem.
My first test animation was a picture consisting of 20 frames, each frame is of size 192 x 192. It worked great.
Then, I tried to animate a player sprite from RPG Maker VX. The sprite sheet consist of 12 sprites, each frame is of size 32 x 32. This one doesn't seems to work as it supposed to.
First, I tried to animate this part only (the first 3 frames of the sheet, so I set 0 as the starting frame and 2 as the end frame).
(http://i48.tinypic.com/14uhypy.png)
Normally, it should go in this order from left to righ (0-1-2). But for some reason, it plays in 0-2-1 order. No matter what I set the start/stop frames to, the animation order will be messed up.
The other problem is, when I don't specify the start and the end frame, and just let the system play the whole sheet, it will somehow add an extra frame, consisting of a 3-pixels wide, and 32 pixels tall vertical bar. This happens when the final frame has been reached, and before the system loops back to the first frame.
Again, neither of these problems happen with larger images (for example, with the one that consist of 192 x 192 frames). But I have no idea why is this happening.
Even if my code is largely the same as in the wiki, I post it here, since I could have been made a mistake.
Here's Animation.h:
#ifndef ANIMATION_H
#define ANIMATION_H
#include <SFML\Graphics.hpp>
class AnimSprite : public sf::Sprite
{
public:
AnimSprite();
AnimSprite(const sf::Texture &img, int frameWidth, int frameHeight);
~AnimSprite();
sf::IntRect AnimSprite::getFramePosition(int frame);
int getFrameCount();
void setFrameSize(int frameW, int frameH);
void setFrame(int frame);
void setLoopSpeed(float newfps);
void play();
void play(int start, int end);
void stop();
void update();
private:
sf::Clock animClock;
float fps;
bool isPlaying;
int loopStart;
int loopEnd;
int currentFrame;
int frameWidth;
int frameHeight;
};
#endif
And here's Animation.cpp:
#include "Animation.h"
AnimSprite::AnimSprite(void) : sf::Sprite()
{
this->fps = 1;
this->currentFrame = 0;
this->loopStart = 0;
this->isPlaying = false;
this->setFrameSize(0, 0);
}
AnimSprite::AnimSprite(const sf::Texture &img, int frameWidth, int frameHeight) : sf::Sprite(img)
{
this->fps = 1;
this->currentFrame = 0;
this->loopStart = 0;
this->isPlaying = false;
this->setFrameSize(frameWidth, frameHeight);
}
AnimSprite::~AnimSprite() { }
int AnimSprite::getFrameCount()
{
unsigned int horizontal = (this->getTexture()->getSize().x / this->frameWidth);
unsigned int vertical = (this->getTexture()->getSize().y / this->frameHeight);
return horizontal * vertical;
}
sf::IntRect AnimSprite::getFramePosition(int frame)
{
unsigned int horizontal = (this->getTexture()->getSize().x / this->frameWidth);
unsigned int vertical = (this->getTexture()->getSize().y / this->frameHeight);
int tileY = frame / horizontal;
int tileX = frame % horizontal;
sf::IntRect result(
tileX * this->frameWidth,
tileY * this->frameHeight,
this->frameWidth,
this->frameHeight);
return result;
}
void AnimSprite::setFrameSize(int frameW, int frameH)
{
this->frameWidth = frameW;
this->frameHeight = frameH;
this->setTextureRect(sf::IntRect(0, 0, frameW, frameH));
}
void AnimSprite::setFrame(int frame) { this->currentFrame = frame; }
void AnimSprite::setLoopSpeed(float newfps) { this->fps = newfps; }
void AnimSprite::play() { this->play(0, getFrameCount()); }
void AnimSprite::play(int start, int end)
{
loopStart = start;
loopEnd = end;
currentFrame = start;
isPlaying = true;
animClock.restart();
}
void AnimSprite::stop() { isPlaying = false; }
void AnimSprite::update()
{
if(isPlaying)
{
int frameCount = (loopEnd + 1) - loopStart;
float timePosition = (animClock.getElapsedTime().asSeconds() * fps);
currentFrame = loopStart + (int)timePosition % frameCount;
this->setTextureRect(getFramePosition(currentFrame));
}
}
Anybody knows what's going on here? Your help would be greatly appreciated. (I believe it's a trivial problem, but I fail to locate it... :S)
EDIT: Okay, I realized it's a bit hard to understand what the problem is, so here's a compiled binary (http://www.sendspace.com/file/w3qsqb) with the full source code.
Using this image: (http://i48.tinypic.com/14uhypy.png)
setFrameSize(85, 85);
play(0, 2);
Tell me what you get.
setFrameSize(32, 32);
play(0, 2);
No that won't work, one frame is 85x85px and not 32px. ;)
But this code does work (tested myself).
setFrameSize(85, 85);
play(0, 2);
Note if you set the framerate of the animation to high it could happen that some frames will get left out or you'll get diffrent animation steps.
The eye recognizes movements as of 24fps but with 3 frames you won't get a nice animation at 24fps. :D
I don't really get it now.
The animation system has no framerate limiter. All I can do is to set the delay between the frames. In my case, for example, it is:
charaAnim.setLoopSpeed(3);
The larger the number, the faster the animation plays. And, as I said before, using a sprite sheet consisting of larger images (like 192 x 192), everything works flawlessly, regardless of the number of frames, or the delay between the frames.
The system starts to fail when I use smaller images. With 32 x 32 sized frames, it doesn't work, with 85 x 85 sized frames, it does. And I'm not sure what causes this.
You missunderstood the use of the setLoopSpeed function. It does exactly behave as I've described it in my previous post. I've no problems with 32x32 images. For the test I used the following example code, with the scaled down image from your first post to 32x96px.
#include <SFML/Graphics.hpp>
#include "AniSprite.hpp"
int main()
{
sf::RenderWindow Screen(sf::VideoMode (800, 600, 32), "Ani");
Screen.setFramerateLimit(60);
sf::Texture Tex;
if(!Tex.loadFromFile("happy.png"))
return EXIT_FAILURE;
AnimSprite Sprite(Tex,32,32);
Sprite.setLoopSpeed(10);
Sprite.play(0,2);
while( Screen.isOpen() )
{
sf::Event Event;
while (Screen.pollEvent (Event))
{
if (Event.type == sf::Event::Closed)
Screen.close();
}
Sprite.update();
Screen.clear();
Screen.draw(Sprite);
Screen.display();
}
}
Okay, sorry for bumping this, but I found the problem.
The wiki states that for SFML2, you need to modify this line:
int frameCount = loopEnd - loopStart;
in the Update() function to this:
int frameCount = (loopEnd+1) - loopStart;
As of the most recent snapshot of SFML2, this is incorrect. Use the original one, it works.