Bazı eski oyunlar neden modern donanımda çok hızlı çalışıyor?


64

90'lı yılların başlarından kalma bir Windows bilgisayarını çıkardığım ve nispeten modern bir bilgisayarda çalıştırmaya çalıştığım birkaç eski programım var. İlginçtir ki, cayır cayır yanan bir hızla koşuyorlardı - hayır, saniye başına 60 kare hızında değil, oh-my-tanrı-karakter-in-the-the-go-the-go-st hızlı. Bir ok tuşuna basardım ve karakterin sprite normalden çok daha hızlı ekran boyunca zip olurdu. Oyunda zaman ilerlemesi olması gerekenden çok daha hızlı gerçekleşiyordu. İşlemcinizi yavaşlatmak için yapılan programlar bile vardır, böylece bu oyunlar gerçekten oynanabilir hale gelir.

Bunun CPU döngüsüne veya bunun gibi bir şeye bağlı olarak oyunla ilgili olduğunu duydum. Benim sorularım:

  • Neden eski oyunlar bunu yapıyor ve onunla nasıl kaçtılar?
  • Nasıl yeni oyunlar yapmak değil bunu yapmak ve bağımsız işlemci frekansının çalıştırmak?

Bu bir süre önceydi ve herhangi bir uyumluluk hilesi yaptığını hatırlamıyorum, ama konunun dışında. Bunun nasıl düzeltileceğine dair birçok bilgi var, ama neden tam olarak bu şekilde çalıştıkları hakkında değil , soruyorum.
TreyK

9
Eski bilgisayarlarda turbo düğmesini hatırlıyor musunuz? : D
Viktor Mellgren

1
Ah. ABC80'deki 1 saniyelik gecikmeyi hatırlatmama neden oluyor (İsveçli Basic z80 tabanlı PC). FOR F IN 0 TO 1000; NEXT F;
Macke

1
Sadece açıklamak gerekirse, "eski 90'ların başında bir Windows bilgisayarını çıkardığım birkaç eski program" bir Windows makinesindeki DOS programları mı, yoksa bu davranışın gerçekleştiği Windows programları mı? DOS üzerinde görmeye alışkınım, ama pencereleri değil, IIRC'yi.
Brezilyalı Adam

Yanıtlar:


52

Sistem saatinin belirli bir hızda çalışacağını varsaydıklarına ve iç zamanlayıcılarını bu saat hızına bağlı olduklarına inanıyorum. Bu oyunların çoğu muhtemelen DOS'ta çalışıyordu ve gerçek modda (tam, doğrudan donanım erişimi var) ve PC'ler için iirc 4.77 MHz sistemi ve bu modelin Amiga gibi diğer sistemler için çalıştırdığı standart işlemci ne olursa olsun çalıştırdığınızı varsayıyorlardı .

Ayrıca, programın içine dahili zamanlama döngüleri yazmadan küçük bir miktar kaynak tasarrufu da dahil olmak üzere bu varsayımlara dayanarak akıllıca kısayollar aldılar. Ayrıca, yavaş, çoğu zaman pasif olarak soğutulmuş cips günlerinde iyi bir fikirdi.

Başlangıçta, farklı işlemci hızlarında dolaşmanın bir yolu eski (sisteminizi yavaşlatan) eski Turbo butonuydu. Modern uygulamalar korumalı moddadır ve işletim sistemi kaynakları yönetme eğilimindedir - bir DOS uygulamasının (NTVDM'de 32-bit bir sistemde çalışmakta olan) çoğu durumda tüm işlemciyi kullanmasına izin vermez . Kısacası, işletim sistemleri API'ler gibi daha akıllı hale geldi.

Oldukça mantık ve hafızanın başarısız olduğu Oldskool PC'de bu rehbere dayanıyordu - bu harika bir okuma ve muhtemelen "neden" e daha derinlemesine gidiyor.

CPUkiller gibi şeyler , sisteminizi "yavaşlatmak" için mümkün olduğunca fazla kaynak kullanır, bu da verimsizdir. Uygulamanızın gördüğü saat hızını yönetmek için DOSBox kullanarak daha iyi olursunuz.


14
Bu oyunların bazıları hiçbir şey varsaymıyorlardı, olabildiğince hızlı koştular, bu CPU'larda 'oynanabilir' ;-)
Jan Doggen

2
Bilgi için re. “Yeni oyunlar bunu nasıl yapmaz ve CPU frekansından bağımsız olarak nasıl çalışır?” gamedev.stackexchange.com gibi bir şey için arama yapmayı deneyin game loop. Temelde 2 yöntem var. 1) Mümkün olduğunca hızlı koşun ve oyunun hızına bağlı olarak hareket hızlarını ölçün. 2) Çok hızlıysanız sleep(), bir sonraki 'kene' hazır olana kadar ( ) bekleyin .
George Duckett,

24

Journeyman Geek'in (kodlamam reddedildi çünkü) kodlama bölümü / geliştirici bakış açısıyla ilgilenen insanlar için verdiği cevaba ek olarak:

Programcıların bakış açısından, ilgilenenler için, DOS zamanları, her CPU onayının önemli olduğu zamanlardı, böylece programcılar kodu olabildiğince hızlı tuttu.

Herhangi bir programın maksimum CPU hızında çalışacağı tipik bir senaryo bu basittir (sözde C):

int main()
{
    while(true)
    {

    }
}

Bu sonsuza kadar sürecek, şimdi, bu kod parçacığını sözde DOS oyununa dönüştürelim:

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

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

Eğer DrawGameOnScreenfonksiyonlar çift tamponlama / V-sync kullanmıyorsa (ki, DOS oyunlarının yapıldığı günlerde biraz pahalıydı), oyun maksimum CPU hızında çalışacaktır. Modern bir günde mobil i7 bu (saniyede yaklaşık 1.000.000 ila 5.000.000 kez çalışacaktı (dizüstü bilgisayar yapılandırmasına ve mevcut cpu kullanımına bağlı olarak).

Bu, 64bit pencerelerdeki modern CPU'mda çalışan herhangi bir DOS oyununu çalıştırabilirsem, fizik işlemeyi "varsa" yürütürse, herhangi bir insanın oynaması için çok hızlı olan binden fazla (1000!) FPS elde edebileceğim anlamına gelirdi. 50-60 fps arasında.

Günümüzdeki geliştiricilerin (yapabilecekleri) yapabilecekleri:

  1. Oyunda V-Sync'i etkinleştirin (* pencereli uygulamalar için mevcut değildir ** [aka sadece tam ekran uygulamalarda kullanılabilir])
  2. Son güncelleme arasındaki zaman farkını ölçün ve FPS oranına bakılmaksızın oyunu / programı etkin bir şekilde aynı hızda çalıştırmasını sağlayan zaman farkına göre fiziği güncelleyin
  3. Kare hızını programsal olarak sınırlayın

*** grafik kartına / sürücüye / os konfigürasyonuna bağlı olarak mümkün olabilir .

Nokta 1 için göstereceğim hiçbir örnek yok çünkü gerçekten herhangi bir "programlama" değil. Sadece grafik özelliklerini kullanıyor.

2. ve 3. noktalara gelince, karşılık gelen kod parçacıklarını ve açıklamalarını göstereceğim:

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

Burada kullanıcı girişi ve fiziğin zaman farkını hesaba kattığını görebilirsiniz, ancak döngü mümkün olduğu kadar hızlı çalıştığı için hala ekranda 1000+ FPS elde edebilirsiniz. Fizik motoru ne kadar zaman geçtiğini bildiğinden, "varsayım yok" ya da "belirli bir kare hızına" bağlı olması gerekmez, böylece oyun herhangi bir cpu üzerinde aynı hızda çalışır.

3:

Geliştiricilerin kare hızını sınırlamak için yapabilecekleri, örneğin, 30 FPS aslında daha zor bir şey değil, sadece bir göz atın:

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

Burada gerçekleşen, programın kaç milisaniyenin geçtiğini saymasıdır, eğer belirli bir miktara ulaşıldığında (33 msn) oyun ekranını yeniden çizer ve etkin bir şekilde ~ 30 civarında bir kare hızı uygular.

Ayrıca, geliştiriciye bağlı olarak, TÜM işlemeyi 30 fps ile sınırlamayı seçebilir, yukarıdaki kod bu şekilde hafifçe değiştirilir:

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

Birkaç başka yöntem var ve bazıları gerçekten nefret ediyorum.

Örneğin, kullanarak sleep(<amount of milliseconds>).

Bunun kare hızını sınırlamanın bir yöntemi olduğunu biliyorum, ancak oyun işlemleriniz 3 milisaniye veya daha fazla sürdüğünde ne olur? Ve sonra sen uykuyu çalıştır.

bu, sadece sleep()neden olması gerekenden daha düşük bir kare hızına neden olacaktır.

Örneğin, 16 ms'lik bir uyku süresi alalım. Bu, programı 60 hz'de çalıştırır. Şimdi verilerin işlenmesi, girdi, çizim ve tüm şeyler 5 milisaniye sürer. Şimdi bir döngü için 21 milisaniyeyiz, bu da 50 hz'den biraz daha az sonuç verirken, kolayca 60 hz'de olabilirsiniz ancak uykudan dolayı imkansız.

Çözümlerden biri , işlem zamanını ölçmek ve işlem zamanını istenen uykusundan düşmek şeklinde uyarlanabilir bir uyku yapmak olacaktır;

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

Bunun ana nedeni, program başladığında kalibre edilmiş bir gecikme döngüsü kullanmaktır. Bir döngünün bilinen bir sürede kaç kez çalıştığını sayar ve daha küçük gecikmeler oluşturmak için bölün. Bu daha sonra oyunun yürütülmesini hızlandırmak için sleep () işlevini uygulamak için kullanılabilir. İşlemciler döngü üzerinde çok daha hızlı olmalarından ve bu küçük gecikmenin çok küçük olmasından dolayı daha hızlı olması nedeniyle bu sayaç maksimize edildiğinde sorunlar ortaya çıkar. Ek olarak, modern işlemciler yükü baz alarak hızı değiştirir, bazen çekirdeği baz alarak bile gecikmeyi daha da artırır.

Gerçekten eski PC oyunları için, oyunu hızlandırmaya çalışmaksızın, ellerinden geldiğince hızlı koştular. IBM PC XT günlerinde bu durum daha da fazlaydı, ancak sistemi bu sebeple 4.77mhz işlemci ile eşleştirmek için yavaşlatan bir turbo butonu vardı.

DirectX gibi modern oyunlar ve kütüphaneler yüksek öncelikli zamanlayıcılara erişebilir, bu nedenle kalibre edilmiş kod tabanlı gecikme döngüleri kullanmanıza gerek yoktur.


4

Tüm ilk PC'ler başlangıçta aynı hızda çalıştığından, hızdaki farklılığı hesaba katmaya gerek yoktu.

Ayrıca, başlangıçtaki birçok oyun oldukça sabit bir cpu yüküne sahipti, bu nedenle bazı karelerin diğerlerinden daha hızlı çalışması pek mümkün değildi.

Günümüzde, yer çocukları ve yer fantezi FPS atıcılarıyla, bir saniye yere bakabilir ve bir sonraki büyük kanyonda yük değişimi daha sık gerçekleşir. :)

(Ve birkaç donanım konsolu, sürekli olarak 60 fps hızında oyun çalıştırmak için yeterince hızlı. Bunun nedeni, konsol geliştiricilerin 30 Hz'yi seçmesi ve pikselleri iki kat daha parlak hale getirmesi ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.