Отделение логики / обновления от кода рендеринга / рисования в одном потоке с помощью сна

9

Я читал, что скорость игровых объектов не должна ограничиваться FPS, а должна зависеть от времени. Как можно разделить код обновления / прорисовки, чтобы максимизировать производительность, не ограничивая скорость прорисовки и обеспечивая постоянную частоту обновления логики в зависимости от времени?

Мой текущий псевдокод выглядит следующим образом

loop
{
    draw();
    if (ticksElapsed() > 100)
    {
        update();
        ticks+= ticksElapsed();
    }        
}

Проблема в том, что код рисования препятствует повышению скорости обновления (). И он потребляет 100% процессорного времени, потому что если включить режим сна, он отбрасывает обе функции рисования / логики.

Я также использую SDL, и у него нет опции vsync. Я также слышал о терминах фиксированный и переменный шаг по времени, однако я не уверен, как это можно сделать с помощью сна ()

Оскенсо Каши
источник
1
Вам не нужно тратить 100% мощности процессора только на ожидание, поместите sleep (0) в конце цикла while, если ticksElapsed () <100. ОС немедленно вернется к потоку, если нет другого потока, который хочет бежать. Но не теряю 100% мощности процессора больше.
Майк Земдер
Однако лучшее решение для такой однопоточной настройки - использовать vsync, если вы не можете vsync, а затем вызывать sleep (0) в цикле, пока не достигнете целевой частоты кадров, затем обновить и отрисовать
Maik Semder

Ответы:

3

В вашем фрагменте кода похоже, что вы пытаетесь запустить игру в режиме фиксированного шага, ожидая занятости, если ваш рисунок и обновление заняли менее 15 мс (60 кадров в секунду). Это возможно, и вы правильно догадались, что это невозможно сделать с помощью вызова в режиме сна, потому что вы точно не знаете, как долго вы собираетесь спать. Петля занятого ожидания - хорошее решение.

Однако рассмотрим случай, когда ваши обновления и рисования превышают 15 мс, теперь у вас есть игровой рисунок и обновление замедляться. Теперь вы можете сделать две вещи: обнаружить это состояние и пропустить кадры (пропустить рисование и перейти непосредственно к обновлению, пока вы не синхронизируете снова), однако, если компьютер просто замедляется, он никогда не нагонит.

Другое решение - сделать вашу логику обновления фиксированной по времени независимой. Для этого вам не нужна отдельная тема, вам просто нужно указать, как быстро все должно двигаться. Вместо 5 пикселей на тик, вы должны использовать 50 пикселей в секунду. Для этого вам понадобится высокоточный таймер, и вся ваша логика обновления должна иметь доступ к таймеру, чтобы увидеть, сколько времени прошло с момента последнего обновления.

В основном вы идете из:

void UpdatePlayer()
 player.x += 10;

к

void UpdatePlayer(float elapsedSeconds) //the total seconds elapsed since last update
 player.x += walkspeed * elapsedSeconds;
Рой Т.
источник
Таким образом, мой двигатель всегда будет потреблять 100%, и я ничего не могу поделать?
Оскенсо Каши
1
Он будет потреблять столько циклов, сколько позволяет планировщик, но это не проблема, поскольку вы не можете делать много других вещей, играя в игру :).
Рой Т.
@Oskenso Однако, если вы используете более 1 потока, проблема заключается в том, что основной поток не позволит другим работать так же долго, как они могли бы, тратя большую вычислительную мощность в цикле while, вы действительно должны рассмотреть режим сна
Майк Земдер
@Maik Semder: у вас есть решение для сна (х), не является точным? По истечении интервала ожидания поток готов к работе. Но готовый поток не гарантированно запускается немедленно. Это до планировщика. Когда вы используете два потока, есть и другие решения, для этого посмотрите эту прекрасную статью: altdevblogaday.com/2011/07/03/threading-and-your-game-loop
Рой Т.
1
@Roy sleep (0) - это решение. Он немедленно возвращается, если нет другого потока, который хочет работать ( Sleep WinAPI ), и дает другим потокам возможность работать. Если другой поток не даст основному потоку возможность работать взамен, у вас возникнет проблема с потоками, но блокирование всего остального, не вызывая спящий режим, делает его еще хуже и вряд ли является решением. Ключ в том, чтобы вызвать sleep (0) и проверить истекшее время до тех пор, пока вы не достигнете своей целевой фиксированной частоты кадров, чтобы не тратить 100% ЦП только на ожидание.
Майк Земдер