С++ Jittery игровой цикл - как сделать его максимально плавным?

Это мой код игрового цикла:

        while (shouldUpdate)
        {
            timeSinceLastUpdate = 0;
            startTime = clock();

                while (timeAccumulator >= timeDelta)
                {
                    listener.handle();
                    em.update();
                    timeAccumulator -= timeDelta;
                    timeSinceLastUpdate += timeDelta;
                }

            rm.beginRender();
                _x->draw();
            rm.endRender();

            timeAccumulator += clock() - startTime;
        }

Он работает почти идеально, но у него есть некоторое дрожание, несколько раз в секунду вместо _x (тестовый объект, который все, что он делает в обновлении, это x++) перемещается на 1 пиксель вправо, он фактически перемещается на 2 пикселя вправо и это заметный эффект задержки/джиттера. Я предполагаю, что clock() недостаточно точен. Итак, что я могу сделать, чтобы улучшить этот игровой цикл?

Если это имеет значение, я использую SDL и SDL_image.

EDIT: ничего не изменилось после того, как мы сделали что-то более точное, чем часы. НО, что я понял, так это то, что все это благодаря timeDelta. Вот как было определено timeDelta, когда я сделал этот пост:

double timeDelta = 1000/60;

но когда я определил это как что-то другое, возясь...

double timeDelta = 16.666666;

Я заметил, что в первые несколько секунд игра запустилась, она была гладкой, как по маслу. Но всего через несколько секунд игра сильно заикалась, а затем снова стала плавной и повторилась. Чем больше 6 (или что-то после . на самом деле) я добавлял, тем дольше игра изначально была гладкой и тем сильнее лаг, когда это происходило. Кажется, атакуют плавающие ошибки. Так что я могу сделать тогда?

EDIT2: я перепробовал так много вещей, что это даже не смешно... может кто-нибудь помочь мне с математической частью цикла? Так как это то, что вызывает это...

РЕДАКТИРОВАТЬ 3: Я отправил тестовую программу некоторым людям, некоторые сказали, что она была идеально гладкой, а другие сказали, что она была дрожащей, как я ее описал. Для всех, кто захочет протестировать это, вот оно (src): https://www.mediafire.com/?vfpy4phkdj97q9j

EDIT4: я изменил ссылку на исходный код.


person Accumulator    schedule 16.07.2015    source источник
comment
Либо выберите std::chrono для C++, либо проверьте это для C для самых точных таймеров.   -  person shauryachats    schedule 16.07.2015
comment
Не используйте clock() для таймера в чем-то вроде игры.   -  person RamblingMad    schedule 16.07.2015
comment
кажется, что timeDelta вызывает проблему   -  person Accumulator    schedule 17.07.2015
comment
timeDelta не должно не быть константой. Вы можете подождать, пока оно не станет больше или равно определенному значению, чтобы получить ограничение частоты кадров, но оно все равно должно рассчитываться для каждого кадра.   -  person RamblingMad    schedule 17.07.2015
comment
Не решение, но вам нужно вызвать часы только один раз в цикле. Как бы то ни было, вы вызываете его один раз в конце, а затем сразу же вызываете снова в начале цикла, когда значение будет почти точно таким же. установите startTime один раз перед началом цикла, затем установите его снова в конце цикла со значением, возвращаемым из clock()   -  person samgak    schedule 17.07.2015
comment
Я действительно не знаю, как рассчитать игровой цикл, я просто скопировал часть этого кода из Интернета и отредактировал его, поскольку я буквально не могу найти примеры игровых циклов на C++.   -  person Accumulator    schedule 17.07.2015
comment
@Omega не относится к C ++, в псевдокоде есть множество примеров игровых циклов.   -  person RamblingMad    schedule 21.07.2015
comment
ТАКЖЕ вы должны предоставить исходный код, а не исполняемый файл. Люди обычно не хотят запускать случайные исполняемые файлы из mediafire. Я даже не запускаю Windows, поэтому я не могу проверить это без исходного кода.   -  person RamblingMad    schedule 21.07.2015
comment
1000/60 — целочисленное деление, в результате получается 16. Если вы хотите лучший дубль, то используйте 1000.0/60. Но лучше было бы предложение CoffeeandCode об ограничении частоты кадров.   -  person Teepeemm    schedule 22.07.2015


Ответы (1)


Это почти наверняка будет из-за точности clock()

Используйте std::chrono или SDL_GetTicks() для измерения времени с начала эпохи.

Я бы рекомендовал использовать std::chrono только потому, что предпочитаю C++ API, вот пример:

int main(){
    using clock = std::chrono::high_resolution_clock;
    using milliseconds = std::chrono::milliseconds;
    using std::chrono::duration_cast;

    auto start = clock::now(), end = clock::now();
    uint64_t diff;


    while(running){
        diff = duration_cast<milliseconds>(end - start).count();
        start = clock::now();     

        // do time difference related things

        end = clock::now();
    }
}

Чтобы обновлять только после указанной дельты, вы должны сделать свой цикл следующим образом:

int main(){
    auto start = clock::now(), end = clock::now();
    uint64_t diff = duration_cast<milliseconds>(end - start).count();

    auto accum_start = clock::now();
    while(running){
        start = clock::now();
        diff = duration_cast<milliseconds>(end - start).count();

        if(duration_cast<nanoseconds>(clock::now() - accum_start).count() >= 16666666){
            // do render updates every 60th of a second
            accum_start = clock::now();
        }

        end = clock::now();
    }
}

start и end оба будут иметь тип std::chrono::time_point<clock>, где clock ранее было определено нами как std::chrono::high_resolution_clock.

Разница между двумя time_point будет std::chrono::duration, которая может выражаться в наносекундах, миллисекундах или любых других единицах измерения, которые вам нравятся. Мы переводим его в миллисекунды, а затем получаем count() для назначения нашему uint64_t. Вы можете использовать другой целочисленный тип, если хотите.

Мое diff — это то, как вы должны рассчитывать свое timeDelta. Вы не должны не устанавливать его как константу. Даже если вы уверены, что это будет правильно, вы ошибаетесь. Каждый кадр будет иметь разную дельту, даже если это наименьшая доля секунды.

Если вы хотите установить постоянную разницу кадров, используйте SDL_GL_SetSwapInterval для установки вертикальной синхронизации.

РЕДАКТИРОВАТЬ

Специально для вас я создал этот репозиторий git для примера. Обратите внимание на main.cpp, где я умножаю на diff, чтобы получить скорректированная разница на кадр. Эта корректировка (или ее отсутствие) и вызывает у вас нервозность.

person RamblingMad    schedule 16.07.2015
comment
не могли бы вы подсказать...? - person Accumulator; 20.07.2015
comment
о timeaccum... ну, я заставил вашу версию цикла работать, и я заставил timeaccum работать следующим образом pastebin.com/62f3Vbmf но он все еще дрожит, как мне это делать? - person Accumulator; 21.07.2015
comment
Что такое em, о котором вы говорите? - person RamblingMad; 21.07.2015
comment
Смотри, я добавлю пример того, как обновлять вручную только после timeDelta, чтобы ты мог это сделать, если хочешь. Но вы действительно должны полагаться только на свою вертикальную синхронизацию для такого поведения. - person RamblingMad; 21.07.2015
comment
еще одна вещь, есть ли способ сделать это без умножения каждого числа на dt? - person Accumulator; 22.07.2015
comment
Пробовал второй пример, но чего-то не хватает. diff всегда близок к нулю. Оба метода сжигают процессор до 100%. - person Yiannis Mpourkelis; 29.01.2019