仍是先說下追幀的問題吧。飛機項目採用的是幀同步的方案,渲染層與邏輯層分離,由定時器一秒20幀來驅動邏輯層作update,而對於渲染層則是以一秒40幀的速度來驅動。渲染層輪循邏輯層作插值。在網絡抖動的狀況下,本地演算的幀LocalFrameId可能會落後或領先服務器下發的ServerFrameId。順便提一句,ServerFrameId在這裏主要是爲了給各個客戶端一個統一的參照系,並不會在Frame中下發其它客戶端的命令。也能夠不經過ServerFrameId,而是經過對比其它客戶端的幀命令來校訂各客戶端的時鐘儘量地保持一致,我我的不推薦這種方案,這裏也就不展開討論了。那麼當LocalFrameId與ServerFrameId差距較大時,我以前的方案會調整本地邏輯層演算的速率,即LocalFrameId落後於ServerFrameId時加快(也就是調小了定時器驅動邏輯層的間隔),而領先於ServerFrameId時則減慢。好比在領先5幀(也就是100MS)左右可能會調整邏輯層驅動間隔由50MS=>120MS,落後5幀時則50MS=>20MS。可是實際測試時效果反而變差了。網絡不太好,但也不是那麼差時會出現飛機忽快忽慢的現象。原本是但願快速調整差距的,沒想到手感反而變差了。
我仔細想了下,以爲以前的認識仍是有一些問題的。首先爲了排除干擾因素,咱們假設客戶端開始遊戲後,其計時時鐘與服務器計時時鐘徹底一致。對客戶端而言,到底什麼是網絡抖動呢?事實上就是網絡幀在某處大量累積,在某刻又忽然大量涌入。這樣致使在一段時間前LocalFrameId遠遠領先ServerFrameId,此時邏輯層的邏輯幀被調得很慢,而在以後後網絡幀又忽然涌入,致使LocalFrameId遠遠落後於ServerFrameId,邏輯幀又被調得很快。若是隻考慮當前玩家的操做體驗的話,在必定的網絡抖動範圍內(假設爲500MS),不調節邏輯層幀率,對玩家而言手感上天然是更好的。只是在預測其它玩家或AI時,可能會過分預測一些,或者某些極少數狀況下玩家的操做在本地演算是成功的,可是實際上並不能成功,會對玩家體驗形成一些影響。可是一方面邏輯層回滾加上渲染層平滑插值會大大減輕這種不適感,二則不少時候的預測是正確或者誤差較小的,整體上來看是徹底能夠接受的。
我接着作了一些修改,在-250MS~250MS的浮動範圍內是50MS一幀,250MS~750MS範圍內爲60MS,-750MS~-250MS範圍爲40MS。簡單來講就是邏輯幀的速率調整變得很是溫和。遊戲開始後服務器與客戶端時鐘可能有誤差,可是這個誤差在平均程度上也會在幾秒內被追平。若是LocalFrameId落後或者領先ServerFrameId超過1S,則採用暴力的手段直接拉平便可。
OK,追幀的算是說完了。還有個快照發送的問題,以前作得不完全,今天回來的路上又整理了下,仍是記下來吧。如今時間已經10點33分了。
以前的一個目標是但願有一個在時間上無限制的競技場,玩家能夠不斷地加入進來或者隨時退出。若是是狀態同步的方案那麼就很好處理,客戶端直接拉取服務器的戰鬥狀態,重建戰場便可。可是幀同步方案下,服務端是不保存狀態的。有兩種方案。一種是服務器起運算結點跑同一套戰鬥邏輯,這樣服務器就有了狀態,但這樣也就喪失了幀同步節省服務器資源的優勢了;二是由客戶端按期向服務器上傳狀態快照,服務器保存快照以及自快照以後的全部客戶端命令,客戶端進來後拉取快照和命令演算至當前幀。粗粗一看,彷佛狀態快照的上傳頗爲簡單,不過,很快你就會看到事實並不是如此。
快照上傳的一個重點就是要保證這個快照是不會再改變的有效快照。ServerFrameId同步到客戶端後,在必定的窗口範圍內,[ServerFrameId-WindowSize,ServerFrameId+WindowSize],其中的幀可能會發生回滾。好比在收到ServerFrameId爲100時又收到另外一個客戶端在第90幀的命令,此時客戶端要回滾到第90幀從新演算。所以咱們只須要發送ServerFrameId-WindowSize幀的快照給服務器便可。OK,思路上沒有問題了,不過事情尚未完。
直接的想法固然是在邏輯幀中直接發送ServerFrameId-WindowSize幀。可是這樣可能出現幾個問題:
一是LocalFrameId<ServerFrameId-WindowSize,有效快照還沒有演算出來,沒法發送。
二是一次計時器回調中可能會連續更新多個邏輯幀,此時ServerFrameId是不變的,每一個邏輯幀內部都不加思考地發送ServerFrameId-WindowSize幀會致使重複發送。
三是可能會發生漏幀,好比在LocalFrameId1時由於有效快照ServerFrameId1-WindowSize還沒有演算出來,所以沒法發送。可是在下一幀以前,ServerFrameId可能會改變爲ServerFrameId2,那麼在LocalFrameId2時即便ServerFrameId1-WindowSize已經演算出來,可是此時計算要發送的快照爲幀ServerFrameId2-WindowSize,出現了漏幀的狀況。
其實仔細考慮下,這個問題是有簡明的方案的。說來講去其實就兩種狀況。
1)由於網絡幀可能會一次性大量涌入,也就是說在網絡的回調函數中可能會一次性扔出連續多個ServerFrameId。ServerFrameId-WindowSize可能會大於LocalFrameId;
2)可能會出如今一次計時器回調中連續更新多個邏輯幀的狀況,此時ServerFrameId是不變的。LocalFrameId+WindowSize可能會>ServerFrameId。
也就是說,在網絡回調和邏輯幀中都要考慮是否發送狀態快照的問題。雖然也能夠解決,不過這樣的作法又繁瑣又醜陋。能夠將ServerFrameId與LocalFrameId的變化扔到一個單獨的模塊X中,那麼問題變轉變成在不超過兩個數組的各自上限的條件下,儘量計算可發送的元素範圍。而X更是能夠由本身來定製快照發送的策略。
我記得以前還有更細微的一些地方要注意,不過我懶得再回想或深刻考慮了,對這個問題而言,上面的這些討論也已經差很少了。命令窗口的問題還要記錄下,不過等下次吧。本想休息下的,誰知道竟23點23分了。煩哪。數組