如何解决SFML 3.0中Pong游戏的移动卡顿问题?
Pong游戏移动卡顿问题排查
我的Pong游戏移动时非常卡顿,推测问题出在程序休眠时长的计算方式上。游戏已设置最大帧率为60,且我的电脑性能足以达到该帧率。我参考了《Fix Your Timestep》指南,但不确定是否正确实现。
代码实现
main.cpp
#include <SFML/Graphics.hpp> #include <iostream> #include "Paddle.hpp" int framerateLimit = 60; unsigned int width = 800; unsigned int height = 600; int main() { sf::RenderWindow window(sf::VideoMode({width, height}), "My window"); window.setFramerateLimit(framerateLimit); sf::RectangleShape leftPaddle; leftPaddle.setSize(sf::Vector2f(10,50)); leftPaddle.setPosition(sf::Vector2f(20, height / 2.0 - leftPaddle.getSize().y/2)); std::cout << leftPaddle.getPosition().x << "," << leftPaddle.getPosition().y << std::endl; sf::RectangleShape rightPaddle; rightPaddle.setSize(sf::Vector2f(10, 50)); rightPaddle.setPosition(sf::Vector2f(width - 20 - leftPaddle.getSize().x, height / 2.0 - leftPaddle.getSize().y / 2)); sf::Clock clock; const float deltaTime = 0.01f; float accumulator = 0.0f; float currentTime = clock.getElapsedTime().asSeconds(); while (window.isOpen()) { int64_t startTicks = clock.getElapsedTime().asMilliseconds(); float newTime = clock.getElapsedTime().asSeconds(); float frameTime = newTime - currentTime; currentTime = newTime; accumulator += frameTime; while (accumulator >= deltaTime) { while (const std::optional event = window.pollEvent()) { // "close requested" event: we close the window if (event->is<sf::Event::Closed>()) { window.close(); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W)) { Paddle::moveUp(leftPaddle); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S)) { Paddle::moveDown(leftPaddle); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up)) { Paddle::moveUp(rightPaddle); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down)) { Paddle::moveDown(rightPaddle); } } accumulator -= deltaTime; } const float alpha = accumulator / deltaTime; window.clear(sf::Color::Black); window.draw(leftPaddle); window.draw(rightPaddle); window.display(); int frameTicks = clock.getElapsedTime().asMilliseconds() - startTicks; if (frameTicks < 1000 / framerateLimit) { int duration = 1000 / framerateLimit - frameTicks; sf::sleep(sf::milliseconds(duration)); } } }
Paddle.cpp
#include "Paddle.hpp" void Paddle::moveUp(sf::RectangleShape& paddle) { paddle.getPosition().y <= 10.f ? paddle.setPosition(sf::Vector2f(paddle.getPosition().x,0)) : paddle.move(sf::Vector2f(0, -10)); } void Paddle::moveDown(sf::RectangleShape& paddle) { paddle.getPosition().y >= height - paddle.getSize().y - 10 ? paddle.setPosition(sf::Vector2f(paddle.getPosition().x,height - paddle.getSize().y)) : paddle.move(sf::Vector2f(0, 10)); }
问题根源
- 输入处理嵌套错误:键盘输入检测放在了固定时间步循环内,当accumulator包含多个deltaTime时,同一帧内会重复执行移动逻辑,导致 paddle 多次位移,移动速度忽快忽慢,视觉上卡顿。
- 重复帧率控制:同时使用
window.setFramerateLimit(60)和手动计算休眠时长,两者冲突,导致帧率不稳定。 - 移动逻辑无时间绑定:每次移动固定10像素,帧率波动时移动速度随帧率变化,造成卡顿感。
- 事件循环重复执行:在固定时间步循环内调用
pollEvent,同一帧内多次处理事件,可能引发异常行为。
修复方案
1. 调整输入与事件处理流程
将事件处理和键盘输入检测移出固定时间步循环,确保每一帧只处理一次输入:
while (window.isOpen()) { // 先处理所有窗口事件 while (const std::optional event = window.pollEvent()) { if (event->is<sf::Event::Closed>()) { window.close(); } } // 每一帧只检测一次键盘状态 bool leftUp = sf::Keyboard::isKeyPressed(sf::Keyboard::W); bool leftDown = sf::Keyboard::isKeyPressed(sf::Keyboard::S); bool rightUp = sf::Keyboard::isKeyPressed(sf::Keyboard::Up); bool rightDown = sf::Keyboard::isKeyPressed(sf::Keyboard::Down); // 时间步计算 float newTime = clock.getElapsedTime().asSeconds(); float frameTime = newTime - currentTime; currentTime = newTime; accumulator += frameTime; // 固定时间步更新游戏逻辑 while (accumulator >= deltaTime) { if (leftUp) Paddle::moveUp(leftPaddle, deltaTime); if (leftDown) Paddle::moveDown(leftPaddle, deltaTime); if (rightUp) Paddle::moveUp(rightPaddle, deltaTime); if (rightDown) Paddle::moveDown(rightPaddle, deltaTime); accumulator -= deltaTime; } // 渲染 window.clear(sf::Color::Black); window.draw(leftPaddle); window.draw(rightPaddle); window.display(); }
2. 修改移动逻辑为时间驱动
让移动速度基于时间步,确保不同帧率下移动速度一致:
// 在Paddle.hpp中添加常量定义 const float PADDLE_SPEED = 300.0f; // 每秒移动300像素 extern unsigned int height; // Paddle.cpp修改为 void Paddle::moveUp(sf::RectangleShape& paddle, float deltaTime) { float moveDist = PADDLE_SPEED * deltaTime; float newY = paddle.getPosition().y - moveDist; paddle.setPosition(paddle.getPosition().x, std::max(newY, 0.0f)); } void Paddle::moveDown(sf::RectangleShape& paddle, float deltaTime) { float moveDist = PADDLE_SPEED * deltaTime; float newY = paddle.getPosition().y + moveDist; float maxY = height - paddle.getSize().y; paddle.setPosition(paddle.getPosition().x, std::min(newY, maxY)); }
3. 移除重复的休眠代码
删除手动计算休眠的代码块,仅保留window.setFramerateLimit(60)即可,SFML的帧率控制足够稳定。
4. 优化时间初始化
确保currentTime在进入循环前正确初始化(原代码已正确实现,无需修改)。
内容的提问来源于stack exchange,提问作者Omar Khaled
相关产品推荐
相关产品推荐

