ทำไมเกมเก่าบางเกมถึงทำงานเร็วเกินไปกับฮาร์ดแวร์ที่ทันสมัย?


64

ฉันมีโปรแกรมเก่าสองสามตัวที่ดึงออกมาจากคอมพิวเตอร์ Windows ยุคต้นยุค 90 และพยายามที่จะรันมันบนคอมพิวเตอร์ที่ค่อนข้างทันสมัย น่าสนใจพอพวกเขาวิ่งด้วยความเร็วที่รวดเร็ว - ไม่ใช่ไม่ใช่ 60 เฟรมต่อวินาทีอย่างรวดเร็วแทนที่จะเป็นแบบตัวละครที่เดินด้วยความเร็ว รวดเร็ว ฉันจะกดปุ่มลูกศรและสไปรต์ของตัวละครจะซิปผ่านหน้าจอเร็วกว่าปกติมาก ความก้าวหน้าของเวลาในเกมกำลังเกิดขึ้นเร็วกว่าที่ควร มีแม้กระทั่งโปรแกรมที่ทำให้ซีพียูของคุณช้าลงเพื่อให้เกมเหล่านี้เล่นได้จริง

ฉันได้ยินมาว่าเรื่องนี้เกี่ยวข้องกับเกมขึ้นอยู่กับรอบการทำงานของ CPU หรืออะไรทำนองนั้น คำถามของฉันคือ:

  • ทำไมเกมรุ่นเก่าถึงทำเช่นนี้และพวกเขาหนีไปได้อย่างไร
  • เกมที่ใหม่กว่าจะไม่ทำเช่นนี้และทำงานโดยไม่ขึ้นกับความถี่ของ CPU อย่างไร

นี่เป็นเวลานานแล้วและฉันจำไม่ได้เลยว่าการใช้กลอุบายที่เข้ากันได้ แต่นั่นอยู่เหนือประเด็น มีข้อมูลมากมายเกี่ยวกับวิธีการแก้ไขปัญหานี้ แต่ไม่มากนักว่าทำไมพวกเขาจึงเรียกใช้วิธีนั้นซึ่งเป็นสิ่งที่ฉันขอ
TreyK

9
จำปุ่มเทอร์โบของพีซีรุ่นเก่าได้หรือไม่ : D
Viktor Mellgren

1
อา ทำให้ฉันจำการหน่วงเวลา 1 วินาทีบน ABC80 (พีซีที่ใช้ z80 แบบสวีเดนพร้อมพื้นฐาน) FOR F IN 0 TO 1000; NEXT F;
Macke

1
เพียงเพื่ออธิบาย "โปรแกรมเก่าสองสามตัวที่ฉันดึงออกจากคอมพิวเตอร์ Windows ยุคต้นยุค 90" เป็นโปรแกรม DOS บนเครื่อง Windows หรือโปรแกรม Windows ที่มีพฤติกรรมนี้เกิดขึ้นหรือไม่ ฉันเคยเห็นมันใน DOS แต่ไม่ใช่ windows, IIRC
Guy บราซิลนั่น

คำตอบ:


52

ฉันเชื่อว่าพวกเขาสันนิษฐานว่านาฬิการะบบจะทำงานในอัตราที่กำหนดและเชื่อมโยงกับตัวจับเวลาภายในกับอัตรานาฬิกาดังกล่าว เกมเหล่านี้ส่วนใหญ่อาจทำงานบน DOS และเป็นโหมดจริง (พร้อมการเข้าถึงฮาร์ดแวร์โดยตรงโดยตรง) และสมมติว่าคุณใช้ระบบiirc 4.77 MHz สำหรับพีซีและโปรเซสเซอร์มาตรฐานใดก็ตามที่ใช้กับระบบอื่นเช่น Amiga

พวกเขายังใช้ทางลัดที่ฉลาดขึ้นอยู่กับสมมติฐานเหล่านั้นรวมถึงการประหยัดทรัพยากรเล็กน้อยโดยไม่ต้องเขียนการวนรอบภายในภายในโปรแกรม พวกเขายังใช้พลังงานตัวประมวลผลมากเท่าที่จะทำได้ซึ่งเป็นความคิดที่ดีในช่วงเวลาที่ช้าชิปมักจะเย็นตัวลงอย่างอดทน!

เริ่มแรกวิธีหนึ่งในการรับความเร็วโปรเซสเซอร์ที่แตกต่างกันคือปุ่ม Turbo ตัวเก่า(ซึ่งทำให้ระบบของคุณช้าลง) แอปพลิเคชั่นที่ทันสมัยอยู่ในโหมดที่ได้รับการป้องกันและระบบปฏิบัติการมีแนวโน้มที่จะจัดการทรัพยากร - พวกเขาจะไม่อนุญาตให้แอปพลิเคชัน DOS (ซึ่งทำงานใน NTVDM บนระบบ 32 บิตอยู่แล้ว) เพื่อใช้โปรเซสเซอร์ทั้งหมดในหลายกรณี ในระยะสั้นระบบปฏิบัติการได้ฉลาดขึ้นเช่นเดียวกับ API

อย่างหนักตามคู่มือนี้บนพีซี Oldskoolที่ตรรกะและหน่วยความจำฉันไม่ได้ - มันเป็นการอ่านที่ยอดเยี่ยมและอาจเจาะลึกลงไปใน "ทำไม"

สิ่งต่างๆเช่นCPUkillerใช้ทรัพยากรให้มากที่สุดเท่าที่จะทำได้เพื่อ "ทำให้ช้าลง" ในระบบของคุณซึ่งไม่มีประสิทธิภาพ คุณควรใช้DOSBoxเพื่อจัดการความเร็วสัญญาณนาฬิกาที่แอปพลิเคชันของคุณเห็น


14
เกมเหล่านี้บางเกมไม่ได้คิดอะไรเลยพวกเขาวิ่งเร็วเท่าที่จะทำได้ซึ่งสามารถ 'เล่นได้' บนซีพียูเหล่านั้น ;-)
Jan Doggen

2
สำหรับข้อมูลเพิ่มเติม "เกมที่ใหม่กว่าไม่ทำอย่างนี้และทำงานโดยไม่ขึ้นกับความถี่ของ CPU" ลองค้นหา gamedev.stackexchange.com game loopบางอย่างเช่น โดยทั่วไปมี 2 วิธี 1) วิ่งเร็วที่สุดเท่าที่จะเป็นไปได้และปรับขนาดความเร็วในการเคลื่อนที่และอื่น ๆ ตามความเร็วของเกม 2) หากคุณรอเร็วเกินไป ( sleep()) จนกว่าเราจะพร้อม 'ติ๊ก' ครั้งต่อไป
George Duckett

24

นอกเหนือจากคำตอบของ Journeyman Geek (เพราะการแก้ไขของฉันถูกปฏิเสธ) สำหรับผู้ที่สนใจในส่วนของการเข้ารหัส / มุมมองของนักพัฒนา:

จากมุมมองของโปรแกรมเมอร์สำหรับผู้ที่สนใจเวลา DOS เป็นช่วงเวลาที่ขีดของ CPU ทุกตัวมีความสำคัญดังนั้นโปรแกรมเมอร์จึงเก็บรหัสไว้อย่างรวดเร็วที่สุด

สถานการณ์โดยทั่วไปที่โปรแกรมใด ๆ จะทำงานด้วยความเร็วซีพียูสูงสุดนั้นง่าย (หลอก 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-sync (ซึ่งค่อนข้างแพงในสมัยที่เกม DOS ถูกสร้างขึ้น) เกมจะทำงานด้วยความเร็ว CPU สูงสุด ในวันที่ทันสมัย ​​i7 มือถือนี้จะทำงานที่ประมาณ 1,000,000 ถึง 5,000,000 ครั้งต่อวินาที (ขึ้นอยู่กับการกำหนดค่าแล็ปท็อปและการใช้ซีพียูปัจจุบัน)

นี่หมายความว่าถ้าฉันสามารถทำให้เกม DOS ใดทำงานบน CPU ที่ทันสมัยของฉันใน windows 64 บิตของฉันฉันจะได้รับมากกว่าหนึ่งพัน (1,000!) FPS ซึ่งเร็วเกินไปสำหรับมนุษย์ที่จะเล่นถ้าการประมวลผลทางฟิสิกส์ "ถือว่า" มันทำงาน ระหว่าง 50-60 fps

สิ่งที่ผู้พัฒนาในปัจจุบันสามารถทำได้คือ:

  1. เปิดใช้งาน V-Sync ในเกม (* ไม่สามารถใช้ได้กับแอปพลิเคชันที่มีหน้าต่าง ** [aka มีเฉพาะในแอปแบบเต็มหน้าจอ])
  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;
        }
    }
}

ที่นี่คุณสามารถเห็นการป้อนข้อมูลของผู้ใช้และฟิสิกส์คำนึงถึงความแตกต่างของเวลา แต่คุณยังสามารถรับหน้าจอมากกว่า 1,000 FPS บนหน้าจอได้เนื่องจากการวนซ้ำทำงานเร็วที่สุด เนื่องจากเอ็นจิ้นฟิสิกส์รู้ว่าเวลาผ่านไปนานเท่าไรจึงไม่จำเป็นต้องพึ่งพา "ไม่มีข้อสันนิษฐาน" หรือ "อัตราเฟรมที่แน่นอน" ดังนั้นเกมจะทำงานด้วยความเร็วเท่ากันใน cpu ใด ๆ

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 fps ด้วยรหัสข้างต้นแก้ไขเล็กน้อยในเรื่องนี้:

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.77mhz ด้วยเหตุผลนี้

เกมและไลบรารี่ที่ทันสมัยเช่น DirectX สามารถเข้าถึงตัวจับเวลา precession สูงได้ดังนั้นจึงไม่จำเป็นต้องใช้ลูปการหน่วงเวลาตามรหัสที่ปรับเทียบ


4

พีซีเครื่องแรกทุกเครื่องวิ่งด้วยความเร็วเท่ากันในตอนเริ่มต้นดังนั้นจึงไม่จำเป็นต้องคำนึงถึงความเร็วที่ต่างกัน

หลายเกมในตอนแรกมีการโหลดซีพียูค่อนข้างคงที่ดังนั้นจึงไม่น่าที่เฟรมบางเฟรมจะทำงานเร็วกว่าเกมอื่น

ทุกวันนี้ด้วยเด็ก ๆ และนักแม่นปืน FPS แฟนซีคุณสามารถมองไปที่พื้นหนึ่งวินาทีและที่แกรนด์แคนยอนในครั้งต่อไปการแปรผันของการโหลดจะเกิดขึ้นบ่อยขึ้น :)

(และคอนโซลฮาร์ดแวร์บางตัวนั้นเร็วพอที่จะรันเกมที่ 60 fps อย่างต่อเนื่องซึ่งส่วนใหญ่เป็นเพราะความจริงที่ว่าผู้พัฒนาคอนโซลเลือกใช้ 30 Hz และทำให้พิกเซลเป็นเงางามเป็นสองเท่า ... )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.