您當(dāng)前的位置:游戲狗新聞>行業(yè)觀點

          對抗網(wǎng)絡(luò)延遲 手游服務(wù)器實時同步方案分享

          2016-03-09 13:44:53來源:游資網(wǎng)編輯:妃卿

          網(wǎng)絡(luò)延遲是所有實時同步的游戲都會遇到的問題,下面是關(guān)于實時同步問題的一些思考和處理方法。具體的解決方法可能比較特殊,首先這里的服務(wù)器并不跑定時器(除了一個游戲結(jié)束倒計時的定時器),由前端驅(qū)動,延遲的情況下主要是由前端來預(yù)測或糾正,服務(wù)器輔助,處理和轉(zhuǎn)發(fā),據(jù)我的了解好像沒什么人這樣子搞吧。所以看完如果覺得我這邊有考慮不周的,或者有更好的思路,歡迎拍磚 / 交流。

          首先這是一個兩邊出兵對攻的游戲,只有2個玩家,而戰(zhàn)場上士兵/英雄的數(shù)量也不會太多,最多不會超過50個吧,士兵都是有AI的,不被玩家控制。玩家能做的操作很有限,這里只以出兵這個操作為例。在游戲開始的時候,會有一次校時操作,這個后面會說到。

          玩家A在Tick1點出兵,播放各種出兵特效之后,Tick1 + Delay = Tick2,在Tick2這個時間才會真正出兵,Delay等于抬手時間,也就是一個允許的延遲時間,并不等于網(wǎng)絡(luò)延遲,應(yīng)該理解為一個前搖動畫的播放時間,這個時間越長越好,在不影響玩家體驗的前提下(例如弄一個士兵生產(chǎn)隊列,從出兵指令發(fā)出到士兵生產(chǎn)完畢,這可需要不少時間)。

          這時候玩家請求服務(wù)器,請求的內(nèi)容包含Tick1 + 出兵指令,服務(wù)器收到指令,對服務(wù)器而言,這時可能有兩種情況,第一種是在Tick2之前收到,第二種是在Tick2之后收到

          在Tick2之前收到,服務(wù)器可以轉(zhuǎn)發(fā)操作,告訴所有的客戶端,我們在Tick2出兵(包的內(nèi)容包含Tick2和出兵指令),這時候?qū)α硗庖粋€玩家,他可能看不到出兵的特效,而是直接看到出兵,這沒有問題,或者他看到的特效在時間上晚了一些,這都是可以接受的,不影響游戲邏輯的。

          這是前后端網(wǎng)絡(luò)正常時的情況:

          在Tick2之后收到,服務(wù)器可以丟棄這個操作,或者立刻執(zhí)行這個操作,這時候已經(jīng)是Tick3了,廣播給所有玩家,在Tick3出一個兵,因為這時服務(wù)器已經(jīng)是Tick3,那么客戶端收到出兵指令的時間是已經(jīng)過了的,這時候會導(dǎo)致客戶端一定會進行一次糾正,那么我們可以再延一延,讓這個指令在Tick4再執(zhí)行,Tick3 + Delay2 = Tick4。丟棄,Tick3,Tick4執(zhí)行的結(jié)果分別是,玩家的操作無效,戰(zhàn)斗需要經(jīng)過一次糾正(糾正會導(dǎo)致玩家看到奇怪的東西),以及玩家的操作延遲了,但是看到的東西是正常的,只是我的操作晚了一段時間而已。

          服務(wù)器延遲執(zhí)行這個指令,客戶端在生效時間之前收到,大概是這樣子的:

          所有的指令從觸發(fā)到生效都不是立即的,都是經(jīng)過延遲的,哪怕我的網(wǎng)絡(luò)延遲在1ms之內(nèi),我這個指令都要在delay時間之后才執(zhí)行,而前后端都會有一個指令Queue,來記錄在哪一幀應(yīng)該執(zhí)行哪一個指令。

          所有的指令,只要做到在第幾幀執(zhí)行的統(tǒng)一,就可以保證結(jié)果的統(tǒng)一。

          客戶端接收到指令的時候,這時候又有2種可能,分別是在Tick2之前,以及Tick2之后。

          在Tick2之前收到,那么皆大歡喜。在Tick2之后收到,那么我們這個操作就延遲了,可能要進行糾正,這里說可能,因為還有一線生機。就是玩家點出兵之后,并不等服務(wù)器返回,而是在Tick2時自動出兵,這樣子的一個好處是,客戶端在網(wǎng)絡(luò)比較差的情況下,所有的東西都可以得到反饋。雖然這些反饋可能是錯誤的,但是沒關(guān)系,關(guān)鍵是玩家體驗的流暢,錯了最后肯定會被糾正,只要處理好糾正時候的表現(xiàn)就可以了。

          如果自動客戶端自行預(yù)測:

          而且這樣子做不一定是錯的,如果服務(wù)器在Tick2之前收到我的請求,那么服務(wù)器會在Tick2執(zhí)行出兵的邏輯,而我在Tick2之后收到服務(wù)器的響應(yīng),但我也在Tick2執(zhí)行了出兵的邏輯,這是同一幀,這種情況下是不需要糾正的,因為雖然延遲了,但是我們正確預(yù)測到了結(jié)果。需要糾正的有兩種情況,第一種是B玩家在Tick2之后收到,因為B玩家是無法預(yù)測到A玩家在Tick1點擊了出兵,在Tick2出了一個兵。第二種是服務(wù)器在Tick2之后才收到,那么兩個端可能都需要糾正,這里說可能,因為還有另外的一線生機。

          如果我們不做預(yù)測,而是等服務(wù)器的結(jié)果,根據(jù)服務(wù)器的結(jié)果來執(zhí)行,這種情況下,客戶端的表現(xiàn)是流暢的,戰(zhàn)斗是流暢的,只是我的點擊沒有立即反應(yīng)而已。服務(wù)器在Tick3下發(fā)(內(nèi)容包含Tick4 + 出兵指令),在Tick4執(zhí)行,客戶端只要在Tick4之前能收到,就可以在Tick4執(zhí)行出兵,那么結(jié)果也是正確的。這種甚至我們可以以服務(wù)器收到的時間為準(zhǔn),服務(wù)器每次收到都在當(dāng)前時間的基礎(chǔ)上延遲一段時間來執(zhí)行,完全無視玩家點擊的時間,只要客戶端在這段延遲時間內(nèi)收到結(jié)果,也是不需要糾正的。

          這兩種一線生機的區(qū)別在于,一個是忽略了包從服務(wù)器回來的延遲,一個是忽略了包到服務(wù)器的延遲,一個是保證客戶端流暢,一個是盡量避免糾正。具體要在實際環(huán)境中測試才能知道,哪種更適合我們游戲。由于所有指令的執(zhí)行會放在一個隊列中,所以這幾種方式的切換只需要改動少量的代碼。當(dāng)我們拿捏不準(zhǔn)的時候,盡可能讓這部分可以被靈活地調(diào)整。整個服務(wù)器這邊的Tick機制就是可以被靈活調(diào)整的(因為我不跑定時器)。

          最后就是糾正了,首先糾正是由前端發(fā)起的,當(dāng)然后端也知道前端延了,后端的處理其實比較隨意,無關(guān)緊要,前面說的,丟棄,立即執(zhí)行,或者延遲執(zhí)行,都是可以的,但看上去延遲執(zhí)行是個更好的主意,這個也看具體游戲吧。前端如何知道自己延了,這個問題其實很簡單,在戰(zhàn)斗開始的時候進行一次校時,然后雙方都以一致的頻率,例如每秒10幀,從第0幀開始前進(實際上服務(wù)器只是記錄一個時間,并不跑定時器)。

          其實前后端只要記錄了第0幀的這個開始時間,是很容易算出當(dāng)前是在第幾幀的,前后端的這個開始時間并不相等,只是邏輯上相對而已。當(dāng)然,這個時間也會存在誤差,誤差的結(jié)果就是,一邊快一點或者慢一點。但是沒關(guān)系,我們不是按照時間來算,我們是按照幀來算,咱不要求同一個時間兩個端的內(nèi)容是完完全全的一樣,咱只要求結(jié)果一樣。例如一臺設(shè)備性能很差,每秒5幀,但是他的結(jié)果不會錯誤,游戲10秒后結(jié)束,他這邊就20秒后結(jié)束,但結(jié)束時的結(jié)果是一樣的就行,至于操作,如果存在這樣的可能性,那服務(wù)器就把操作延遲執(zhí)行,對前端而已可能按下去要等好幾秒才能響應(yīng),但這時候都已經(jīng)不是網(wǎng)絡(luò)延遲的問題,是設(shè)備卡頓的問題。前端一直在跑,但是跑不過來,要解決這個問題,只能是換手機,當(dāng)然我們自己要保證在性能很差的手機上能跑起來才行,例如兩三年前的機器,但實際上咱也不需要花太多心思在這上面,這個玩家這么爛的手機都不舍得換,怎么舍得往游戲里面充錢呢?直接放棄他了。

          服務(wù)器并不跑計時器,這是什么原因呢?有一部分是性能的原因,每個子彈,怪物,BUFF這些可能都需要掛計時器,我不希望服務(wù)器掛太多的計時器。比較大的一部分是讓拿捏不準(zhǔn)的這部分更加的可控。例如這個游戲不做實時同步了,讓事情變得不是那么糟糕。只需要少許的改動,就可以實現(xiàn)服務(wù)器的校驗。

          不跑計時器怎么做呢?很簡單,首先你還是需要有一個計時管理的,類似Schedule,游戲邏輯中添加的計時器全放到這里面,當(dāng)客戶端請求的時候,我們先從上一次計算的時間模擬到當(dāng)前時間,將當(dāng)前時間減去上次的時間,算出有N幀,然后直接 loop N次計時器,這個loop和客戶端的loop相比,就是少了一些判斷逝去時間是否小于最小間隔時間,如果是則sleep一下這樣的代碼,而改成了一個for循環(huán),循環(huán)N次。當(dāng)然,loop的不一定只是計時器,但是一定包含計時器,而且最主要的就是計時器。

          loop完之后,將上次的時間記錄為當(dāng)前時間,然后進行校驗,轉(zhuǎn)發(fā)指令。這時并不執(zhí)行指令,而是把通過校驗的指令放入指令隊列中,等待下次執(zhí)行。指令執(zhí)行的結(jié)果是什么,服務(wù)器現(xiàn)在是不知道的。可以看到是客戶端的請求來驅(qū)動服務(wù)器,而不是計時器來驅(qū)動服務(wù)器運行邏輯。這就有點類似回合制了。那么還有一個問題,假設(shè)客戶端都不發(fā)請求,那服務(wù)器不就動不了了?服務(wù)器會跑一個計時器,這個計時器只做一件事,就是游戲結(jié)束,模擬玩家發(fā)一個空的指令到服務(wù)器這邊,服務(wù)器loop到游戲結(jié)束,然后下發(fā)戰(zhàn)斗結(jié)果。

          不跑計時器,如何讓當(dāng)這個游戲從實時同步變成非實時同步變得簡單呢?可以這樣子,客戶端所有發(fā)送的指令,全部都在客戶端直接運行,但是記錄下來,然后在游戲結(jié)束時,請求一次服務(wù)器,將指令隊列發(fā)給服務(wù)器,服務(wù)器只需要設(shè)置指令隊列,然后執(zhí)行一次loop到游戲結(jié)束的調(diào)用,自然可以校驗到戰(zhàn)斗結(jié)果。這個非實時同步的需求,本身就是存在的,例如單刷副本,這個我們是需要校驗的。所以只需要在外面進行一層包裝,就可以替換他們。而且改動的代碼量并不多。

          接下來還是說糾正的問題,當(dāng)客戶端發(fā)現(xiàn)服務(wù)器下來的包延遲了,超過可接收的時間了,客戶端需要向服務(wù)器請求最新的數(shù)據(jù)來刷新,這個過程中,客戶端是正常運行的,然后當(dāng)數(shù)據(jù)下來之后,大概花0.5-1秒的時候來刷新數(shù)據(jù)。將本地有服務(wù)器沒有的對象干掉,本地沒有服務(wù)器有的對象創(chuàng)建,兩邊都有的數(shù)據(jù)進行一個平滑的插值計算,讓他在這段時間過渡到最新的數(shù)據(jù)。這里一定會產(chǎn)生一些玩家看起來很奇怪的畫面,在上面加一個正在重新連接...,玩家應(yīng)該會比較能接受這小段看上去奇怪的畫面。

          過渡的時間內(nèi),本地的實時邏輯幀是一直在正常運行的,它記錄服務(wù)器當(dāng)前運行到第幾幀了,本地還有另外一個幀變量,這個變量表示當(dāng)前邏輯幀,這兩個幀都是邏輯幀,正常而言,這兩個幀是相等的,但當(dāng)糾正發(fā)生時,當(dāng)前幀會小于實時幀,例如第200幀發(fā)現(xiàn)我本地需要糾正,然后請求服務(wù)器,服務(wù)器下發(fā)了最新,也就是201幀的結(jié)果下來,到客戶端這邊,已經(jīng)是207幀了,但重置到最新的數(shù)據(jù),也就是201幀,然后開始加速執(zhí)行。執(zhí)行到兩個邏輯幀相等,即恢復(fù)正常速度執(zhí)行。

          首先加速是可行的,因為服務(wù)器可以瞬間執(zhí)行完,那么客戶端為什么不能加速執(zhí)行完呢?最關(guān)鍵的是,每一幀的結(jié)果都是一樣的,前面從200幀之前,客戶端有一部分的結(jié)果就已經(jīng)錯誤了,糾正的本質(zhì)是把錯誤的結(jié)果丟棄,然后重新設(shè)置正確的結(jié)果。再繼續(xù)運行。

          另外假設(shè)是客戶端跑得太慢,跟不上服務(wù)器的話,那實際上服務(wù)器使用延遲執(zhí)行的方式,基本上是不會有糾正的需求的,因為所有指令對于這臺設(shè)備而言,都是未發(fā)生的,客戶端只能慢慢跑,慢慢演示戰(zhàn)斗結(jié)果。這樣的設(shè)備上,動畫都是一卡一卡的,問題已經(jīng)從網(wǎng)絡(luò)延遲的問題轉(zhuǎn)變?yōu)樵O(shè)備的性能問題了,這是另外一個優(yōu)化的話題了。

          邏輯幀和顯示幀的分離,有這么幾個目的,邏輯幀用來保證邏輯的準(zhǔn)確性,也就是這場游戲一共跑2000幀,執(zhí)行2000次邏輯處理。這部分是前后端共用的。至于前端的顯示刷新了8000幀,還是6000幀,影響的只是動畫的平滑度而已,不影響結(jié)果,前端花100秒還是200秒來跑完游戲,也是不影響結(jié)果的。第二是方便糾錯和加速,邏輯幀和顯示幀的交互流程是這樣的,每次邏輯幀執(zhí)行的時候,會修改類似位置這樣的屬性,或者改變一些狀態(tài)。顯示幀負(fù)責(zé)平滑地從當(dāng)前位置,狀態(tài)改變到邏輯幀修改的位置和狀態(tài),并播放相應(yīng)的動畫。邏輯幀并不直接改變這些屬性,而是將這些修改放到一個每個對象特有的數(shù)據(jù)組件中,邏輯判斷時,是取這些里面的數(shù)據(jù)來判斷,而不是顯示層的數(shù)據(jù)。每次更新,顯示幀都會做一個從當(dāng)前過渡到該數(shù)據(jù)的邏輯。邏輯幀的頻率加快了,對顯示幀而已也是沒有影響的,兩者互相獨立,邏輯幀只管邏輯處理還有寫數(shù)據(jù),顯示幀只管取出數(shù)據(jù)來進行顯示。

          客戶端這邊,邏輯幀和顯示幀都是由同一個Schedule來驅(qū)動運行的,但頻率不同,他們比較大的一點區(qū)別是,邏輯幀每一幀的dt是一個固定的值,例如每秒10次邏輯幀,那么這個dt就固定是0.1,而顯示幀是根據(jù)實際的逝去時間來作為dt的。這里的dt指的是每次update傳入的逝去時間。每次邏輯幀寫入的數(shù)據(jù)除了位置狀態(tài)等數(shù)據(jù),還會包含一個需要顯示幀在多長時間內(nèi)模擬完成的數(shù)據(jù),這個也是一個定值0.1。顯示幀拿到位置數(shù)據(jù),我要移動去哪,再拿到時間數(shù)據(jù),多長時間內(nèi)移動到,那么就可以執(zhí)行平滑顯示的邏輯了。

          至于2G 3G的網(wǎng)絡(luò)延遲,這個和PC的延遲有什么區(qū)別呢?一個是延遲會更大,另外一個就是不穩(wěn)定。延遲的數(shù)據(jù)是多少,這個數(shù)據(jù)意義不是太大,因為這個數(shù)據(jù)只有一些參考意義,實際的延遲并不是按照這個數(shù)據(jù)來,而是很不穩(wěn)定的。可能你蹲個廁所,把廁所門一關(guān),信號一下子就差了很多,這是很常見的。因為移動設(shè)備是可移動的,移動到不同的地方都可能有不同的延遲,這時候可能有兩個數(shù)據(jù)會比較有用,一個是2G 3G比較快的速度是怎樣,另一個是2G 3G比較慢的速度是怎樣。在這樣差的網(wǎng)絡(luò)下,必定會出現(xiàn)很多次的延遲糾正,但只要我們能收到消息,游戲就可以運行。客戶端模擬是可以獲得高延遲下好很多的體驗,因為每次點擊都有反應(yīng),雖然真正生效的時間延遲了,但你的操作還是生效了。2G 3G的另外一個問題就是斷線,斷線重連其實又是另外一個略有蛋疼的話題了。

          在2G 3G下,在高延遲下給用戶帶來不差的體驗,這個是實時同步比較難做到的,關(guān)鍵看游戲指令的可延遲性,實時要求的高低,玩家操作的頻繁度,以及錯誤糾正時的體驗,能否讓玩家接受。如果不行,那么就需要弱化它,例如在進入游戲之前,檢測一下ping值,如果太高,則提醒玩家,你當(dāng)前的網(wǎng)絡(luò)延遲較高,讓玩家自己決定在不在高延遲的環(huán)境下游戲。實際上對于網(wǎng)絡(luò)延遲,絕大多數(shù)的玩家都不陌生,打了一劑預(yù)防針之后,對后面的反應(yīng)不及時的體驗包容性會高一點。在延遲高的情況下,建議玩家去刷單機副本,也是一個可行的方案。另外,還有一個方案,雖然有點欺騙玩家,但只要玩家覺得游戲流暢就可以了,就是派AI的機器人跟高延遲的玩家打。

          整個同步的想法目前正在試驗中,但理論上是可行的,看上去可能有些復(fù)雜,但實際的代碼框架搭建起來之后,代碼寫其實是很簡潔的,因為所有的邏輯都不需要關(guān)注延遲。并且可能會變化的部分被隔離開了,每個東西盡可能地獨立。當(dāng)然,在實現(xiàn)的過程中肯定是會碰到更多的問題,有問題解決就是了,關(guān)鍵是要有思路,有解決問題的方向。

          整個想法的落地大概是這樣的,前后端都是用的C++,前端是2dx,后端是kxserver,后端搭建一套模擬2dx的框架,實現(xiàn)一份簡化版的Director,Schedule,Node,Component,然后制定編寫邏輯相關(guān)組件的規(guī)則,用自己寫的消息機制來傳遞消息,當(dāng)有一部分邏輯需要執(zhí)行到顯示相關(guān)的內(nèi)容時,可以用事件來處理,客戶端存在這個監(jiān)聽者,而服務(wù)端不存在。另外也可以使用預(yù)處理,根據(jù)是否定義了Running In Server這樣一個宏來預(yù)處理一些代碼。

          設(shè)計中是分了比較多的層和模塊,來確保萬一哪個不行了,不影響到其他的代碼。在落地的過程中會持續(xù)地完善代碼,打磨,驗證想法。希望這個項目結(jié)束后,能沉淀下一套Cocos2d-x網(wǎng)絡(luò)實時同步的規(guī)范和前后端簡易框架,來復(fù)用到其他有類似需求的項目中。

          相關(guān)新聞
          ?游戲狗 Gamedog.cn 北京手游天下數(shù)字娛樂科技股份有限公司 版權(quán)所有
          安全百店 ANVA自律組
          游戲狗微信關(guān)注游戲狗訂閱號
          主站蜘蛛池模板: 国产免费拔擦拔擦8x| 欧美日韩国产一区二区| 国产永久免费观看的黄网站| 东北大坑第二部txt| 福利一区二区三区视频在线观看| 国产男女猛烈无遮挡免费视频网站 | 国产aⅴ精品一区二区三区久久| 一本丁香综合久久久久不卡网站| 欧美高清在线视频在线99精品| 国产一区二区三区不卡免费观看 | 宵宫被爆3d动画羞羞漫画| 久久精品国产清高在天天线 | 嫩草成人永久免费观看| 久久精品日日躁夜夜躁欧美| 欧美综合自拍亚洲综合图片区| 午夜国产精品久久久久| 青青青青久久久久国产| 国产精品国产国产aⅴ| 久久久亚洲欧洲日产国码aⅴ| 欧美日韩亚洲一区二区三区| 免费看男女下面日出水视频| 竹菊影视国产精品| 无码色偷偷亚洲国内自拍| 亚洲乱码一二三四区麻豆| 老师别揉我胸啊嗯上课呢视频| 国产精品三级国语在线看| a级韩国乱理论片在线观看| 最近免费中文字幕大全高清大全1| 人人爽人人爽人人爽人人片av| 黑人巨茎大战欧美白妇| 国内精品视频一区二区三区| 久久久亚洲欧洲日产国码农村| 欧美成人精品第一区| 人妻av一区二区三区精品| 美女扒开尿口让男人30视频| 国产公开免费人成视频| 五月婷在线视频| 妖精动漫在线观看| 亚洲av无码专区亚洲av桃| 精品久久人妻av中文字幕| 国产乱子伦在线观看不卡|