幀同步在服務端採用房間模式,玩家在對戰時創建對應房間並將玩家加入該房間進行對戰,對於在線遊戲玩家數超過單服務器負載上限時可以多開房間服,這樣可以有效分散帶寬壓,為了更好的遊戲體驗一般會對進行對戰遊戲匹配進入合適的房間。

幀同步保證所有客戶端在每一個frame執行後的結果是一致的,但考慮不同客戶端延遲不同使用嚴格模式必須等待所有客戶端消息收到才能廣播,一個客戶端卡會造成所有用戶都卡頓,在這我們使用樂觀鎖模式。

樂觀模式:
服務端每一個turn(frame)無須等待客戶端消息,直接將當前收集到的消息廣播給客戶端,因此卡的客戶端自己會顯示卡頓或是無效,但不會影響到其他用戶

90362-wjtypddppvg.png

我們務必保證客戶端上傳FrameData依據的關鍵幀Id是準確無誤的,也就是客戶端A在 FrameId = 233 跟客戶端B在 FrameId = 233,不能有時間差,所以保證了客戶端 Update 跟計算關鍵幀都是依據真實時間就能做到這點,這樣客戶端上傳FrameData就醫定是校時過的。

網路平滑化
當然服務端或客戶端收到對方消息可能是網路延遲過N各Frame才收到,這個就看服務端/客戶端的處理是設定平滑多少了,平滑設定間隔大了

服務端處理的是 Current FrameId - N FrameId 的消息,也因此客戶端收到消息必定也是延遲N Frame過後的數。這邊會牽涉到預判跟回滾,因為樂觀鎖不會因為消息沒收到就停止,所以客戶端必須預測目前Frame的玩家行為,但如果預測錯了就必須回滾回去。

邏輯計算要點
在邏輯運算中必須保證多端之間的運算結果是一致的,因此必須保證以下重點

  1. 使用隨機必須保證執行結果一致
    每一個房間分配一個隨機種子,根據種子計算的到結果一致
  2. 禁止使用float進行計算
    自行實現float class
  3. 客戶端刷新 entity 不可以有順序性問題,譬如判斷 entity.UserId == MyUserId 造成運算順序差異

在寫操作判斷時發現狀態的紀錄總是有問題, 有時候系統沒有keyUp事件造成了狀態殘留, 寫法如下

void FixedUpdate(){
    if (Input.GetKeyDown(KeyCode.A)){
        Log.Debug("KeyDown");
    }
    if (Input.GetKeyUp(KeyCode.A)){
        Log.Debug("KeyUp");
    }
}

後來發現 Input.GetKeyDown, Input.GetKeyUp 不能寫在 FixedUpdate 不然就會出現缺少的問題

當使用 TexturePacker 打包 Atlas 到 Unity 結果發現原本已經配置好的貼圖都變成了 missing 狀態
57611-b9wk7sparxl.png

查了 Prefab 並沒有變動, 從資源 Atlas_XXX.png.meta 發現如果 nameFileIdTable 裡面對應的 fileId 為 0 則
圖集重新生成就會變成丟失, 要解決問題只能刪除 Atlas_XXX.png 重新生成 .meta , 圖集中刪除圖片沒有效果

49914-tgmges6ea4f.png

之前使用了 Unity FixedUpdate 計時器來處理 update, 後來發現在多端之間客戶端計算結果不一致
開始查起這個問題, 查到最後發現是因為客戶端調用 update 次數不一致造成的
由於A端跟B端在FPS上有所差別, A端FPS=60, B端FPS=90, 在這個基礎上調用的 fixedUpdate 次數不一致
造成了B端的怪物行走比A端快

修改思維
(1) 如果不改變 fixedUpdate 造成的次數問題就必須把行走距離改成根據時間差來計算實際距離, 如下

movement = 120 pixels/sec
則 120/50 =2.4/Frame, 每幀需要行走2.4pixels, 
如果當前幀跟上一幀時間間距為32 minisec,
 則 32 / 20 = 1.6 * 2.4 = 3.84 pixels 為當下幀需要行走的距離

(2) 修改計時器保證每一次 update 都是 20 ms

//將 fixedUpdate 更新頻率拉高到 120 FPS
void FixedUpdate(){
    long nowt = this.GetUtcTimeMS();
    finish = false;
    while (!finish){
    if ((nowt - this.updateTs) > 20){
        this.updateTs += 20;
        //實際調用刷新函式
        this.doUpdateLogic(param);
        finish = false;
    }
}
假設客戶端在一個tick內調用3次doUpdateLogic, 底下調用不可以使用 UTC.Now 去計算時間差值, 不然3次計算
出來的差值都會是最大值

怪物追擊思考了幾個作法

一、以房主客戶端為主計算 Boss AI 的行為, 發送給服務端進行廣播, 其他客戶端收到消息後根據消息內容進行撥放
裡面涉及到幾個問題
(1) 需要服務端綁定房間主, 如果房間主離線則其他人需要頂替發送 AI 操作行為, 銜接上不少細節問題
(2) 其他端收到消息需要根據消息的 frameId 進行回放或是快進

二、各端自行計算並達成一致
(1) 開房時服務端把必要參數發送給客戶端確保客戶端計算結果一致
(2) 客戶端AI刷新要改成時間累計方式, 不然會有錯

NOTE
一、斷線重連處理