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.
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.
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.
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.
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
elseif(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;
elseif(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?
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.
// 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?
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,
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.
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.
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.
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.