Perché alcuni vecchi giochi funzionano troppo velocemente su hardware moderno?


64

Ho alcuni vecchi programmi che ho estratto da un computer Windows dei primi anni '90 e ho provato a eseguirli su un computer relativamente moderno. È interessante notare che correvano a una velocità incredibilmente veloce - no, non i 60 fotogrammi al secondo di tipo veloce, piuttosto il tipo oh-mio-dio-il-personaggio-sta-camminando-alla-velocità-del-suono di veloce. Premevo un tasto freccia e lo sprite del personaggio si spostava sullo schermo molto più velocemente del normale. La progressione del tempo nel gioco stava accadendo molto più velocemente di quanto dovrebbe. Ci sono anche programmi creati per rallentare la tua CPU in modo che questi giochi siano effettivamente giocabili.

Ho sentito che questo è legato al gioco a seconda dei cicli della CPU o qualcosa del genere. Le mie domande sono:

  • Perché i giochi più vecchi lo fanno e come sono riusciti a cavarsela?
  • In che modo i giochi più recenti non lo fanno e funzionano indipendentemente dalla frequenza della CPU?

Questo era un po 'di tempo fa e non ricordo di aver fatto alcun trucco di compatibilità, ma non è questo il punto. Ci sono molte informazioni là fuori su come risolvere questo problema, ma non tanto sul perché esattamente corrono in quel modo, che è quello che sto chiedendo.
TreyK,

9
Ricordi il pulsante turbo sui PC più vecchi? : D
Viktor Mellgren,

1
Ah. Mi fa ricordare il ritardo di 1 secondo sull'ABC80 (PC svedese basato su z80 con Basic). FOR F IN 0 TO 1000; NEXT F;
Macke,

1
Solo per chiarire, "alcuni vecchi programmi che ho estratto da un computer Windows dei primi anni '90" sono quei programmi DOS su un computer Windows o programmi Windows in cui si verifica questo comportamento? Sono abituato a vederlo su DOS, ma non su Windows, IIRC.
Quel ragazzo brasiliano, il

Risposte:


52

Credo che presumessero che l'orologio di sistema funzionasse a una frequenza specifica e si legasse nei loro timer interni a quella frequenza. La maggior parte di questi giochi probabilmente funzionava su DOS ed era in modalità reale (con accesso completo e diretto all'hardware) e supponeva che avessi un sistema iirc 4,77 MHz per PC e qualunque processore standard quel modello funzionasse per altri sistemi come l'Amiga.

Hanno anche preso scorciatoie intelligenti basate su questi presupposti, incluso il risparmio di un po 'di risorse non scrivendo cicli di temporizzazione interni all'interno del programma. Hanno anche assorbito quanta più potenza possibile del processore, il che era una buona idea ai tempi dei chip lenti, spesso raffreddati passivamente!

Inizialmente un modo per aggirare la diversa velocità del processore era il buon vecchio pulsante Turbo (che rallentava il sistema). Le moderne applicazioni sono in modalità protetta e il sistema operativo tende a gestire le risorse - non consentirebbero a un'applicazione DOS (che è comunque in esecuzione in NTVDM su un sistema a 32 bit) di utilizzare tutto il processore in molti casi. In breve, i sistemi operativi sono diventati più intelligenti, così come le API.

Fortemente basato su questa guida su PC Oldskool in cui la logica e la memoria mi hanno deluso - è un'ottima lettura e probabilmente approfondisce il "perché".

Cose come CPUkiller consumano quante più risorse possibile per "rallentare" il sistema, il che è inefficiente. Faresti meglio a usare DOSBox per gestire la velocità di clock della tua applicazione.


14
Alcuni di questi giochi non
presumevano

2
Per informazioni re. "In che modo i giochi più recenti non fanno questo e funzionano indipendentemente dalla frequenza della CPU?" prova a cercare gamedev.stackexchange.com per qualcosa di simile game loop. Esistono sostanzialmente 2 metodi. 1) Corri il più velocemente possibile e scala le velocità di movimento ecc. In base alla velocità di esecuzione del gioco. 2) Se sei troppo veloce aspetta ( sleep()) fino a quando non siamo pronti per il prossimo 'tick'.
George Duckett,

24

Come aggiunta alla risposta di Journeyman Geek (perché la mia modifica è stata respinta) per le persone che sono interessate alla parte di programmazione / prospettiva degli sviluppatori:

Dal punto di vista dei programmatori, per coloro che sono interessati, i tempi di DOS erano tempi in cui ogni tick della CPU era importante, quindi i programmatori mantenevano il codice il più velocemente possibile.

Uno scenario tipico in cui qualsiasi programma verrà eseguito alla massima velocità della CPU è questo semplice (pseudo C):

int main()
{
    while(true)
    {

    }
}

questo funzionerà per sempre, ora, trasformiamo questo frammento di codice in un gioco pseudo-DOS:

int main()
{
    bool GameRunning = true;
    while(GameRunning)
    {
        ProcessUserMouseAndKeyboardInput();
        ProcessGamePhysics();
        DrawGameOnScreen();

        //close game
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

a meno che le DrawGameOnScreenfunzioni non utilizzino il doppio buffering / V-sync (che era un po 'costoso nei giorni in cui venivano creati i giochi DOS), il gioco funzionava alla massima velocità della CPU. Su un moderno i7 mobile questo dovrebbe funzionare da circa 1.000.000 a 5.000.000 di volte al secondo (a seconda della configurazione del laptop e dell'utilizzo attuale della CPU).

Ciò significherebbe che se potessi far funzionare qualsiasi gioco DOS sulla mia CPU moderna nelle mie finestre a 64 bit, potrei ottenere più di mille (1000!) FPS che è troppo veloce per essere giocato da qualsiasi essere umano se l'elaborazione della fisica "presume" che funzioni tra 50-60 fps.

Ciò che gli sviluppatori (possono) oggi possono fare è:

  1. Abilita V-Sync nel gioco (* non disponibile per le applicazioni con finestre ** [aka disponibile solo nelle app a schermo intero])
  2. Misura la differenza di tempo tra l'ultimo aggiornamento e aggiorna la fisica in base alla differenza di tempo che fa effettivamente funzionare il gioco / programma alla stessa velocità, indipendentemente dalla frequenza FPS
  3. Limitare il framerate a livello di codice

*** a seconda della configurazione della scheda grafica / driver / sistema operativo potrebbe essere possibile.

Per il punto 1 non c'è alcun esempio che mostrerò perché non è proprio una "programmazione". Sta solo usando le funzionalità grafiche.

Per quanto riguarda i punti 2 e 3, mostrerò i frammenti di codice e le spiegazioni corrispondenti:

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;
        }
    }
}

Qui puoi vedere l'input dell'utente e la fisica prende in considerazione la differenza di tempo, ma potresti comunque ottenere sullo schermo 1000+ FPS perché il loop sta funzionando il più velocemente possibile. Poiché il motore fisico sa quanto tempo è passato, non deve dipendere da "nessuna ipotesi" o "un certo framerate", quindi il gioco funzionerà alla stessa velocità su qualsiasi CPU.

3:

Ciò che gli sviluppatori possono fare per limitare il framerate, ad esempio, a 30 FPS non è in realtà nulla di più difficile, basta dare un'occhiata:

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;
        }
    }
}

Quello che succede qui è che il programma conta quanti millisecondi sono passati, se viene raggiunto un determinato importo (33 ms), quindi ridisegna la schermata di gioco, applicando effettivamente una frequenza dei fotogrammi vicino a ~ 30.

Inoltre, a seconda dello sviluppatore, può scegliere di limitare TUTTA l'elaborazione a 30 fps con il codice sopra leggermente modificato in questo modo:

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;
            }
        }
    }
}

Ci sono alcuni altri metodi e alcuni di loro odio davvero.

Ad esempio, usando sleep(<amount of milliseconds>).

So che questo è un metodo per limitare il framerate, ma cosa succede quando l'elaborazione del tuo gioco richiede 3 millisecondi o più? E poi esegui il sonno ...

questo comporterà un framerate inferiore rispetto a quello che solo sleep()dovrebbe causare.

Prendiamo ad esempio un tempo di sospensione di 16 ms. questo farebbe funzionare il programma a 60 hz. ora l'elaborazione dei dati, input, disegno e tutto il resto richiede 5 millisecondi. siamo a 21 millisecondi per un loop ora che risulta in poco meno di 50 hz, mentre potresti facilmente essere ancora a 60 hz ma a causa del sonno è impossibile.

Una soluzione sarebbe quella di rendere un sonno adattivo sotto forma di misurazione del tempo di elaborazione e deduzione del tempo di elaborazione dal sonno desiderato con conseguente correzione del nostro "bug":

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

Una delle cause principali è l'utilizzo di un loop di ritardo che viene calibrato all'avvio del programma. Contano quante volte viene eseguito un loop in un periodo di tempo noto e lo dividono per generare ritardi minori. Questo può quindi essere usato per implementare una funzione sleep () per accelerare l'esecuzione del gioco. I problemi sorgono quando questo contatore viene massimizzato a causa dei processori che sono molto più veloci sul loop che il piccolo ritardo finisce per essere troppo piccolo. Inoltre, i moderni processori cambiano la velocità in base al carico, a volte anche su base core, il che rende il ritardo ancora maggiore.

Per i giochi per PC davvero vecchi, hanno funzionato il più velocemente possibile, indipendentemente dal tentativo di accelerare il gioco. Questo era più il caso nei giorni IBM PC XT, tuttavia, in cui esisteva un pulsante turbo che rallentava il sistema per abbinare un processore da 4,77 MHz per questo motivo.

I giochi e le librerie moderni come DirectX hanno accesso a timer ad alta precessione, quindi non è necessario utilizzare loop di ritardo basati su codice calibrato.


4

Tutti i primi PC funzionavano alla stessa velocità all'inizio, quindi non era necessario tenere conto della differenza di velocità.

Inoltre, molti giochi all'inizio avevano un carico di CPU piuttosto fisso, quindi era improbabile che alcuni frame funzionassero più velocemente di altri.

Al giorno d'oggi, con i tuoi bambini e i tuoi fantastici sparatutto in prima persona, puoi guardare il pavimento un secondo, e il Grand Canyon il successivo, la variazione di carico avviene più spesso. :)

(E poche console hardware sono abbastanza veloci da far girare giochi a 60 fps costantemente. Ciò è dovuto principalmente al fatto che gli sviluppatori di console optano per 30 Hz e rendono i pixel due volte più brillanti ...)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.