This has been bugging me for few days now. I'm trying to stop movement jittering in my game. I've looked towards semi and fixed time steps - moving towards the latter below so I can really understand (as I hope to create multiplayer games in future).
I have a very basic set up with a ball that moves at a constant speed, unless the user tries to control it by pressing P to takeover. It's currently got a hard-coded 60 FPS value. When the accumulated time reaches that, the update is called and the ball is moved.
My issue is that I get occasional stuttering during movement and I really can't grasp why. Is there anything you guys would recommend to better what I have below?
Thank you in advance!
#include <iostream>
#include <SFML/Graphics.hpp>
int main() {
sf::RenderWindow window(sf::VideoMode(1920, 1080), "Ping Pong");
// window.setFramerateLimit(60);
// window.setKeyRepeatEnabled(false);
sf::Font font;
font.loadFromFile("arial.ttf");
sf::Text text;
text.setFillColor(sf::Color::Green);
text.setCharacterSize(72);
text.setFont(font);
text.setString("hello: ");
sf::CircleShape circle;
circle.setRadius(20.0f);
circle.setOrigin(circle.getRadius(), circle.getRadius());
circle.setPosition(window.getSize().x/2, window.getSize().y/2);
float TIME_PER_FRAME = 1.0f / 60.0f;
sf::Clock clock;
float lastFrameTime = clock.getElapsedTime().asSeconds();
float frameStartTime;
float frameTime;
float accumulator;
int updateCount;
bool movable = false;
while(window.isOpen()) {
updateCount = 0;
frameStartTime = clock.getElapsedTime().asSeconds();
frameTime = frameStartTime - lastFrameTime;
accumulator += frameTime;
lastFrameTime = frameStartTime;
// events
sf::Event event;
while(window.pollEvent(event)) {
if (event.type == sf::Event::KeyPressed) {
if (event.key.code == sf::Keyboard::Escape) {
window.close();
}
//make ball controllable by player
if (event.key.code == sf::Keyboard::P) {
movable = (movable) ? false : true;
}
}
}
// update
while (accumulator >= TIME_PER_FRAME) {
//character movable by WASD
if (movable) {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
circle.move(sf::Vector2f(200*TIME_PER_FRAME,0));
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
circle.move(sf::Vector2f(-200*TIME_PER_FRAME,0));
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
circle.move(sf::Vector2f(0,-200*TIME_PER_FRAME));
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
circle.move(sf::Vector2f(0,200*TIME_PER_FRAME));
}
}
//automated movement to the right
if (!movable) {
circle.move(sf::Vector2f(200*TIME_PER_FRAME,0));
}
//if ball goes off the right of the screen, appear on the left side
sf::Vector2f c_position = circle.getPosition();
if (c_position.x - circle.getRadius() > window.getSize().x) {
circle.setPosition(sf::Vector2f(0-circle.getRadius(), c_position.y));
}
accumulator -= TIME_PER_FRAME;
updateCount++;
}
//render
window.clear();
window.draw(circle);
window.draw(text);
window.display();
std::cout << "updated " << updateCount << std::endl;
}
return 0;
}
EDIT:
I've had a look since the original implementation and the suggested interpolation steps. I tried initially to update the accumulator only in the while loop, interpolating (updating with the interpolating) afterwards but made little progress.
Since then I've created a simple player class and only interpolating on the rendering. This hasn't resolved it either. Below are my attempts:
main.cpp
#include <iostream>
#include <SFML/Graphics.hpp>
#include "player.cpp"
sf::Vector2f linearInterpolation(sf::Vector2f start, sf::Vector2f end, float alpha) {
return (start * (1 - alpha) + end * alpha);
}
sf::Vector2f addTwoVectors(sf::Vector2f v1, sf::Vector2f v2) {
return sf::Vector2f(v1.x + v2.x, v1.y + v2.y);
}
int main() {
sf::RenderWindow window(sf::VideoMode(1920, 1080), "Ping Pong");
window.setFramerateLimit(1000000);
// window.setKeyRepeatEnabled(false);
sf::Font font;
font.loadFromFile("arial.ttf");
sf::Time time;
sf::Text text;
text.setFillColor(sf::Color::Green);
text.setCharacterSize(72);
text.setFont(font);
text.setString("hello: ");
sf::CircleShape circle;
circle.setRadius(20.0f);
circle.setOrigin(circle.getRadius(), circle.getRadius());
circle.setPosition(window.getSize().x/8, window.getSize().y/2);
Player player = Player(window);
const float speed = 2.0f;
float TIME_PER_FRAME = 1.0f / 60.0f;
sf::Clock clock;
float lastFrameTime = clock.getElapsedTime().asSeconds();
float frameStartTime = 0;
float frameTime = 0;
float accumulator = 0;
float lastaccumulator = 0;
float alpha = 0;
int updateCount = 0;
int nonUpdateCount = 0;
bool movable = false;
while(window.isOpen()) {
frameStartTime = clock.getElapsedTime().asSeconds();
frameTime = frameStartTime - lastFrameTime;
lastaccumulator = accumulator;
accumulator += frameTime;
lastFrameTime = frameStartTime;
// events
sf::Event event;
while(window.pollEvent(event)) {
if (event.type == sf::Event::KeyPressed) {
if (event.key.code == sf::Keyboard::Escape) {
window.close();
}
//make ball controllable by player
if (event.key.code == sf::Keyboard::P) {
movable = (movable) ? false : true;
}
}
}
// update
while (accumulator > TIME_PER_FRAME) {
player.update();
std::cout << "----------- updated one frame -----------" << std::endl;
std::cout << "player position: " << player.getPosition().x << ", " << player.getPosition().y << std::endl;
accumulator -= TIME_PER_FRAME;
}
alpha = accumulator / TIME_PER_FRAME;
std::cout << "\n";
std::cout << "accumulator: " << accumulator << std::endl;
std::cout << "alpha: " << alpha << std::endl;
//render
window.clear();
player.draw(alpha);
window.draw(text);
window.display();
}
return 0;
}
player.cpp
#include <SFML/Graphics.hpp>
class Player {
private:
sf::RenderWindow& window;
sf::Vector2f speed;
sf::CircleShape circle;
sf::CircleShape i_circle;
public:
Player(sf::RenderWindow& window) : window(window) {
this->speed = sf::Vector2f(5.0f, 0);
circle.setRadius(20.0f);
circle.setOrigin(circle.getRadius(), circle.getRadius());
i_circle.setRadius(20.0f);
i_circle.setOrigin(i_circle.getRadius(), i_circle.getRadius());
sf::Vector2f middleOfScreen = sf::Vector2f(window.getSize().x/2, window.getSize().y/2);
circle.setPosition(middleOfScreen);
};
~Player() {};
sf::Vector2f getPosition() {
return circle.getPosition();
}
void update(){
this->circle.move(speed);
//if ball goes off the right of the screen, appear on the left side
sf::Vector2f c_position = circle.getPosition();
if (c_position.x - circle.getRadius() > window.getSize().x) {
sf::Vector2f newPosition = sf::Vector2f(0-circle.getRadius(), c_position.y);
circle.setPosition(newPosition);
}
};
void draw(float interpolate) {
i_circle.setPosition(circle.getPosition() + speed*interpolate);
std::cout << "i_circle pos: " << i_circle.getPosition().x << ", " << i_circle.getPosition().y << std::endl;
window.draw(i_circle);
};
};
If you have any ideas, I'd appreciate the help, thank you!
sf::Vector2f spawnPositionto the constructor and use that. For thedrawfunction pass in a reference to the window. \$\endgroup\$