Tại sao một số trò chơi cũ chạy quá nhanh trên phần cứng hiện đại?


64

Tôi đã có một vài chương trình cũ Tôi đã tạo ra một máy tính Windows đầu thập niên 90 và cố gắng chạy chúng trên một máy tính tương đối hiện đại. Điều thú vị là, họ chạy với tốc độ nhanh - không, không phải là 60 khung hình mỗi giây, mà là kiểu âm thanh ồ ạt của nhân vật đang đi bộ ở tốc độ âm thanh Nhanh. Tôi sẽ nhấn một phím mũi tên và sprite của nhân vật sẽ nén trên màn hình nhanh hơn nhiều so với bình thường. Tiến trình thời gian trong trò chơi đã diễn ra nhanh hơn nhiều so với bình thường. Thậm chí có những chương trình được thực hiện để làm chậm CPU của bạn để những trò chơi này thực sự có thể chơi được.

Tôi đã nghe nói rằng điều này có liên quan đến trò chơi tùy thuộc vào chu kỳ CPU, hoặc một cái gì đó tương tự. Câu hỏi của tôi là:

  • Tại sao các trò chơi cũ làm điều này, và làm thế nào họ thoát khỏi nó?
  • Làm thế nào để các trò chơi mới hơn không làm điều này và chạy độc lập với tần số CPU?

Đây là một thời gian trước đây và tôi không nhớ là đã thực hiện bất kỳ mánh khóe tương thích nào, nhưng đó là điểm chính. Có rất nhiều thông tin về cách khắc phục điều này, nhưng không nhiều về lý do tại sao chính xác họ chạy theo cách đó, đó là những gì tôi đang hỏi.
TreyK

9
Bạn có nhớ nút turbo trên PC cũ không? : D
Viktor Mellgren

1
Ah. Làm cho tôi nhớ độ trễ 1 giây trên ABC80 (PC dựa trên z80 của Thụy Điển với Basic). FOR F IN 0 TO 1000; NEXT F;
Macke

1
Chỉ cần làm rõ, "một vài chương trình cũ mà tôi đã sử dụng máy tính Windows đầu thập niên 90" là những chương trình DOS trên máy Windows hay chương trình Windows nơi hành vi này xảy ra? Tôi đã từng thấy nó trên DOS, nhưng không phải là windows, IIRC.
chàng người Brazil

Câu trả lời:


52

Tôi tin rằng họ cho rằng đồng hồ hệ thống sẽ chạy ở một tốc độ cụ thể và gắn với bộ tính giờ bên trong của họ với tốc độ đồng hồ đó. Hầu hết các trò chơi này có thể chạy trên DOS và ở chế độ thực (với quyền truy cập hoàn toàn, phần cứng trực tiếp) và giả sử bạn đang chạy hệ thống iirc 4,77 MHz cho PC và bất kỳ bộ xử lý tiêu chuẩn nào chạy cho các hệ thống khác như Amiga.

Họ cũng đã sử dụng các phím tắt thông minh dựa trên các giả định đó bao gồm tiết kiệm một chút tài nguyên bằng cách không viết các vòng lặp thời gian nội bộ bên trong chương trình. Họ cũng chiếm nhiều sức mạnh bộ xử lý nhất có thể - đó là một ý tưởng hay trong thời đại chip chậm, thường được làm mát thụ động!

Ban đầu, một cách để vượt qua tốc độ bộ xử lý khác nhau là nút Turbo cũ tốt (làm chậm hệ thống của bạn). Các ứng dụng hiện đại ở chế độ được bảo vệ và HĐH có xu hướng quản lý tài nguyên - chúng sẽ không cho phép ứng dụng DOS (dù đang chạy trong NTVDM trên hệ thống 32 bit) trong nhiều trường hợp sử dụng hết bộ xử lý. Nói tóm lại, các hệ điều hành đã trở nên thông minh hơn, cũng như có API.

Dựa vào hướng dẫn này trên PC Oldskool , nơi logic và bộ nhớ đã làm tôi thất bại - đó là một bài đọc tuyệt vời và có lẽ đi sâu hơn vào "tại sao".

Những thứ như CPUkiller sử dụng càng nhiều tài nguyên càng tốt để "làm chậm" hệ thống của bạn, điều này không hiệu quả. Bạn nên sử dụng DOSBox để quản lý tốc độ đồng hồ mà ứng dụng của bạn nhìn thấy.


14
Một số trò chơi này thậm chí không giả định bất cứ điều gì, chúng chạy nhanh hết mức có thể, có thể 'chơi được' trên các CPU đó ;-)
Jan Doggen

2
Để biết thông tin lại. "Làm thế nào để các trò chơi mới hơn không làm điều này và chạy độc lập với tần số CPU?" hãy thử tìm kiếm gamedev.stackexchange.com cho một cái gì đó như game loop. Về cơ bản có 2 phương pháp. 1) Chạy nhanh nhất có thể và tăng tốc độ di chuyển, vv dựa trên tốc độ trò chơi chạy. 2) Nếu bạn chờ quá nhanh ( sleep()) cho đến khi chúng tôi sẵn sàng cho 'đánh dấu' tiếp theo.
George Duckett

24

Là một bổ sung cho câu trả lời của Journeyman Geek (vì bản chỉnh sửa của tôi đã bị từ chối) cho những người quan tâm đến quan điểm của nhà phát triển / phần mã hóa:

Từ góc độ lập trình viên, đối với những người quan tâm, thời gian DOS là thời điểm mà mọi dấu kiểm CPU đều quan trọng để các lập trình viên giữ mã nhanh nhất có thể.

Một kịch bản điển hình trong đó bất kỳ chương trình nào sẽ chạy ở tốc độ CPU tối đa là đơn giản này (giả C):

int main()
{
    while(true)
    {

    }
}

Điều này sẽ chạy mãi mãi, bây giờ, hãy biến đoạn mã này thành trò chơi giả DOS:

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

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

trừ khi các DrawGameOnScreenchức năng sử dụng bộ đệm đôi / đồng bộ hóa V (vốn khá đắt tiền vào thời mà trò chơi DOS được tạo ra), trò chơi sẽ chạy ở tốc độ CPU tối đa. Trên một thiết bị di động i7 hiện đại, nó sẽ chạy ở mức khoảng 1.000.000 đến 5.000.000 lần mỗi giây (tùy thuộc vào cấu hình máy tính xách tay và việc sử dụng cpu hiện tại).

Điều này có nghĩa là nếu tôi có thể khiến bất kỳ trò chơi DOS nào hoạt động trên CPU hiện đại của mình trong các cửa sổ 64 bit của mình, tôi có thể nhận được hơn một nghìn (1000!) FPS, quá nhanh để mọi người chơi nếu xử lý vật lý "giả định" nó chạy trong khoảng 50-60 khung hình / giây.

Những gì các nhà phát triển ngày nay (có thể) làm là:

  1. Bật V-Sync trong trò chơi (* không khả dụng cho các ứng dụng có cửa sổ ** [aka chỉ khả dụng trong các ứng dụng toàn màn hình])
  2. Đo chênh lệch thời gian giữa lần cập nhật gần nhất và cập nhật vật lý theo chênh lệch thời gian giúp trò chơi / chương trình chạy hiệu quả ở cùng tốc độ bất kể tốc độ FPS
  3. Hạn chế tốc độ khung hình

*** tùy thuộc vào cấu hình card đồ họa / trình điều khiển / os thể có thể.

Đối với điểm 1 không có ví dụ nào tôi sẽ trình bày vì nó không thực sự là bất kỳ "lập trình" nào. Nó chỉ sử dụng các tính năng đồ họa.

Đối với điểm 2 và 3, tôi sẽ hiển thị các đoạn mã và giải thích tương ứng:

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

Ở đây bạn có thể thấy đầu vào của người dùng và vật lý có sự khác biệt về thời gian, nhưng bạn vẫn có thể nhận được hơn 1000 FPS trên màn hình vì vòng lặp đang chạy nhanh nhất có thể. Bởi vì công cụ vật lý biết thời gian trôi qua bao lâu, nên nó không phải phụ thuộc vào "không giả định" hay "tốc độ khung hình nhất định" nên trò chơi sẽ hoạt động với cùng tốc độ trên bất kỳ cpu nào.

3:

Những gì các nhà phát triển có thể làm để hạn chế tốc độ khung hình, ví dụ, 30 FPS thực sự không có gì khó hơn, chỉ cần xem:

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

Điều xảy ra ở đây là chương trình đếm được bao nhiêu mili giây đã trôi qua, nếu đạt được một lượng nhất định (33 ms) thì nó sẽ vẽ lại màn hình trò chơi, áp dụng hiệu quả tốc độ khung hình gần ~ 30.

Ngoài ra, tùy thuộc vào nhà phát triển, anh ấy / cô ấy có thể chọn giới hạn TẤT CẢ xử lý ở mức 30 khung hình / giây với mã trên được sửa đổi đôi chút về điều này:

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

Có một vài phương pháp khác, và một số trong số đó tôi thực sự ghét.

Ví dụ, sử dụng sleep(<amount of milliseconds>).

Tôi biết đây là một phương pháp để hạn chế tốc độ khung hình, nhưng điều gì xảy ra khi quá trình xử lý trò chơi của bạn mất 3 mili giây trở lên? Và sau đó bạn thực hiện giấc ngủ ...

điều này sẽ dẫn đến tốc độ khung hình thấp hơn so với cái mà chỉ sleep()nên gây ra.

Ví dụ, hãy dành thời gian ngủ là 16 ms. điều này sẽ làm cho chương trình chạy ở 60 hz. bây giờ việc xử lý dữ liệu, đầu vào, bản vẽ và tất cả các công cụ mất 5 triệu giây. bây giờ chúng tôi đang ở mức 21 triệu giây cho một vòng lặp, kết quả là hơi thấp hơn 50 hz, trong khi bạn vẫn có thể dễ dàng ở mức 60 hz nhưng vì giấc ngủ không thể.

Một giải pháp sẽ là tạo một giấc ngủ thích ứng dưới dạng đo thời gian xử lý và trừ thời gian xử lý khỏi giấc ngủ mong muốn dẫn đến việc sửa "lỗi" của chúng tôi:

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

Một nguyên nhân chính là sử dụng vòng lặp trễ được hiệu chỉnh khi chương trình bắt đầu. Họ đếm số lần một vòng lặp thực hiện trong một khoảng thời gian đã biết và chia nó để tạo ra độ trễ nhỏ hơn. Điều này sau đó có thể được sử dụng để thực hiện chức năng ngủ () để tăng tốc độ thực thi của trò chơi. Các vấn đề xảy ra khi bộ đếm này được tăng tối đa do bộ xử lý nhanh hơn rất nhiều trên vòng lặp đến nỗi độ trễ nhỏ kết thúc quá nhỏ. Ngoài ra, các bộ xử lý hiện đại thay đổi tốc độ dựa trên tải, đôi khi thậm chí dựa trên cơ sở mỗi lõi, điều này làm cho độ trễ giảm đi nhiều hơn.

Đối với các trò chơi PC thực sự cũ, họ chỉ chạy nhanh nhất có thể mà không liên quan đến việc cố gắng tăng tốc trò chơi. Đây là trường hợp nhiều hơn trong những ngày IBM PC XT, tuy nhiên đã tồn tại một nút turbo làm chậm hệ thống để phù hợp với bộ xử lý 4,77 MHz vì lý do này.

Các trò chơi và thư viện hiện đại như DirectX có quyền truy cập vào bộ tính giờ trước cao, do đó không cần sử dụng các vòng lặp trễ dựa trên mã được hiệu chỉnh.


4

Tất cả các PC đầu tiên đều chạy ở cùng tốc độ lúc ban đầu, do đó không cần phải tính đến sự khác biệt về tốc độ.

Ngoài ra, nhiều trò chơi lúc đầu có tải cpu khá cố định, do đó, một số khung hình sẽ chạy nhanh hơn các trò chơi khác.

Ngày nay, với những đứa trẻ và những game bắn súng FPS lạ mắt hơn, bạn có thể nhìn xuống sàn một giây và tại hẻm núi lớn, sự thay đổi tải tiếp theo xảy ra thường xuyên hơn. :)

(Và, một số máy chơi game phần cứng đủ nhanh để chạy các trò chơi ở tốc độ 60 khung hình / giây liên tục. Điều này chủ yếu là do các nhà phát triển giao diện điều khiển chọn 30 Hz và làm cho các pixel sáng gấp đôi ...)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.