SFML community forums

Help => Graphics => Topic started by: synok on December 19, 2012, 05:15:18 pm

Title: SFML and Sprites
Post by: synok on December 19, 2012, 05:15:18 pm
Hello all,

I have been searching my head off for an anwser to handling Sprites / Spritesheet.

I have tried doing this with both seperate sprites and spritesheets and I am using SFML 2.0. I have a spritesheet of a character I made and want to animate him. The problem is that the Time function and / or the Clock function does not work. Time / Clock will NOT start ticking after it is created, thus I cannot animate my character.

After all, it makes no sense to me to have one Time and one Clock function. Why not just have one timer function instead of confusing people with more than one function to handle time.

What I have tried so far is adding a spritesheet, seperating them with .setTextureRect and applying new dimensions to the sprite. That worked, as long as the Clock is equal to 0.0. Otherwise it wont work. I tried cutting out each and every frame from the spritesheet loading them individually, still this wont work since Time ALWAYS is equal to 0. Pointless function. Any help suggested for the simplest task of creating an animated sprite with either Spritesheet or seperate Sprites?

If anyone can write a solution, I take everything back that I said. I dare you.
Title: Re: SFML and Sprites
Post by: eXpl0it3r on December 19, 2012, 05:22:23 pm
A glimpse at the documentation (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Time.php#details), would've probably already resolved your confusion. ;)
Quote
Represents a time value.
sf::Time encapsulates a time value in a flexible way.
It allows to define a time value either as a number of seconds, milliseconds or microseconds. It also works the other way round: you can read a time value as either a number of seconds, milliseconds or microseconds.
By using such a flexible interface, the API doesn't impose any fixed type or resolution for time values, and let the user choose its own favorite representation.
Time values support the usual mathematical operations: you can add or subtract two times, multiply or divide a time by a number, compare two times, etc.
Since they represent a time span and not an absolute time value, times can also be negative.
In short: sf::Time only represents a value and doesn't increase or decrease. It also is a class and not a function.

If you want something that increases with time, you'll have to use sf::Clock (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Clock.php). It will start counting as soon as it's created or when you call restart(), but keep in mind that you can't pause it.

To use a spritesheet you simple load the image into a sf::Texture and pass it to you instance of sf::Sprite. Then you 'cut-out' a sub-rectangle of the whole spritesheet with setTextureRect(). If the spritesheet holds an animation, you'll have to move the texture rectangle accordingly to the sprite position and time.
Title: Re: SFML and Sprites
Post by: synok on December 19, 2012, 05:52:29 pm
A glimpse at the documentation (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Time.php#details), would've probably already resolved your confusion. ;)
Quote
Represents a time value.
sf::Time encapsulates a time value in a flexible way.
It allows to define a time value either as a number of seconds, milliseconds or microseconds. It also works the other way round: you can read a time value as either a number of seconds, milliseconds or microseconds.
By using such a flexible interface, the API doesn't impose any fixed type or resolution for time values, and let the user choose its own favorite representation.
Time values support the usual mathematical operations: you can add or subtract two times, multiply or divide a time by a number, compare two times, etc.
Since they represent a time span and not an absolute time value, times can also be negative.
In short: sf::Time only represents a value and doesn't increase or decrease. It also is a class and not a function.

If you want something that increases with time, you'll have to use sf::Clock (http://www.sfml-dev.org/documentation/2.0/classsf_1_1Clock.php). It will start counting as soon as it's created or when you call restart(), but keep in mind that you can't pause it.

To use a spritesheet you simple load the image into a sf::Texture and pass it to you instance of sf::Sprite. Then you 'cut-out' a sub-rectangle of the whole spritesheet with setTextureRect(). If the spritesheet holds an animation, you'll have to move the texture rectangle accordingly to the sprite position and time.

Followed your suggestions, now I see two sprites at the same time. And speaking of time, there is no delay for the animation. Here is a picture when he is moving to the right:

(http://i48.tinypic.com/jgsc3s.png)
Title: Re: SFML and Sprites
Post by: eXpl0it3r on December 19, 2012, 06:03:17 pm
Followed your suggestions, now I see two sprites at the same time.
You're probably not cutting out double the width of the character instead of just once (e.g. you cut-out a square instead of half a square).

And speaking of time, there is no delay for the animation.
I don't know what you mean with delay or what you're expecting...
Title: Re: SFML and Sprites
Post by: synok on December 19, 2012, 06:13:42 pm
Followed your suggestions, now I see two sprites at the same time.
You're probably not cutting out double the width of the character instead of just once (e.g. you cut-out a square instead of half a square).

And speaking of time, there is no delay for the animation.
I don't know what you mean with delay or what you're expecting...

Well now I set the sprite width same as the x, for example:

Code: [Select]
playerSprite.setTextureRect(sf::IntRect(22,2,20,40));
Instead of previous code:

Code: [Select]
playerSprite.setTextureRect(sf::IntRect(20,2,40,40));
The character is correctly drawn now, oddly enough.

With delay I mean I want some kind of short delay between the cycling frames, so they are not cycling all at once. A small delay of around 0.1 seconds would make the character walk smooth.
Title: Re: SFML and Sprites
Post by: eXpl0it3r on December 19, 2012, 06:36:24 pm
The character is correctly drawn now, oddly enough.
Why should this be odd?
One character is 20px in width and 40px in height. If you cut-out a 40px by 40px sub-image you'll obviously get the character twice. ;D

With delay I mean I want some kind of short delay between the cycling frames, so they are not cycling all at once. A small delay of around 0.1 seconds would make the character walk smooth.
So you mean that the animation runs slower?

Well you can use a sf::Clock and wait till the wanted 'delay time' has passed. For example if you want the animation to run at 5 frames per second, you do:
if(clock.getElapsedTime().asSeconds() >= 1.f/5.f)
     ++animationFrame;
... or similar. ;)
Title: Re: SFML and Sprites
Post by: synok on December 19, 2012, 06:48:47 pm
Yeah, well, what I want is it to draw the first texture rectangle at key pressed, then wait for a few frames, then move the texture rectangle to draw the second frame in the spritesheet, and repeat.

Does it matter where I define sf::Clock? Inside or outside loops for example.

Edit: Everything seems to work now. Thanks eXplo0it3r for your guidance. There is just one small thing left. In the end of the walk cycle he blinks for a small moment, then continues to loop when you hold down the button to move him. Why does he blink? It is like he goes transparent for a frame or so then continue to animate as normal.
Title: Re: SFML and Sprites
Post by: masskiller on December 20, 2012, 07:29:56 am
Quote
Everything seems to work now. Thanks eXplo0it3r for your guidance. There is just one small thing left. In the end of the walk cycle he blinks for a small moment, then continues to loop when you hold down the button to move him. Why does he blink? It is like he goes transparent for a frame or so then continue to animate as normal.

This may depend on your SpriteSheet (Having an extra block with alpha pixels) and your algorithm, say that the rect you are setting goes out of texture bounds due to an unhandled index value or that it doesn't go out of texture bounds, but it goes to the alpha filled part for a while.
Title: Re: SFML and Sprites
Post by: synok on December 20, 2012, 11:14:36 am
Oh okay, so what can I do about it? Also, I have noticed when I draw some text, that it also flickers. I am calling it when a mouse button is pressed in a specific area. I think it has something to do with window.display(), but im not sure.
Title: Re: SFML and Sprites
Post by: eXpl0it3r on December 20, 2012, 12:12:18 pm
Oh okay, so what can I do about it? Also, I have noticed when I draw some text, that it also flickers. I am calling it when a mouse button is pressed in a specific area. I think it has something to do with window.display(), but im not sure.
You need to provide code, so we can figure out what's going on. As for now we can only guess around the thousands of different possible mistakes or errors and won't really get to an result. ;)
Title: Re: SFML and Sprites
Post by: synok on December 22, 2012, 12:41:17 pm
Oh okay, so what can I do about it? Also, I have noticed when I draw some text, that it also flickers. I am calling it when a mouse button is pressed in a specific area. I think it has something to do with window.display(), but im not sure.
You need to provide code, so we can figure out what's going on. As for now we can only guess around the thousands of different possible mistakes or errors and won't really get to an result. ;)

Sorry if I am a bit late, but here is a snippet of code of the animation on keypress:

Code: [Select]

if (event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::D)
playerSprite.setTextureRect(sf::IntRect(2,2,18,40));

if(sf::Keyboard::isKeyPressed(sf::Keyboard::D)){
// Animate Player to walk.
if(clock.getElapsedTime().asSeconds() >= 0.05f)
playerSprite.setTextureRect(sf::IntRect(22,1,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.1f)
playerSprite.setTextureRect(sf::IntRect(42,1,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.15f)
playerSprite.setTextureRect(sf::IntRect(62,2,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.2f)
playerSprite.setTextureRect(sf::IntRect(82,2,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.25f)
playerSprite.setTextureRect(sf::IntRect(102,1,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.3f)
playerSprite.setTextureRect(sf::IntRect(122,1,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.35f)
playerSprite.setTextureRect(sf::IntRect(142,2,18,40));
if(clock.getElapsedTime().asSeconds() >= 0.4f){
playerSprite.setTextureRect(sf::IntRect(160,2,18,40));
clock.restart();
}
charPosX+=.45f;

And the spritesheet I am using is looking like this:

(http://i48.tinypic.com/2q3cjlk.png)

With dimensions of 200x100. Looking to extend the spritesheet later. The "clock" is as you guessed it, a sf::Clock, and the charPosX variable is a float to define the current position of the character.

So the result is a walk animation that blinks for a frame at the end. Same goes with the Text which has alot of unoptimized code, but here it is:

Code: [Select]
if(mouse.getPosition().x<=460&&mouse.getPosition().x>=400){

if(mouse.getPosition().y<=520&&mouse.getPosition().y>=420){
checkFridge=true;
if(checkFridge == true){
useLabelSprite.setTexture(useLabel);
useLabelSprite.setPosition(mouse.getPosition().x-10,mouse.getPosition().y-50);
useLabelSprite.setScale(2.0f,2.0f);

if(event.type==sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left){
text.setPosition(charPosX,charPosY-20);
window.draw(text);
window.display();

}else if(event.type==sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left){
text.setPosition(charPosX,charPosY-20);
window.draw(text);
window.display();
}
}
}else{
// Unload UseLabel.
checkFridge=false;
useLabelSprite.setScale(0.0f,0.0f);
}
}else{
// Unload UseLabel.
checkFridge=false;
useLabelSprite.setScale(0.0f,0.0f);
}

Thanks for any help.
Title: Re: SFML and Sprites
Post by: cire on December 22, 2012, 05:15:12 pm
Why are you calling window.display() in event processing code?
Title: Re: SFML and Sprites
Post by: synok on December 22, 2012, 11:19:42 pm
Because the text should be displayed if the mouse is pressed, at the moment.
Title: Re: SFML and Sprites
Post by: masskiller on December 23, 2012, 12:51:14 am
As I suspected, your sprite sheet has an extra of transparent pixels, and you start your rect with 22 as a left parameter, when it should be 0 so that it starts with the left-most sprite. In your eight setTextureRect call it falls apart, causing your blinking.

I recommend you use an index for it. You don't need to manually set the rect each time, you can use an index to multiply and you can reduce 8 if's to 1. The index would start in zero and would increase according to time.

This:


if(sf::Keyboard::isKeyPressed(sf::Keyboard::D)){
                        // Animate Player to walk.
                        if(clock.getElapsedTime().asSeconds() >= 0.05f)
                                playerSprite.setTextureRect(sf::IntRect(22,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.1f)
                                playerSprite.setTextureRect(sf::IntRect(42,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.15f)
                                playerSprite.setTextureRect(sf::IntRect(62,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.2f)
                                playerSprite.setTextureRect(sf::IntRect(82,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.25f)
                                playerSprite.setTextureRect(sf::IntRect(102,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.3f)
                                playerSprite.setTextureRect(sf::IntRect(122,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.35f)
                                playerSprite.setTextureRect(sf::IntRect(142,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.4f){
                                playerSprite.setTextureRect(sf::IntRect(160,2,18,40));
                                clock.restart();
                        }
                        charPosX+=.45f;
 

Can turn into:

const float timeInterval = 0.05f;
sf::Time T = sf::seconds(timeInterval);
unsigned i = 0;

if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
    // Animate Player to walk.
        if ( i > maxRows )
    {
                i = 0;
                T = sf::seconds(timeInterval);  
        }  
       
    if(clock.getElapsedTime().asSeconds() >= T)
    {
        playerSprite.setTextureRect(sf::IntRect(i * width, currentColumn * height, width, height));
        ///currentColumn is an index that starts in 0 and increases however you may want it to.
        ++i;
        T = sf::seconds(i * timeInterval);
    }
}
 

As a small sample. That way it becomes more manageable and not dependent on magical numbers. You can use the index as a static variable in a function if you use that algorithm only for said sprite, as it exists only once per program run and it won't be called for any other animation of any other object, else you have the animation index as a class member once you take it all out of main.

Quote
Because the text should be displayed if the mouse is pressed, at the moment.

The display call should always be outside of the event loop. The reason you don't call
display(object);
is because the display function exists for everything that is getting drawn, not just that one object.
Title: Re: SFML and Sprites
Post by: synok on December 26, 2012, 12:43:13 am
As I suspected, your sprite sheet has an extra of transparent pixels, and you start your rect with 22 as a left parameter, when it should be 0 so that it starts with the left-most sprite. In your eight setTextureRect call it falls apart, causing your blinking.

I recommend you use an index for it. You don't need to manually set the rect each time, you can use an index to multiply and you can reduce 8 if's to 1. The index would start in zero and would increase according to time.

This:


if(sf::Keyboard::isKeyPressed(sf::Keyboard::D)){
                        // Animate Player to walk.
                        if(clock.getElapsedTime().asSeconds() >= 0.05f)
                                playerSprite.setTextureRect(sf::IntRect(22,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.1f)
                                playerSprite.setTextureRect(sf::IntRect(42,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.15f)
                                playerSprite.setTextureRect(sf::IntRect(62,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.2f)
                                playerSprite.setTextureRect(sf::IntRect(82,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.25f)
                                playerSprite.setTextureRect(sf::IntRect(102,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.3f)
                                playerSprite.setTextureRect(sf::IntRect(122,1,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.35f)
                                playerSprite.setTextureRect(sf::IntRect(142,2,18,40));
                        if(clock.getElapsedTime().asSeconds() >= 0.4f){
                                playerSprite.setTextureRect(sf::IntRect(160,2,18,40));
                                clock.restart();
                        }
                        charPosX+=.45f;
 

Can turn into:

const float timeInterval = 0.05f;
sf::Time T = sf::seconds(timeInterval);
unsigned i = 0;

if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
    // Animate Player to walk.
        if ( i > maxRows )
    {
                i = 0;
                T = sf::seconds(timeInterval);  
        }  
       
    if(clock.getElapsedTime().asSeconds() >= T)
    {
        playerSprite.setTextureRect(sf::IntRect(i * width, currentColumn * height, width, height));
        ///currentColumn is an index that starts in 0 and increases however you may want it to.
        ++i;
        T = sf::seconds(i * timeInterval);
    }
}
 

As a small sample. That way it becomes more manageable and not dependent on magical numbers. You can use the index as a static variable in a function if you use that algorithm only for said sprite, as it exists only once per program run and it won't be called for any other animation of any other object, else you have the animation index as a class member once you take it all out of main.

Quote
Because the text should be displayed if the mouse is pressed, at the moment.

The display call should always be outside of the event loop. The reason you don't call
display(object);
is because the display function exists for everything that is getting drawn, not just that one object.

I tweaked your code, and got it to:

Code: [Select]


const float timeInterval = 0.05f;
sf::Time T = sf::seconds(timeInterval);
unsigned i = 0;

// Animate Player to walk.
if ( i > 7 )
{
i = 0;
T = sf::seconds(timeInterval); 

   

{
playerSprite.setTextureRect(sf::IntRect(i * 21, 0 * 42, 21, 42));
///currentColumn is an index that starts in 0 and increases however you may want it to.
i++;
T = sf::seconds(i * timeInterval);
        }


Edit: Noticed I have put the definition of the variables in the loop. That's why. Now, he animates very fast, is there a solution to solve that? Might add that I had to set your variable "T" in the:

Code: [Select]
if(clock.getElapsedTime().asSeconds() >= 0.05f) case since I had the following error:
Quote
error C2678: binary '>=' : no operator found which takes a left-hand operand of type 'float' (or there is no acceptable conversion)
in Visual Studio. I am guessing that T is causing the delay I am looking for in this place.
Title: Re: SFML and Sprites
Post by: masskiller on December 26, 2012, 03:02:49 am
Quote
That's why. Now, he animates very fast, is there a solution to solve that?

If your animation is too fast just increase the delay time. Delay time depends on what kind of effect you want to give, how many sprites are you animating and so on, it's perfectly tweekable as you may wish it to be.

Quote
I am guessing that T is causing the delay I am looking for in this place.

It is indeed what causes it, notice that the getElapsedTime() function returns an sf::Time value that can be compared to other sf::Time values, take a look at this code, which is the SFML source of Time.cpp

bool operator <=(Time left, Time right)
{
    return left.asMicroseconds() <= right.asMicroseconds();
}

bool operator >=(Time left, Time right)
{
    return left.asMicroseconds() >= right.asMicroseconds();
}

Instead of manually converting times and ridding of the T parameter you can just compare times as needed, it has even more precision than the seconds comparison.

I just noticed that in my code I left a mistaken "asSeconds()" call. That was a mistake I overlooked since you should be comparing sf::time with another sf::time and not with a float. asSeconds() returns a float and it doesn't know how to compare itself with time, which is the reason your compiler whines.

It should look something like this:

if(clock.getElapsedTime() >= T)

My bad for overlooking this when I was refactoring your code. With that change and leaving the parameter T as I set it should work well and with no compiling errors.

Edit: As a side-note, you can perfectly use variables instead of the plain numbers, it makes your code more controllable over-time, mainly when you forget what that number meant. Not to mention that it can also may make your code work in more than one situation.

All you need to do is set the width and height variable before your loop using the texture's getSize function:

unsigned width = (texture name).getSize().x;
unsigned heigth = (texture name).getSize().y;

///further settings of the particular sprite sheet.

///Game loop and fancy stuff.
 

And the code can work for any sprite sheet that be organized in a horizontal way. You should always aim for making your solution broader unless you require something extremely specific you'll never use for anything else. It's always better to write one semi-universal animator than an animator for each sprite sheet you use.
Title: Re: SFML and Sprites
Post by: synok on December 26, 2012, 03:31:20 am
All I did now was changing the typo you made and it worked, although the animation didnt loop so all I added was
Code: [Select]
clock.restart(); when the frame reaches 6.

While were at it, I do not understand why the text kind of "blinks". I added
Code: [Select]
window.draw(text); in the event of where the mouse button is pressed. This should render the text only, I guess.
Title: Re: SFML and Sprites
Post by: masskiller on December 26, 2012, 04:00:04 am
Quote
While were at it, I do not understand why the text kind of "blinks". I added
Code: [Select]

window.draw(text);

in the event of where the mouse button is pressed. This should render the text only, I guess.

If I understand well, what you want to do is to render only the text when you click and nothing else. In that case you need to make the draw calls as a response of when the mouse isn't being clicked, in other words, the false part of the mouse event polling.

///The event loop.

 /**The conditional of your mouse and all it does. Draws only the text*/
  else
{ /*draws everything.*/  }
 

So in this particular case all your draw calls should be based on events (or lack of them) and no draw call should be outside of your event polling I guess.