Я думаю, что вопрос показывает немного недопонимания того, как игровые движки должны быть разработаны. Что совершенно нормально, потому что это чертовски сложные вещи, которые трудно понять правильно;)
У вас правильное впечатление, что вы хотите то, что называется независимостью от частоты кадров. Но это касается не только рендеринга кадров.
Фрейм в однопоточных игровых движках обычно называется тик. Каждый тик вы обрабатываете ввод, обрабатываете игровую логику и визуализируете фрейм, основанный на результатах обработки.
То, что вы хотите сделать, это иметь возможность обрабатывать игровую логику на любом FPS (кадрах в секунду) и иметь детерминированный результат.
Это становится проблемой в следующем случае:
Проверьте ввод:
- Ввод ключа: «W», что означает, что мы двигаем персонажа игрока вперед на 10 единиц:
playerPosition + = 10;
Теперь, так как вы делаете это каждый кадр, если вы работаете со скоростью 30 кадров в секунду, вы будете перемещать 300 единиц в секунду.
Но если вместо этого вы работаете со скоростью 10 FPS, вы будете перемещать только 100 единиц в секунду. И поэтому ваша игровая логика не Частота кадров не зависит.
К счастью, решить эту проблему и сделать игровую логику независимой от частоты кадров является довольно простой задачей.
Во-первых, вам нужен таймер, который будет отсчитывать время рендеринга каждого кадра. Это число в секундах (т. Е. 0,001 секунды для завершения тика) умножается на то, что вы хотите быть независимым от частоты кадров. Так что в этом случае:
Удерживая 'W'
playerPosition + = 10 * frameTimeDelta;
(Delta - причудливое слово для «Измени что-то»)
Таким образом, ваш игрок будет перемещать некоторую долю 10 за один тик, и после полной секунды тиков вы переместите полные 10 единиц.
Однако, это будет падать, когда речь идет о свойствах, где скорость изменения также меняется со временем, например, ускорение транспортного средства. Эту проблему можно решить с помощью более продвинутого интегратора, такого как «Verlet».
Многопоточный подход
Если вы все еще заинтересованы в ответе на ваш вопрос (поскольку я не ответил на него, но представил альтернативу), вот он. Разделение игровой логики и рендеринга на разные темы. У него есть свои недостатки, хотя. Достаточно, чтобы подавляющее большинство игровых движков оставалось однопоточным.
Это не значит, что в так называемых однопоточных двигателях работает только один поток. Но все значимые задачи обычно находятся в одном центральном потоке. Некоторые вещи, такие как Collision Detection, могут быть многопоточными, но обычно фаза Collision блока Tick блокируется до тех пор, пока все потоки не вернутся, и механизм не вернется к одному потоку выполнения.
Многопоточность представляет целый, очень большой класс проблем, даже некоторые из которых касаются производительности, поскольку все, даже контейнеры, должны быть поточно-ориентированными. И игровые движки - это очень сложные программы, поэтому они редко стоят того, чтобы усложнить их многопоточность.
Подход с фиксированным временным шагом
Наконец, как заметил другой комментатор, наличие временного шага фиксированного размера и контроль того, как часто вы «шагаете» по игровой логике, также может быть очень эффективным способом решения этой проблемы со многими преимуществами.
Здесь приведена ссылка для полноты, но другой комментатор также ссылается на нее:
Fix Your Time Step