SFML 2.1 movement help

Pages: 12
Ok so I just started learning SFML and I love it so far and i'm trying to get a sprite to move fluidly but i cant seem to get it right. When I move it takes a second to move in that direction and then when i stop and pick another direction, the sprite moves a few pixels in the last direction it was moving in. How can I make the movement fluid, I have been trying for a while now and I cant seem to get it right.

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
#include <iostream>
#include <SFML/Graphics.hpp>

using namespace std;

int main()
{
    float x = 0;
    float y = 0;

    const float speed = 4.0;

    sf::RenderWindow window(sf::VideoMode(800, 600), "Sprite Movement");

    //Load Texture
    sf::Texture texture;
    if(!texture.loadFromFile("Resources/Textures/triangle.png"))
    {
        cout << "Error Loading Texture" << endl;
    }
    else
    {
        cout << "Texture Loaded" << endl;
    }

    //Clock
    sf::Clock clock;
    sf::Time t1 = sf::microseconds(10000);
    sf::Time t2 = sf::milliseconds(10);
    sf::Time t3 = sf::seconds(0.0f);

    //Set Sprite
    sf::Sprite sprite;
    sprite.setTexture(texture);
    sprite.setOrigin(sf::Vector2f(0, 0));
    sprite.setPosition(400, 300);

    sf::Vector2f pos = sprite.getPosition();

    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(sf::Event::KeyReleased)
                    {

                    }
                    if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
                    {
                        sprite.setPosition(pos.x, pos.y);
                        pos.x -= speed;
                    }
                    else if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
                    {
                        sprite.setPosition(pos.x, pos.y);
                        pos.x += speed;
                    }
                    else if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
                    {
                        sprite.setPosition(pos.x, pos.y);
                        pos.y -= speed;
                    }
                    else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
                    {
                        sprite.setPosition(pos.x, pos.y);
                        pos.y += speed;
                    }
                }
            }
        }

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

        window.draw(sprite);

        window.display();
    }

    return 0;
}
First... this is backwards:

1
2
3
4
5
                    if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
                    {
                        sprite.setPosition(pos.x, pos.y);  // <- set the sprite's position to 'pos'...
                        pos.x -= speed;  // <- before you actually moved 'pos'?
                    }


So if you press 'A', the sprite won't move... but then 'pos' will be updated. So then if you press 'W'.. the sprite will move left but not up... but then 'pos' will be updated to move up. Etc.



Second... keydown events are only sent when the key is first pressed. So your if statements on line 60+ will only happen when you press a key (because they are inside your keydown event handler). They will not continue to happen as the key is held. To do this... you must perform them even when no key is pressed. IE: move them outside of your event handler.


Third... You'll want to limit the framerate. Right now your program will just run as fast as your computer will allow... which means you'll get different speeds depending on how fast and how busy your computer is. When you set a fixed framerate, you'll get the same speed regardless of all that.

Throw this in after you create your window:

 
window.setFramerateLimit(60);



That ought to do it.
It works exactly how i want it to thank you :) I have one more question though.

I just now created this if statement so I can move diagonally:

1
2
3
4
5
if(sf::Keyboard::isKeyPressed(sf::Keyboard::A) && sf::Keyboard::isKeyPressed(sf::Keyboard::S))
        {
            pos.x -= speed;
            pos.y += speed;
        }


However even though it moves diagonally, it moves faster than just plain up and down, what do I do here? I was just thinking to lower the speed here or just make a diagonal speed variable but i'm not sure, what should I do?

Also I dont need to put: sprite.setPosition(pos.x, pos.y); in the diagonal direction statemnts do I? it works just fine without them but sometimes that doesnt mean anything in programming.
Last edited on
Forgive me, but I'm going to make this a bigger problem than it actually is. Bear with me. =)

Having a fixed speed that you add to a position is not ideal. Usually you'd want an object to accelerate and decelerate. This is normally accomplished by having a 'velocity' variable that is added to your position every update. Then, instead of moving your object directly when a key is pressed, you just change the velocity.

This may seem like an excessive amount of work... but it allows for so much flexibility. Like if you want different accel/decel speeds at times (like if the player is on some slippery ice, you'd want low accel/decel.

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
    sf::Vector2f pos;  // our position
    sf::Vector2f vel;   // our velocity
    float maxspeed = 4.0f;  // maximum speed
    float accel = 1.5f;  // acceleration rate (added to velocity)
    float decel = 0.1f;  // deceleration rate (multiplied into velocity.. must be < 1)

// ... then in your logic updates

        // horizontal movement
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
            vel.x -= accel;     // apply leftward acceleration
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
            vel.x += accel;     // apply rightward acceleration
        else
            vel.x *= decel;     // else, not moving horizontally -- so apply deceleration

        // vertical movement - same deal, but use Y instead of X
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
            vel.y -= accel;
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
            vel.y += accel;
        else
            vel.y *= decel;

        // now that we've updated our velocity, make sure we are not going to fast
        if(vel.x < -maxspeed)       vel.x = -maxspeed;
        if(vel.x >  maxspeed)       vel.x =  maxspeed;
        if(vel.y < -maxspeed)       vel.y = -maxspeed;
        if(vel.y >  maxspeed)       vel.y =  maxspeed;

        // Now we have our final velocity.  Use it to update our position, and move our object
        pos += vel;
        sprite.setPosition(pos);



Now this will have more or less the same effect as what you have now. Your object will move faster diagonally than it will just left/right. BUT... we have the means to change that very easily.

Above I am just comparing X and Y individually to the max speed. But we want to apply movement as a whole to the max speed. So for that, we can use good old Pythagorean's theorum on our velocity to compute the actual speed. And if we exceed the maximum, we just scale our velocity appropriately.


So... you could replace those 4 if statements with this:
1
2
3
4
5
        // now that we've updated our velocity, make sure we are not going to fast
        float actualspeed = sqrt( (vel.x * vel.x) + (vel.y * vel.y) );  // a*a + b*b = c*c

        if(actualspeed > maxspeed)          // are we going too fast?
            vel *= maxspeed / actualspeed;  // scale our velocity down so we are going at the max speed 
I think I understand, i implemented the code you posted but when I run it, my sprite just goes down, I can move it left and right but it just falls, why is that? did I do something wrong?

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
#include <iostream>
#include <math.h>
#include <SFML/Graphics.hpp>

using namespace std;

int main()
{
    float x = 0;
    float y = 0;

    sf::Vector2f position;
    sf::Vector2f velocity;
    float maxspeed = 4.0f;
    float accel = 1.5f;
    float decel = 0.01f;

    sf::RenderWindow window(sf::VideoMode(800, 600), "Sprite Movement");
    window.setFramerateLimit(60);

    //Load Texture
    sf::Texture texture;
    if(!texture.loadFromFile("Resources/Textures/triangle.png"))
    {
        cout << "Error loading resource '::Texture'" << endl;
    }
    else
    {
        cout << "Texture Loaded" << endl;
    }

    //Set Sprite
    sf::Sprite sprite;
    sprite.setTexture(texture);
    sprite.setOrigin(sf::Vector2f(0, 0));
    sprite.setPosition(400, 300);

    //sf::Vector2f pos = sprite.getPosition();

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

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

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

        window.draw(sprite);

        if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
        {
            velocity.x -= accel;
            //sprite.setPosition(pos.x, pos.y);
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
        {
            velocity.x += accel;
            //sprite.setPosition(pos.x, pos.y);
        }
        else
        {
            velocity.x *= decel;
        }

        if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
        {
            velocity.y -= accel;
            //sprite.setPosition(pos.x, pos.y);
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
        {
            velocity.y += accel;
            //sprite.setPosition(pos.x, pos.y);
        }
        else
        {
            velocity.y *= decel;
        }

        if(velocity.x < -maxspeed) velocity.x = -maxspeed;
        if(velocity.x >  maxspeed) velocity.x =  maxspeed;
        if(velocity.y < -maxspeed) velocity.y = -maxspeed;
        if(velocity.y <  maxspeed) velocity.y =  maxspeed;

        position += velocity;
        sprite.setPosition(position);

        float actualspeed = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y));

        if(actualspeed > maxspeed)
        {
            velocity *= maxspeed / actualspeed;
        }

        window.display();
    }

    return 0;
}
Nevermind I figured it out. I had a < instead of a > in one part. So how do I make it stop moving when it hits the edge of the screen, I was trying all day to do that on a different project yesterday but I couldnt get it to worjk right. I wanted it to get the screens size and stop moving when it hits the edge.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        // This code will cap out your velocity at the maximum speed
        if(velocity.x < -maxspeed) velocity.x = -maxspeed;
        if(velocity.x >  maxspeed) velocity.x =  maxspeed;
        if(velocity.y < -maxspeed) velocity.y = -maxspeed;
        if(velocity.y >  maxspeed) velocity.y =  maxspeed;

        // This code will apply the current velocity to our position, and will update the sprite
        position += velocity;
        sprite.setPosition(position);

        // This code caps out the velocity AGAIN (you already did this above)
        //  What's more... you already moved the sprite... so modifying the velocity here
        //  is pointless
        float actualspeed = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y));

        if(actualspeed > maxspeed)
        {
            velocity *= maxspeed / actualspeed;
        }


It's a one or the other type deal. You're doing both.



So how do I make it stop moving when it hits the edge of the screen, I was trying all day to do that on a different project yesterday but I couldnt get it to worjk right.


check to see if the position is within the screen bounds, and if it isn't, force it to be.

ie:

1
2
if(position.x < 0)  position.x = 0;  // can't go offscreen to the left
// etc 
Ok thank you, so for the if statements vs the math code, which is better? or are they both equally good? Also for checking the colision on the right side of the screen, how would I do that? The screen size will change so I cant enter a fixed value, also I'm going to make it so the screen doesnt stretch everything when you maximize it, will this effect it any?
Ok thank you, so for the if statements vs the math code, which is better?


They both do different things. I've leave it to you to determine which is closer to what you want.

The if statements cap the velocity on x,y individually
The math caps velocity on total movement.

Also for checking the colision on the right side of the screen, how would I do that? The screen size will change so I cant enter a fixed value


It's the same thing... only you compare the object's position to the right side of the screen instead of the left.

You can use window.getSize() to get the current window size so you don't have to use a fixed value.

also I'm going to make it so the screen doesnt stretch everything when you maximize it, will this effect it any?


I'm not sure. I'm fuzzy on what SFML does when you resize the window -- I always just disallowed window resizing.
Last edited on
Alright i'll try to figure out which one works best. As for the bounds problem, I did this and it's not working:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sf::Vector2u currWSize = window.getSize();

        if(position.x < 0)
        {
            position.x = 0;
        }
        if(position.y < 0)
        {
            position.y = 0;
        }
        if(position.x > currWSize.x)
        {
            position.x = currWSize.x;
        }
        if(position.y > currWSize.y)
        {
            position.y = currWSize.y;
        }
Why don't you use the sf::Sprite::move(); method, since it saves you from checking for the current position of the sprite, and just move to any direction,

Aceix.
How would that look?
Nobody has mentioned this but you should use deltatime so that the velocity is the same on each computer. Something like:

1
2
3
4
5
6
7
8
9
10
sf::Clock deltaTime; //calls default constructor

while(window.pollEvent(event))
{
    //get user input
    //change the x or y velocity accordingly
    velocity += acceleration * deltaTime.restart().asSeconds();
    // http://sfml-dev.org/documentation/2.1/classsf_1_1Clock.php#a123e2627f2943e5ecaa1db0c7df3231b
    // http://sfml-dev.org/documentation/2.1/classsf_1_1Time.php#a7538140d095e48da9d7eee015dd455a9
}

*Accidently had .asSecond() instead of .asSeconds()

Last edited on
it says Time has no member namd 'restart'.
restart() is a method for a sf::Clock THe first link in my code is for it.
Nobody has mentioned this but you should use deltatime so that the velocity is the same on each computer. Something like:


I've you're running at a fixed FPS, then the framerate drives the logic rate. His logic will update at 60 FPS therefore it will run consistent on all machines without additional timing code.
60 FPS != Each frame takes 1/60 of a second. So it will slightly jitter. Anyways, just because you tell it you want a limit of 60fps != it will always have 60 fps. By the limit would be the "max" not the min and max. http://sfml-dev.org/documentation/2.1/classsf_1_1Window.php#af4322d315baf93405bf0d5087ad5e784
Limit the framerate to a maximum fixed frequency.

If a limit is set, the window will use a small delay after each call to display() to ensure that the current frame lasted long enough to match the framerate limit. SFML will try to match the given limit as much as it can, but since it internally uses sf::sleep, whose precision depends on the underlying OS, the results may be a little unprecise as well (for example, you can get 65 FPS when requesting 60).


I would also suggest v-sync there is no need to let someone get an fps of say 120 if their monitor only has 60hz. That could cause weird visual effects.
Last edited on
Fair enough. I'm just trying to keep it simple. I'm already bombarding him with physics stuff =P.

FWIW, there are downsides to scaling logic by the passed time as well. Notably, slower computers are more likely to "tunnel" by taking larger logic steps. And it makes it next to impossible to do record/playback functionality since the exact gameflow is non-deterministic.

I prefer a hybrid technique, myself. But I won't get into details unless you're really interested.
Last edited on
I would be interested in your method. Though, I don't use SFML very often anymore; I prefer Unity and its component based system makes life very easy. Though implementing a component based system in c++ wouldn't be all that trivial.
Ok now it says:

73|error: no match for 'operator+=' (operand types are 'sf::Vector2f {aka sf::Vector2<float>}' and 'float')|

I also enabled Vsync, but I still can't get the sprite to stop when it hits the other parts of the screen.

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
140
141
142
143
144
145
146
#include <iostream>
#include <math.h>
#include <SFML/Graphics.hpp>

using namespace std;

int main()
{
    sf::Vector2f position;
    sf::Vector2f velocity;
    float maxspeed = 4.0f;
    float accel = 1.5f;
    float decel = 0.01f;
    bool vSyncEnabled = true;

    //Setup Window and Framerate
    sf::RenderWindow window(sf::VideoMode(800, 600), "Sprite Movement");
    window.setFramerateLimit(60);
    window.setVerticalSyncEnabled(vSyncEnabled);

    //Load Texture
    sf::Texture texture;
    if(!texture.loadFromFile("Resources/Textures/triangle.png"))
    {
        cout << "Error loading resource '::Texture'" << endl;
    }
    else
    {
        cout << "Texture Loaded" << endl;
    }

    //Set Sprite
    sf::Sprite sprite;
    sprite.setTexture(texture);
    sprite.setOrigin(sf::Vector2f(0, 0));
    sprite.setPosition(400, 300);

    //Collision
    sf::FloatRect boundingBox = sprite.getGlobalBounds();
    sf::Vector2f point = sprite.getPosition();
    if(boundingBox.contains(point))
    {

    }

    //Set delta time - This ensures the velocity is the same on all computers
    sf::Clock deltaTime;

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

        while(window.pollEvent(event))
        {
            switch(event.type)
            {
                case sf::Event::Closed:
                {
                    window.close();
                }
                break;
                //Dont stretch the window contents when its resized
                case sf::Event::Resized:
                {
                    sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
                    window.setView(sf::View(visibleArea));
                }
                break;
            }
        }

        window.clear(sf::Color::White);
        //velocity += accel * deltaTime.restart().asSeconds();

        window.draw(sprite);

        //WASD Movement
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
        {
            velocity.x -= accel;
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
        {
            velocity.x += accel;
        }
        else
        {
            velocity.x *= decel;
        }
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
        {
            velocity.y -= accel;
        }
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
        {
            velocity.y += accel;
        }
        else
        {
            velocity.y *= decel;
        }

        //Make sure the sprite isn't going too fast
        if(velocity.x < -maxspeed) velocity.x = -maxspeed;
        if(velocity.x >  maxspeed) velocity.x =  maxspeed;
        if(velocity.y < -maxspeed) velocity.y = -maxspeed;
        if(velocity.y >  maxspeed) velocity.y =  maxspeed;

        //Update sprite position and move sprite
        position += velocity;
        sprite.setPosition(position);

        /*
        float actualspeed = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y));

        if(actualspeed > maxspeed)
        {
            velocity *= maxspeed / actualspeed;
        }
        */

        sf::Vector2u currWSize = window.getSize();

        if(position.x < 0)
        {
            position.x = 0;
        }
        if(position.y <= 0)
        {
            position.y = 0;
        }
        if(position.x > currWSize.x)
        {
            position.x = currWSize.x;
        }
        if(position.y > currWSize.y)
        {
            position.y = currWSize.y;
        }

        window.display();
    }

    return 0;
}



BTW giblet, what is unity's component system? how is it easier?
Last edited on
Pages: 12