Ограничение цикла while для работы со скоростью 30 кадров в секунду с использованием дельта-переменной C++

В основном мне нужен цикл while, чтобы работать только со скоростью 30 кадров в секунду. Мне сказали сделать это: «Внутри цикла while создайте deltaT, и если эта deltaT меньше 33 миллисекунд, используйте sleep(33-deltaT)».

Но я действительно не совсем понял, как инициализировать дельту/что установить для этой переменной. Я также не мог получить ответ от человека, который предложил это.

Я также не уверен, почему значение во сне равно 33 вместо 30.

Кто-нибудь знает, что я могу сделать по этому поводу?

Это в основном для игрового сервера, чтобы обновлять игроков со скоростью 30 кадров в секунду, но, поскольку я не делаю никакого рендеринга на сервере, мне нужен способ просто заставить код спать, чтобы ограничить, сколько раз он может запускаться в секунду, иначе он будет обрабатывать игроков слишком быстро.


person Joe Bid    schedule 18.01.2015    source источник


Ответы (3)


В основном вам нужно сделать что-то вроде этого:

int now = GetTimeInMilliseconds();
int lastFrame = GetTimeInMilliseconds();

while(running)
{
    now = GetTimeInMilliseconds();
    int delta = now - lastFrame;
    lastFrame = now;

    if(delta < 33)
    {
        Sleep(33 - delta);
    }

    //...
    Update();
    Draw();
}

Таким образом, вы вычисляете количество миллисекунд, прошедшее между текущим кадром и последним кадром, и если оно меньше 33 миллисекунд (1000/30, 1000 миллисекунд в секунду, деленное на 30 кадров в секунду = 33,333333....), то вы спите до 33. миллисекунд прошло. Имеет функции GetTimeInMilliseconds() и Sleep(), это зависит от используемой библиотеки и/или платформы.

person UnTraDe    schedule 18.01.2015
comment
Это не зависит от платформы, <chrono> является переносимым. - person MSalters; 18.01.2015
comment
Кстати, int now = GetTimeInMilliseconds(); нужно заменить на int now;. - person HolyBlackCat; 18.01.2015
comment
Это неправильно, так как со временем он будет сдвигаться на время, необходимое для выполнения Update() и Draw(). Следующим lastFrame должно быть lastFrame + 33;, а не now. Иногда вам может понадобиться пропустить кадр. - person Alexis Wilke; 08.03.2018

С++ 11 предоставляет для этого простой механизм:

#include <chrono>
#include <thread>
#include <iostream>

using namespace std;
using namespace std::chrono;

void doStuff(){
    std::cout << "Loop executed" << std::endl;
}

int main() {

    time_point<system_clock> t = system_clock::now();

    while (1) {
        doStuff();
        t += milliseconds(33);
        this_thread::sleep_until(t);
    }
}

Единственное, о чем вы должны знать, это то, что если одна итерация цикла занимает больше времени, чем 33 мс, следующие две итерации будут выполняться без паузы между ними (пока t не догонит реальное время), что может или может быть не таким, каким ты хочешь.

person MikeMB    schedule 18.01.2015
comment
Вы можете легко настроить t, снова запросив now(), а если t слишком мало, мгновенно пропустить кадры. - person Alexis Wilke; 08.03.2018

Несколько лет назад Гленн Фидлер написал хорошую статью на эту тему. Взлом с помощью sleep() не очень точен, вместо этого вы хотите запускать свою физику фиксированное количество раз в секунду, позволить вашей графике работать свободно, а между кадрами вы делаете столько фиксированных временных шагов, сколько прошло времени.

Код, который следует, сначала выглядит пугающе, но как только вы уловите идею, он станет простым; лучше прочитать статью полностью.

Исправьте временной шаг

double t = 0.0;
double dt = 0.01;

double currentTime = hires_time_in_seconds();
double accumulator = 0.0;

State previous;
State current;

while ( !quit )
{
    double newTime = hires_time_in_seconds();
    double frameTime = newTime - currentTime;
    if ( frameTime > 0.25 )
        frameTime = 0.25;
    currentTime = newTime;

    accumulator += frameTime;

    while ( accumulator >= dt )
    {
        previousState = currentState;
        integrate( currentState, t, dt );
        t += dt;
        accumulator -= dt;
    }

    const double alpha = accumulator / dt;

    State state = currentState * alpha + 
        previousState * ( 1.0 - alpha );

    render( state );
}

Если он отключится, должно быть доступно несколько резервных копий; однако я помню Гаффера в играх уже много лет.

person Sebastian Mach    schedule 18.01.2015
comment
Лично мне нравится передавать дельту функции Update(), где происходит вся игровая логика, а затем учитывать дельту там, где это необходимо. - person UnTraDe; 18.01.2015
comment
Разве эта строка double newTime = time(); не должна использовать функцию hires_time_in_seconds(); вместо time()? - person Alexis Wilke; 08.03.2018
comment
@AlexisWilke: Хороший улов. - person Sebastian Mach; 08.03.2018