[從零開(kāi)始的Unity網(wǎng)絡(luò)同步] 5.服務(wù)器將狀態(tài)同步給客戶(hù)端(狀態(tài)緩存,狀態(tài)插值,估算幀)

上一篇文章中,已經(jīng)可以在服務(wù)器上直接根據(jù)服務(wù)器自己的操作指令,模擬得出結(jié)果,修改球的位置了,接下來(lái),將要考慮如何將服務(wù)器模擬的位置如何同步到客戶(hù)端.

1.服務(wù)器向客戶(hù)端發(fā)送單位實(shí)體(Entity)狀態(tài)

首先需要設(shè)定一個(gè)發(fā)包的頻率(SendRate),目前設(shè)置的是每10個(gè)模擬幀發(fā)送一次,對(duì)于60模擬幀每秒的游戲世界來(lái)說(shuō),這也相當(dāng)于6個(gè)包每秒.這個(gè)包的數(shù)據(jù)應(yīng)該是描述Entity在當(dāng)前模擬幀的狀態(tài).

public class State
{
    public int frame;                                               //模擬的幀號(hào)
    public Entity entity;                                          //所屬的Entity
    public List<Property> properties;                              //需要描述的屬性

    public int Pack(Packet packet)
    {
        packet.Write(frame);
        //將屬性數(shù)據(jù)寫(xiě)入消息包packet
    }
    public int Read(Packet packet)
    {
        frame = packet.ReadInt();
        //從消息包中取出屬性數(shù)據(jù)
    }
}

發(fā)送的方法:

public void FixedUpdate()
{
    if (Core.frame % SendRate == 0)                    //每隔10幀發(fā)送一次
    {
        foreach (var conn in connections)
        {
            conn.Send();                                            
        }
    }
}
//connection中發(fā)送的方法
public void Send()
{
    Packet packet = PacketPool.Get();
    foreach(Entity entity in entities)
    {
        entity.currentState.Pack(packet);                  //將當(dāng)前狀態(tài)數(shù)據(jù)寫(xiě)入消息包
    }
    _connection.Send(CustomMsgTypes.InGameMsg,  packet.msg_untiy);        //通過(guò)UnityEngine.Networking組件的Connection發(fā)送數(shù)據(jù)
}

這樣就把Entity的狀態(tài)打包發(fā)向所有的客戶(hù)端了.

2.客戶(hù)端接收到服務(wù)端的狀態(tài)包

客戶(hù)端接收到服務(wù)端的數(shù)據(jù)包,然后從數(shù)據(jù)包中拿到描述Entity狀態(tài)的數(shù)據(jù)后,需要考慮的是,如果是第一個(gè)狀態(tài),可以直接拿來(lái)應(yīng)用到Entity上,如果不是第一個(gè)狀態(tài)的話,那就不能直接應(yīng)用,因?yàn)榫W(wǎng)絡(luò)傳輸抖動(dòng)的因素,服務(wù)端雖然是每隔10幀發(fā)一個(gè)包,但是客戶(hù)端收包頻率不一定是每隔10幀就收到的,如果直接應(yīng)用的話,必然會(huì)導(dǎo)致抖動(dòng).這個(gè)時(shí)候,我們就需要在客戶(hù)端對(duì)服務(wù)器端進(jìn)行狀態(tài)緩存(StateBuffer)狀態(tài)插值(StateInterpolation).

1.為什么需要狀態(tài)緩存狀態(tài)插值

客戶(hù)端收到的狀態(tài)包都是帶幀號(hào)(Frame),幀號(hào)表示了這個(gè)狀態(tài)是服務(wù)器在那幀模擬得到的狀態(tài),客戶(hù)端想要,去除抖動(dòng),平滑的渡過(guò)的狀態(tài)之間的時(shí)間的話.就需要在State_A與State_B進(jìn)行插值計(jì)算.插值計(jì)算的公式應(yīng)該是這樣

Current = MathUtils.Interpolate(State_A, State_B, ???? / (State_B.frame -State_A.frame ))

在公式右側(cè),除了????,其他都是已知的,想要得到插值結(jié)果,那么????應(yīng)該是什么呢?
因?yàn)榉帜傅?strong>兩個(gè)狀態(tài)的幀號(hào)差,所以分子應(yīng)該也是幀號(hào)才對(duì),客戶(hù)端的幀號(hào)跟服務(wù)端幀號(hào)不一致(因?yàn)榉?wù)器肯定早就啟動(dòng)了,客戶(hù)端是后來(lái)才連接服務(wù)器的),這個(gè)時(shí)候就要新增一個(gè)變量用來(lái)表示客戶(hù)端估算出來(lái)的服務(wù)器幀(RemoteEstimatedFrame).
這個(gè)估算幀用來(lái)表示客戶(hù)端在本地估測(cè)服務(wù)器模擬的幀號(hào),它的第一次賦值應(yīng)該是客戶(hù)端收到服務(wù)器的幀號(hào)時(shí),

// 調(diào)整遠(yuǎn)程估算幀
public void AdjustRemoteEstimatedFrame()
{
    if (packetsReceived == 1)
        remoteEstimatedFrame = remoteActualFrame;                    //當(dāng)收到第一個(gè)包時(shí),將包的幀號(hào)賦值給估算幀
}

估算幀也是按照模擬頻率一直累加的,但是估算不一定總是準(zhǔn)的,有時(shí)提前收到包,有時(shí)延遲收到包,甚至丟包.所以如果收到的包幀號(hào)跟估算幀相差太大的時(shí)候,就需要對(duì)估算幀重新調(diào)整

public void AdjustRemoteEstimatedFrame()
{
    if (packetsReceived == 1)
        remoteEstimatedFrame = remoteActualFrame;          //當(dāng)收到第一個(gè)包時(shí),將包的幀號(hào)賦值給估算幀
    else
    {
        remoteDiffFrame = remoteActualFrame - remoteEstimatedFrame;// 差異=實(shí)際收到的幀號(hào)-估算幀
        if (remoteDiffFrame < minDiff || (remoteDiffFrame > maxDiff)        //如果差異太大的話,估算幀就要重新賦值
        {
            remoteEstimatedFrame = remoteActualFrame;
        }
    }
}

效果如下:

server 1.gif

從這個(gè)圖可以看出,服務(wù)器移動(dòng)很平滑,但是客戶(hù)端移動(dòng)可以明顯看出抖動(dòng)的情況,問(wèn)題在哪呢?其實(shí)問(wèn)題是出在估算幀的設(shè)置問(wèn)題,從狀態(tài)A插值到狀態(tài)B的過(guò)程,由于估算幀等于(或者接近)狀態(tài)A的幀號(hào),而狀態(tài)B的包客戶(hù)端還沒(méi)有收到,這就造成了在狀態(tài)B到來(lái)之前,客戶(hù)端沒(méi)辦法插值,只好原地等待,當(dāng)狀態(tài)B的包到來(lái)的時(shí)候,立即設(shè)置了位置,所以造成了抖動(dòng),那么如何解決這個(gè)問(wèn)題呢?
做法是故意讓估算幀的幀號(hào)在實(shí)際的狀態(tài)包幀號(hào)之前,讓客戶(hù)端滯后:

public void AdjustRemoteEstimatedFrame()
{
    if (packetsReceived == 1)
        remoteEstimatedFrame = remoteActualFrame - delay;          //當(dāng)收到第一個(gè)包時(shí),估算幀 = 包幀號(hào) - 延遲
    else
    {
        remoteDiffFrame = remoteActualFrame - remoteEstimatedFrame;// 差異=實(shí)際收到的幀號(hào)-估算幀
        if (remoteDiffFrame < minDiff || (remoteDiffFrame > maxDiff)        //如果差異太大的話,估算幀就要重新賦值
        {
            remoteEstimatedFrame = remoteActualFrame - delay;
        }
    }
}

delay = 10(因?yàn)榉?wù)器每10幀發(fā)個(gè)包)這樣盡可能的預(yù)留出一個(gè)狀態(tài)包用來(lái)做插值計(jì)算了,看看效果:

server 2.gif

可以看到客戶(hù)端的抖動(dòng)幾乎看不出來(lái)了,但是代價(jià)是延遲比較大了(為了更好的表現(xiàn),這個(gè)犧牲是必要的)

3.小結(jié)

服務(wù)端模擬結(jié)果,下發(fā)狀態(tài)給客戶(hù)端基本就完成了,需要補(bǔ)充的是,在估算幀的計(jì)算中,可以根據(jù)估算幀和實(shí)際幀的差距動(dòng)態(tài)的調(diào)整本地模擬的頻率,比如:

如果估算幀滯后太多了,那客戶(hù)端就每幀加2,甚至加3(默認(rèn)是每個(gè)模擬幀加1)來(lái)追趕.
如果估算幀超前很多,那客戶(hù)端就估算幀的累加可以暫停來(lái)等待,通過(guò)這樣的方式來(lái)緩和.

現(xiàn)在客戶(hù)端通過(guò)插值,實(shí)現(xiàn)了比較平滑的表現(xiàn),但是有比較明顯的延遲,這個(gè)可以通過(guò)加大發(fā)包的頻率來(lái)緩解這個(gè)問(wèn)題.
后續(xù)實(shí)現(xiàn)了客戶(hù)端的預(yù)表現(xiàn)后,這個(gè)問(wèn)題也就不那么重要了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恩溅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宿接,老刑警劉巖荔仁,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牡属,死亡現(xiàn)場(chǎng)離奇詭異宅倒,居然都是意外死亡囤锉,警方通過(guò)查閱死者的電腦和手機(jī)以故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)蜗细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人怒详,你說(shuō)我怎么就攤上這事炉媒。” “怎么了昆烁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵吊骤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我静尼,道長(zhǎng)白粉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任茅郎,我火速辦了婚禮蜗元,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘系冗。我一直安慰自己奕扣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布掌敬。 她就那樣靜靜地躺著惯豆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奔害。 梳的紋絲不亂的頭發(fā)上楷兽,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音华临,去河邊找鬼芯杀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揭厚。 我是一名探鬼主播却特,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筛圆!你這毒婦竟也來(lái)了裂明?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤太援,失蹤者是張志新(化名)和其女友劉穎闽晦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體提岔,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仙蛉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碱蒙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捅儒。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖振亮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞭莽,我是刑警寧澤坊秸,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站澎怒,受9級(jí)特大地震影響褒搔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喷面,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一星瘾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惧辈,春花似錦琳状、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至边翁,卻和暖如春翎承,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背符匾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工叨咖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓甸各,卻偏偏與公主長(zhǎng)得像垛贤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痴晦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理南吮,服務(wù)發(fā)現(xiàn),斷路器誊酌,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 1.啃玉米棒子部凑,小寶用手掰了粒玉米,說(shuō):“我掉了一顆牙碧浊⊥垦” 2.給小寶穿襪子時(shí),她說(shuō):“我說(shuō)停就停箱锐”让悖”穿到一半,喊...
    Jamesyanyb閱讀 172評(píng)論 0 0
  • 何謂死亡驹止?” “枯藤浩聋,老樹(shù),昏鴉臊恋∫陆啵” “可否具體?” “古道抖仅,西風(fēng)坊夫,瘦馬〕仿” “可否再具體环凿?” “萬(wàn)物皆永恒,卿不在”
    燕小乙乙閱讀 348評(píng)論 0 0
  • 所需工具:windows電腦就足夠了放吩。當(dāng)前使用的移動(dòng)盒子型號(hào):中興B860AV2.1智听。目前來(lái)說(shuō)盒子的破解大致分為2...
    iosRn閱讀 84,202評(píng)論 1 1
  • 你來(lái)時(shí),像一顆花生屎慢。光禿禿的身體瞭稼,蜷縮著,掙扎著腻惠,不知是欣喜還是控訴环肘。那一天,2010年4月22日集灌。 你成長(zhǎng)的時(shí)候...
    懿冉臻閱讀 413評(píng)論 0 1