1. 程式人生 > 實用技巧 >HTML5 GAME TUTORIAL(三): Create a proper game loop(譯)

HTML5 GAME TUTORIAL(三): Create a proper game loop(譯)

原文地址:Create a proper game loop

在JavaScript中建立適當的遊戲迴圈,並瞭解幀頻。測量並顯示fps,以檢視迴圈效果。請求動畫幀,並在本HTML5遊戲教程結束之前執行自己的迴圈。

為什麼你需要一個遊戲迴圈

在上一教程中,您已經建立了一個在HTML5畫布上繪製矩形的應用程式。 一切都非常好,但是繪製操作僅執行一次。如果要使其看起來好像矩形正在移動,並最終建立一個實際的遊戲,則需要繪製更多內容。多很多。

為此,您需要一個迴圈來重複執行遊戲邏輯。在遊戲世界中,此迴圈稱為遊戲迴圈。這將是您遊戲的核心,並觸發繪圖操作。您將不再繪製單個靜態影象,但是許多影象將相互繪製,從而產生運動。

這是一個帶有移動圓的示例。它連續顯示五個框架,每個框架的圓圈位置略有不同。這五個框架類似於遊戲迴圈邏輯的五個迭代。 一步一步地快速移動圓形,可以產生運動效果。

高幀頻的好處

您可能知道,在遊戲中,人們談論他們的遊戲裝備可以達到多少fps。Fps代表每秒的幀數。 每次在螢幕上繪製遊戲物件時,都將算作一個幀。

Fps是每秒可以在螢幕上繪製遊戲的時間。通常,硬體越好,fps越高。fps越高,遊戲將越流暢。同樣,幀之間的延遲將變得更小,從而使遊戲的狀態更快。 這為您提供了遊戲方面的優勢。這就是為什麼專業遊戲玩家想要擠出他們能得到的每一幀並在最佳硬體上花費很多的原因。

遊戲迴圈的理想幀速率

每秒鐘

人類可以處理10到12幀。 超出此限制的幀數,您就會將其視為一種運動,這就是您想要的遊戲。 當然,以12fps的速度執行的遊戲非常不穩定。您可能會認為它是一種動作,但是如果要使其看起來更平滑,則需要更多幀。電影通常使用24fps,但大多數遊戲的幀數甚至更多。如果有更好的選擇,為什麼不僅僅以120fps的速度運行遊戲並使它變得更加流暢呢?

當然,您還需要考慮顯示器的重新整理速率。在60Hz的顯示屏上(每秒60次重新整理率)以120fps的速度運行遊戲不會有什麼好處。您將要繪製的幀超出了顯示屏的處理能力。多餘的幀將永遠不會顯示,只會浪費您的系統資源。現代顯示器支援更高的重新整理率,因此如果您想每秒充分利用所有120幀,則需要有一個這樣的顯示器。 但是HTML5遊戲不僅適用於具有顯示效果出色的高階桌上型電腦,而且還必須在移動裝置上執行並且能表現良好。

理想的情況是幀率等於顯示器重新整理率的fps。它將提供最流暢的體驗,並且遊戲狀態更新也是最快的。同時,您希望限制最大fps,以防止遊戲佔用過多的系統資源,併成為移動裝置上的耗電裝置。

因此,總而言之,這是在建立HTML5遊戲時要在遊戲迴圈中需要考慮的內容:

  • 儘可能高的幀率以獲得平滑度
  • 幀速率不高於螢幕重新整理率
  • 幀速率不佔用過多的系統資源

讓我們檢查一些選項,看看它們滿足這些要求的程度。您將找到適合您遊戲的完美迴圈,並瞭解常見錯誤。

建立遊戲迴圈

好的,所以您需要遊戲迴圈嗎?為什麼不只在JavaScript程式碼中使用這個簡單的while迴圈?

// A bad game loop
while (running) {
    draw();
}

迴圈會啟動並無限期地繪製,直到有人告訴它停止執行(例如暫停遊戲時)。因此,您將從系統中獲得最大的潛力,並達到硬體允許的最大fps。

但是有一個小問題。每個瀏覽器選項卡上的JavaScript都在一個執行緒上執行。通過while迴圈,可用系統資源的每一點將一次又一次地執行繪製操作。這將使您的瀏覽器無法執行其他任務,例如管理使用者輸入或其他重要的事件。當您讓它執行一段時間後,它將掛起您的瀏覽器,並最終發出著名的“此頁面未響應”警告。

要解決此問題,您需要在執行遊戲迴圈時讓瀏覽器喘氣。您可以使用setInterval()之類的方法為每個幀迴圈設定的時間。這將在每個繪圖操作之間花費一些時間,從而為系統提供了執行繪圖以外的其他任務的空間。

// Another bad game loop
setInterval(gameLoop, 16);

function gameLoop() {
    draw();
}

以16ms的間隔,此遊戲迴圈將達到約60fps。這將比while迴圈執行得更好,但是不能保證當gameLoop()函式被觸發時瀏覽器已準備好執行重新繪製。您可能會計算出永遠不會顯示在顯示器上的幀。如何解決這個問題?

JavaScript正確的遊戲迴圈

您需要一種在迴圈時讓瀏覽器有空的方法,並使遊戲迴圈與瀏覽器重畫同步。 幸運的是有一個解決方案。您可以使用window.requestAnimationFrame()告訴瀏覽器您要請求動畫或遊戲的重繪。

// The proper game loop
window.requestAnimationFrame(gameLoop);

function gameLoop() {
    draw();
    window.requestAnimationFrame(gameLoop);
}

瀏覽器將自行執行回撥函式。意味著它不會掛起系統。另外,當瀏覽器標籤不再集中時,瀏覽器可能會減少回撥的數量,以減少系統載入。例如,這將提高執行該遊戲的裝置的電池壽命。

您還記得有關幀頻的那一段嗎?好吧,瀏覽器會為正在執行的裝置選擇合適的fps。這通常意味著您的遊戲迴圈將以60fps的速度執行,但通常會與您的顯示重新整理率匹配。requestAnimationFrame()函式負責此工作。

該方法滿足適當的遊戲迴圈的所有要求:迴圈以幀速率執行,這將使遊戲看起來更流暢,考慮螢幕的重新整理率並減少系統資源的使用。 這是用於您自己的HTML5遊戲的完美匹配。

如何使用requestAnimationFrame()?

函式window.requestAnimationFrame()接受回撥函式作為引數。您可以在其中傳遞gameLoop()函式。當瀏覽器準備就緒時,它將執行該函式。

您將必須通過一次呼叫window.requestAnimationFrame()來啟動遊戲迴圈,然後繼續在迴圈內呼叫它。在下一個示例中更容易理解:

<script>
    "use strict";
    let canvas;
    let context;

    window.onload = init;

    function init(){
        canvas = document.getElementById('canvas');
        context = canvas.getContext('2d');

        // Start the first frame request
        window.requestAnimationFrame(gameLoop);
    }

    function gameLoop(timeStamp){
        draw();

        // Keep requesting new frames
        window.requestAnimationFrame(gameLoop);
    }

    function draw(){
        let randomColor = Math.random() > 0.5? '#ff8080' : '#0099b0';
        context.fillStyle = randomColor;
        context.fillRect(100, 50, 200, 175);
    }
</script>

來自requestAnimationFrame()的回撥具有一個引數,它是包含當前時間的時間戳。它的值與呼叫Performance.now()所獲得的值相同,但是以這種可選的方式提供。您可以從gameLoop()函式訪問timestamp

您現在不會用到時間戳。在下一個有關畫布動畫的教程中,您將學到更多關於將時間用作遊戲迴圈因素的資訊。當您有移動的物體時,解釋那裡的東西會更容易。 只要記住您以後可以使用它,它對於計算運動很重要。

檢視正在執行的遊戲迴圈

讓我們嘗試一下您的新遊戲迴圈。執行此程式碼時,與上一教程相比,您僅需呼叫一次draw()函式即可看到很大的不同。現在連續多次呼叫draw()。矩形大約每秒重繪60次,具體取決於執行迴圈的裝置。

這是您的遊戲迴圈的示意圖。隨著本教程系列的進展,它將擴充套件更多工。

矩形的顏色仍然是隨機的。因此,每幀都會選擇一種新的隨機顏色。這就是為什麼您現在看到一個閃爍的矩形在紅色和藍色之間快速切換的原因。這樣可以更輕鬆地檢視遊戲迴圈是否正在正常工作(但看起來並不愉快)。

計算並顯示fps

做得好,您已經制作了一個遊戲迴圈,並且可以正常工作。但是,如果您可以衡量其效能並檢查每秒產生多少幀,那不是很好嗎?讓我們嘗試使用遊戲迴圈中提供的timestamp引數來計算當前fps。

首先通過計算從上一幀開始經過的時間開始。您可以通過從當前時間戳中減去前一幀的時間戳來實現。剩下的時間是自上一幀以來的毫秒數。

將該數字除以1000,可以從毫秒到秒。花費一秒鐘的時間即可生成一幀。用1除以該數字即可得到每秒的幀數。

這是一個使用fillText()繪圖操作的非常簡單的實現示例,就像在畫布繪圖教程處理一樣,將fps作為文字繪製到螢幕上:

let secondsPassed;
let oldTimeStamp;
let fps;

function gameLoop(timeStamp) {

    // Calculate the number of seconds passed since the last frame
    secondsPassed = (timeStamp - oldTimeStamp) / 1000;
    oldTimeStamp = timeStamp;

    // Calculate fps
    fps = Math.round(1 / secondsPassed);

    // Draw number to the screen
    context.fillStyle = 'white';
    context.fillRect(0, 0, 200, 100);
    context.font = '25px Arial';
    context.fillStyle = 'black';
    context.fillText("FPS: " + fps, 10, 30);

    // Perform the drawing operation
    draw();

    // The loop function has reached it's end. Keep requesting new frames
    window.requestAnimationFrame(gameLoop);
}

您可以執行程式碼來檢查fps計,並檢視遊戲迴圈是否正常執行。如果您在臺式機裝置上檢視此頁面,則該頁面每秒應能命中60幀。

fps每幀更新一次,因此可能視覺體驗不好,但對於本演示很好。如果願意,您可以花更多時間,並考慮一些使fps更新頻率降低的方法。例如,您可以緩衝fps,並且每秒僅更新一次。但是目前,這一切都在fps計上。

下一步是什麼?

現在所有這些都在遊戲迴圈中。您的JavaScript遊戲迴圈就位,並且矩形每秒重繪多次。您瞭解fps和遊戲迴圈之間的關係,並且可以測量自己遊戲的fps。如果您有任何疑問,請隨時在評論部分提問。

在本教程的下一步中,矩形將更加生動。您將學習如何在HTML5畫布上新增運動並製作動畫。此外,您還將瞭解幀頻對動畫的影響。