Как дополнение к ответу Journeyman Geek (потому что мое редактирование было отклонено) для людей, которые интересуются частью кода / разработчиком:
С точки зрения программистов, для тех, кто интересуется, времена DOS были временем, когда каждый такт процессора был важен, поэтому программисты сохраняли код как можно быстрее.
Типичный сценарий, в котором любая программа будет работать с максимальной скоростью ЦП, выглядит следующим образом (псевдо-C):
int main()
{
while(true)
{
}
}
это будет работать вечно, теперь давайте превратим этот фрагмент кода в псевдо-DOS-игру:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
если функции DrawGameOnScreen
используют двойную буферизацию /V-синхронизацию (что было довольно дорого в те времена, когда создавались игры для DOS), игра будет работать с максимальной скоростью процессора.
На современном мобильном i7 это будет работать примерно от 1 000 000 до 5 000 000 раз в секунду (в зависимости от конфигурации ноутбука и текущего использования процессора).
Это означало бы, что если бы я мог заставить любую DOS-игру работать на моем современном процессоре в моих 64-битных окнах, я мог бы получить больше тысячи (1000!) FPS, который слишком быстрый для любого человека, чтобы играть, если физическая обработка "предполагает", что он работает между 50-60 fps.
Разработчики текущего дня (могут):
- Включить V-Sync в игре (* недоступно для оконных приложений ** [также доступно только в полноэкранных приложениях])
- Измерьте разницу во времени между последним обновлением и обновите физику в соответствии с разницей во времени, которая эффективно заставляет игру / программу работать с одинаковой скоростью независимо от скорости FPS.
- Ограничить частоту кадров программно
*** это может быть возможно в зависимости от конфигурации видеокарты / драйвера / операционной системы.
Для пункта 1 нет примера, который я покажу, потому что на самом деле это не "программирование". Это просто использование графических функций.
Что касается пунктов 2 и 3, я покажу соответствующие фрагменты кода и пояснения:
2:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
DrawGameOnScreen();
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Здесь вы можете видеть, как пользовательский ввод и физика учитывают разницу во времени, но вы все равно можете получить более 1000 кадров в секунду на экране, потому что цикл работает максимально быстро. Поскольку физический движок знает, сколько времени прошло, он не должен зависеть от "никаких предположений" или "определенной частоты кадров", поэтому игра будет работать с одинаковой скоростью на любом процессоре.
3:
Что разработчики могут сделать, чтобы ограничить частоту кадров, например, до 30 кадров в секунду, на самом деле ничего сложнее, просто взгляните:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double FPS_WE_WANT = 30;
//how many milliseconds need to pass before we need to draw again so we get the framerate we want?
double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
//For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
double LastDraw = GetCurrentTime();
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//if certain amount of milliseconds pass...
if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
{
//draw our game
DrawGameOnScreen();
//and save when we last drawn the game
LastDraw = LastTick;
}
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Здесь происходит то, что программа считает, сколько миллисекунд прошло, если достигнут определенный объем (33 мс), то она перерисовывает игровой экран, эффективно применяя частоту кадров около ~ 30.
Кроме того, в зависимости от разработчика он / она может ограничить ВСЕ обработки до 30 кадров в секунду с немного измененным кодом выше:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double FPS_WE_WANT = 30;
//how many miliseconds need to pass before we need to draw again so we get the framerate we want?
double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
//For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
double LastDraw = GetCurrentTime();
while(GameRunning)
{
LastTick = GetCurrentTime();
TimeDifference = LastTick-LastDraw;
//if certain amount of miliseconds pass...
if(TimeDifference >= TimeToPassBeforeNextDraw)
{
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//draw our game
DrawGameOnScreen();
//and save when we last drawn the game
LastDraw = LastTick;
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
}
Есть несколько других методов, и некоторые из них я действительно ненавижу.
Например, используя sleep(<amount of milliseconds>)
.
Я знаю, что это один из способов ограничения частоты кадров, но что происходит, когда обработка вашей игры занимает 3 миллисекунды или больше? И тогда вы выполняете сон ...
это приведет к более низкой частоте кадров, чем та, которую должна вызывать только sleep()
.
Давайте, например, возьмем время сна 16 мс. это заставит программу работать на частоте 60 Гц. теперь обработка данных, ввод, рисование и все такое занимает 5 миллисекунд. Сейчас мы находимся на 21 миллисекунде для одной петли, что приводит к чуть менее 50 Гц, в то время как вы можете легко быть на 60 Гц, но из-за сна это невозможно.
Одним из решений было бы создание адаптивного сна в форме измерения времени обработки и вычитания времени обработки из требуемого сна, что приводит к исправлению нашей "ошибки":
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
long long NeededSleep;
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//draw our game
DrawGameOnScreen();
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
NeededSleep = 33 - (GetCurrentTime()-LastTick);
if(NeededSleep > 0)
{
Sleep(NeededSleep);
}
}
}