2015年12月30日 星期三

畫面更新的正確作法

強者我朋友,陳大師在十幾年前曾經分享一個很有用的概念,就是如何更新遊戲畫面才是對的,用虛擬碼解釋比較快:


while(true)
begin
     ...
     current_time = get_current_time
     if current_time - before_time >= 33 millisecond
     begin
           update_screen()
     end
     ...
end

上面的虛擬碼就是說每隔 1/30 秒就要更新畫面一次(1/30=33.33333...millisecond),沒有例外。也就是現在人們口中的 Frame per Second = FPS = 畫面更新率,按照 wiki 的解釋是「如果所看畫面之影格率高於每秒約10-12影格的時候,就會認為是連貫的」,當然以遊戲來說 10-12 遠遠不夠,起碼要 30-60 FPS。

這種近乎於常識的東西有什麼好寫的?重點來了,wiki 沒有告訴你更新的時間間隔不一定會發生什麼事?例如這次 30ms,下一次 10ms,再下一次 50ms ...... 我也可以湊出 30 FPS 阿,那使用者會有什麼感覺?

他會感覺到你的畫面是跳動的、不平滑!

為什麼會犯這樣的錯呢?打從 Windows 3.1 開始,Windows 程式設計的基礎就是「事件驅動」(event driven),就算是相關開發工具如 VB、MFC、Delphi、C++ Builder、Qt...乃至於晚近流行的 JavaScript 處處可見事件驅動的概念深植其中。

於是初學者就有一種錯誤印象,就是只有事件驅動才是對的,對於一些畫面偏向靜態的應用程式如文字處理器,這個觀念還講得通,但是如果碰上一些資料經常變化的場合,程式就會看起來「怪怪的」。

可是,上方虛擬碼那種死迴圈感覺跟事件驅動的模型衝突,要怎麼結合呢?很簡單,timer 也是一種事件,所以我們只要設定好 timer,當 timer time out 時刷新畫面就好了,下面就是一個例子:


左邊的 tree view 代表網路上各個裝置節點,當點選 tree view 上這些節點時右邊就會切換到對應的表格進行顯示,timer 會每 0.1 sec 發出一次事件要求表格重繪,在顯示時我們可以比對是否與上次資料有所不同才進行重繪,因為畫面的更新往往是最耗 CPU 資源,當然以現在的 PC 效能來講全部重繪都無所謂。

注意喔,這隻程式並不是遊戲軟體,事實上在工業機台控制中常常要寫類似的程式,把機台上各種資訊即時顯示出來,例如馬達的轉速、扭力、位置、電壓、電流...

如果你沒有用正確的觀念寫程式,什麼都要套事件驅動會發生什麼事?站長看過 LED 取料機是用「噴」的,工業用的計數器一秒鐘計數個幾百到幾千次也很常見,把這個當事件灌進去 event loop,那你覺得你的電腦有可能這麼快,或是去追這個速度是對的嗎?

下面這個不等式請放在心裡:

IO 速度 > 網路速度 > 畫面更新速度

當然有人會說 internet 比我的獨立顯卡慢...這個我們等一下再談,從這個不等式來看畫面根本就不該隨著 IO 連動,再說 IO 可能不是固定間隔變化,那回到前面的定義,使用者反而會覺得你的畫面更新不平滑,雖然浪費了一大堆 CPU 資源!

再回頭談 internet 比顯卡慢的問題,首先要正視的是網路是會延遲的,每次收到資料的時間可能都不一樣,所以要在固定的時間間隔取得資料更新,無異於癡人說夢,解決方式有很多、時鐘同步、外插...這個議題太大了,乾脆推薦一本好書給大家:


這本書花了很多篇幅不斷強調站長一開始強調的 FPS 觀念,可說是吾道不孤必有鄰~

沒有留言:

張貼留言