62

У меня есть несколько старых программ, которые я снял с компьютера ранней 90-х годов, и попытался запустить их на относительно современном компьютере. Интересно, что они бегали с невероятной скоростью - нет, не с частотой 60 кадров в секунду, а с типом «о, боже мой, персонаж ходит на скорости звука». быстро. Я нажимал клавишу со стрелкой, и спрайт персонажа проносился по экрану гораздо быстрее, чем обычно. Временная прогрессия в игре происходила намного быстрее, чем следовало. Есть даже программы, разработанные для замедления вашего процессора, чтобы в эти игры можно было играть.

Я слышал, что это связано с игрой в зависимости от циклов процессора или что-то в этом роде. Мои вопросы:

  • Почему старые игры делают это, и как им это сходит с рук?
  • Как новые игры не делают этого и работают независимо от частоты процессора?

4 ответа4

51

Я полагаю, что они предполагали, что системные часы будут работать с определенной частотой, и привязали свои внутренние таймеры к этой частоте. Большинство из этих игр, вероятно, работали в DOS и работали в реальном режиме (с полным, прямым доступом к оборудованию) и предполагали, что вы используете систему iirc 4,77 МГц для ПК и любой другой стандартный процессор этой модели для других систем, таких как Amiga.

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

Первоначально одним из способов обойти разную частоту процессора была старая добрая кнопка Turbo (которая замедляла работу вашей системы). Современные приложения находятся в защищенном режиме, а ОС имеет тенденцию управлять ресурсами - они не позволят приложению DOS (которое в любом случае работает в NTVDM на 32-разрядной системе) использовать весь процессор во многих случаях. Короче говоря, операционные системы стали умнее, как и API.

Основано на этом руководстве на ПК Oldskool, где логика и память подвели меня - это отличное чтение и, вероятно, более подробно объясняет "почему".

Такие вещи, как CPUkiller, используют как можно больше ресурсов, чтобы "замедлить" работу вашей системы, что неэффективно. Вам лучше использовать DOSBox для управления тактовой частотой, которую видит ваше приложение.

23

Как дополнение к ответу 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.

Разработчики текущего дня (могут):

  1. Включить V-Sync в игре (* недоступно для оконных приложений ** [также доступно только в полноэкранных приложениях])
  2. Измерьте разницу во времени между последним обновлением и обновите физику в соответствии с разницей во времени, которая эффективно заставляет игру / программу работать с одинаковой скоростью независимо от скорости FPS.
  3. Ограничить частоту кадров программно

*** это может быть возможно в зависимости от конфигурации видеокарты / драйвера / операционной системы.

Для пункта 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);
        }
    }
}
16

Одной из основных причин является использование контура задержки, который калибруется при запуске программы. Они подсчитывают, сколько раз цикл выполняется за известное время, и делят его для генерации меньших задержек. Затем его можно использовать для реализации функции sleep() для ускорения выполнения игры. Проблемы возникают, когда этот счетчик становится максимальным из-за того, что процессоры в цикле работают намного быстрее, поэтому небольшая задержка оказывается слишком маленькой. Кроме того, современные процессоры меняют скорость в зависимости от нагрузки, иногда даже в расчете на ядро, что делает задержку еще больше.

Для действительно старых компьютерных игр они просто бежали так быстро, как только могли, даже не пытаясь делать темп игры. Это было больше в те дни, когда у IBM PC XT была турборежим, который по этой причине замедлял работу системы до 4,77 МГц.

Современные игры и библиотеки, такие как DirectX, имеют доступ к таймерам высокой прецессии, поэтому нет необходимости использовать калиброванные циклы задержки на основе кода.

4

В начале все первые ПК работали с одинаковой скоростью, поэтому не было необходимости учитывать разницу скоростей.

Кроме того, многие игры в начале имели довольно фиксированную загрузку процессора, поэтому маловероятно, что некоторые кадры будут работать быстрее, чем другие.

В настоящее время, с вашими детьми и вашими модными стрелками FPS, вы можете смотреть на пол одну секунду, а в Гранд-Каньон на следующем, изменение нагрузки происходит чаще. :)

(И лишь несколько аппаратных консолей достаточно быстры, чтобы постоянно запускать игры со скоростью 60 кадров в секунду. В основном это связано с тем, что разработчики консолей выбирают 30 Гц и делают пиксели в два раза ярче ...)

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .