iT邦幫忙

2021 iThome 鐵人賽

DAY 13
1
自我挑戰組

翻車機率極高的2D平台遊戲(2D Platformer)製作系列 第 13

[Day13] 時間處理

今日目標

  • 限制幀數
  • Framerate Independent

設計錯誤

這個是我再次參考Game Progarmming Pattern - Game Loop這一章節,與Ralylib這個純C的libary得到的一個結果。

之前想說設計是這樣子:

while (true) {
StartScene();

// Input here
// Update here

BeginRendering();

// Render here

EndRendering();
EndScene();
}

然後把時間的處理放在StartScene()EndScene(),但看了Game Programming Pattern之後發現,正常來說如果要限制FPS,達到Framerate Indepent的話,應該是只要對Update(),也就是邏輯的地方做限制,但上面這個的設計是連Input()Render()都在裡面了。後來在github上找到一個遊戲libary - Raylib(沒錯!是當"抄"人的時候了),他的做法是這樣

while (true) {

// Input here
// Update here

BeginDrawing();

// Render here

EndDrawing();

Raylib是把delta time稱作frame time,是由update time跟render time合併起來的。

BeginDrawing()呼叫時會計算update time然後儲存;EndDrawing()呼叫時,同理會更新render time。然後這兩個相加,就是這一個frame的處理時間,再來就是一個重點了。

// in `BeginDrawing`
WINDOW.Time.current_time = (float)glfwGetTime();
WINDOW.Time.update_time = WINDOW.Time.current_time - WINDOW.Time.previous_time;
WINDOW.Time.previous_time = WINDOW.Time.current_time;
// in `EndDrawing`
WINDOW.Time.current_time = (float)glfwGetTime();
WINDOW.Time.render_time = WINDOW.Time.current_time - WINDOW.Time.previous_time;
WINDOW.Time.previous_time = WINDOW.Time.current_time;

WINDOW.Time.frame_time = WINDOW.Time.render_time + WINDOW.Time.update_time; // 這個就是delta time

如果要限制60 FPS,也就是約0.01666秒的時間,update time加上render time的時間可能會小於0.016666秒,於是可以用一些方法讓game loop強制處理剩餘的秒數。

第一種: Sleep

沒錯!讓執行序強制停止N秒,沒記錯的話,SDL中的SDL_Delay底下就是調用各名台相關的Sleep

第二種 while loop

我是用這一種方式,在EndDrawing()那邊接下去寫來著:

    if (WINDOW.Time.frame_time < WINDOW.Time.target_time) {
        float wait_time = WINDOW.Time.target_time - WINDOW.Time.frame_time;

        // wait time
        float start_wait_time = glfwGetTime();
        float end_wait_time = 0.0f;
        while ((end_wait_time - start_wait_time) < wait_time) {
            end_wait_time = glfwGetTime();
        }
        
        // add extra time
        WINDOW.Time.current_time = (float)glfwGetTime();
        float extra_time = WINDOW.Time.current_time - WINDOW.Time.previous_time;
        WINDOW.Time.previous_time = WINDOW.Time.current_time;

        WINDOW.Time.frame_time += extra_time;
    }

實驗: Framerate Independent

昨天有提到Framerate Independent,來實驗一下

在不同的FPS下,高FPS會有較低的delta time;低FPS的會有較高的delta time。故兩者最後都會在同時間抵達目的地。

這邊貼上,我簡單測的code

    SetTargetFPS(10); // change fps here!!
    
    float current_time = GetTime();
    float previous_time = GetTime();
    float time_elapsed = 0.0f;

    int test_count = 0;

    // Game loop
    while (IsWindowRunning()) {
        
        if (e.pos.x > 800.0f) {
            current_time = GetTime();
            time_elapsed = current_time - previous_time;
            previous_time = current_time;

            LogInfo("Time elapsed: %f", time_elapsed);
            e.pos.x = 0.0f;
        }

        e.pos = V2fAdd(e.pos, V2fScalef(e.speed, GetDeltaTime()));

        StartScene(COLOR_BLACK);
        BeginRendering();

        DrawRectangle(e.pos, size, 0.0f, e.c);

        EndRendering();
        EndScene();
    }

有了delta time的調節,不管是10 FPS還是144 FPS都會在同個(會有0.0幾的誤差...)時間,從左走到右。如果沒有個話,就會是全看CPU的頻率在表現跟處理了。

參考

備註: raylib作者還有在更新,我貼的是在00a763e的這個版本

完成!這是今日的成果


上一篇
[Day12] 關於時間粗略紀錄一下
下一篇
[Day14] 初見碰撞系統
系列文
翻車機率極高的2D平台遊戲(2D Platformer)製作33

尚未有邦友留言

立即登入留言