Чому деякі старі ігри працюють занадто швидко на сучасному обладнанні?


64

У мене є кілька старих програм, я зняв комп'ютер Windows раннього 90-х років і спробував запустити їх на порівняно сучасному комп'ютері. Цікаво, що вони бігали зі спалахуючою швидкістю - ні, не 60 кадрів на секунду швидко, швидше, о-о-мій-бог-персонаж-ходить-на-швидкості звуку швидко. Я натиснув би клавішу зі стрілкою, а спрайт символу блискав би по екрану набагато швидше, ніж зазвичай. Прогресування часу в грі відбувалося набагато швидше, ніж слід. Існують навіть програми, що сповільнюють ваш процесор, щоб ці ігри були насправді відтворюваними.

Я чув, що це пов’язано з грою залежно від циклу процесора, чи щось подібне. Мої запитання:

  • Чому старі ігри роблять це, і як вони від них пішли?
  • Як новіші ігри не роблять цього і не запускаються незалежно від частоти процесора?

Це було деякий час назад, і я не пригадую, щоб робити якісь хитрощі щодо сумісності, але це вже не в питанні. Існує багато інформації про те, як це виправити, але не стільки про те , чому саме вони так працюють, про що я і запитую.
TreyK

9
Пам'ятаєте кнопку турбо на старих ПК? : D
Віктор Мелгрен

1
Ага. Змушує мене запам’ятати затримку на 1 секунду на ABC80 (шведський ПК на базі z80 з Basic). FOR F IN 0 TO 1000; NEXT F;
Макке

1
Просто для уточнення: "кілька старих програм, з яких я зняв комп'ютер Windows 90-х років", це програми DOS на машині Windows, або програми Windows, де така поведінка відбувається? Я звик бачити це на DOS, але не на Windows, IIRC.
Той бразильський хлопець

Дивіться також CPU або
рамкові

Відповіді:


52

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

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

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

Цей посібник сильно ґрунтується на ПК Oldskool, де логіка та пам’ять мене не витримали - це чудове прочитання, і, ймовірно, йде більш глибоко в "чому".

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


14
Деякі з цих ігор навіть нічого не передбачали, вони працювали так швидко, як тільки могли, що було «грати» на цих процесорах ;-)
Ян Догген

2
Для інформації повторно. "Як новіші ігри не роблять цього і не запускаються незалежно від частоти процесора?" спробуйте пошукати щось на зразок gamedev.stackexchange.com game loop. В основному є 2 методи. 1) Бігайте якомога швидше та масштабуйте швидкість руху тощо залежно від того, наскільки швидко працює гра. 2) Якщо ви занадто швидкі, зачекайте ( sleep()), поки ми не будемо готові до наступного "галочки".
Джордж Дакетт

24

Як додаток до відповіді Journeyman Geek (через те, що мою редакцію відхилено) для людей, які цікавляться кодуючою частиною / перспективою розробника:

З точки зору програмістів, для тих, хто цікавиться, часи DOS були періодами, коли кожна галочка процесора була важливою, тому програмісти зберігали код якомога швидше.

Типовий сценарій, коли будь-яка програма буде працювати з максимальною швидкістю процесора, такий простий (псевдо С):

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 кадрів в секунду.

Що можуть зробити розробники поточного дня:

  1. Увімкнути V-синхронізацію в грі (* недоступно для віконних програм ** [він також доступний лише у повноекранних програмах])
  2. Виміряйте різницю у часі між останнім оновленням та оновленням фізики відповідно до різниці у часі, що ефективно змушує гру / програму працювати з однаковою швидкістю незалежно від швидкості FPS
  3. Програмуйте обмеження частоти кадрів

*** В залежності від конфігурації графічної карти / драйвера / OS це може бути можливо.

Для пункту 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+ FPS на екрані, оскільки цикл працює як можна швидше. Оскільки двигун фізики знає, скільки часу пройшло, це не повинно залежати від "жодних припущень" чи "певного фреймрейду", тому гра буде працювати з однаковою швидкістю на будь-якому процесорі.

3:

Що розробники можуть зробити, щоб обмежити частоту кадрів, наприклад, 30 FPS - це насправді нічого складніше, просто подивіться:

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

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

Для справді старих ігор на ПК вони просто бігали так швидко, як могли, не зважаючи на те, що намагаються темп гри. Так було більше у дні IBM PC XT, однак там, де існувала турбо кнопка, яка сповільнила систему з відповіддю процесору 4,77 МГц з цієї причини.

Сучасні ігри та бібліотеки, такі як DirectX, мають доступ до таймерів з високою прецесією, тому не потрібно використовувати калібровані затримки на основі коду.


4

На початку всі ПК працювали з однаковою швидкістю, тому не було потреби враховувати різницю швидкостей.

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

На сьогоднішній день, з юними дітьми та вишуканими шутерами FPS, ви можете дивитись на підлогу одну секунду, а на великому каньйоні наступне, коливання навантаження відбувається частіше. :)

(І мало апаратних консолей досить швидко, щоб постійно запускати ігри зі швидкістю 60 кадрів в секунду. Це пов'язано з тим, що розробники консолей вибирають 30 Гц і роблять пікселі вдвічі блискучішими ...)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.