用狀態(tài)同步的方式實現(xiàn)一個坦克大戰(zhàn)的小游戲,這也是一次全新的嘗試带欢,從游戲的效果來看运授,在正常的網(wǎng)絡(luò)速度下效果符合預(yù)期。這里跟大家分享下游戲客戶端中用到的關(guān)鍵技術(shù)點乔煞。
一吁朦、 同步方式的選擇,狀態(tài)同步or 幀同步渡贾?
? 狀態(tài)同步: 同步的是游戲中的各種狀態(tài)逗宜,游戲邏輯由服務(wù)器實現(xiàn),只是將計算后的結(jié)果同步給客戶端空骚,客戶端根據(jù)收到的狀態(tài)纺讲,同步本地的游戲狀態(tài)。
? 實現(xiàn)狀態(tài)同步的一般流程是:
? 客戶端上傳操作至服務(wù)器 ====> 服務(wù)器收到后按固定頻率計算游戲行為的結(jié)果囤屹,然后以廣播的形式以固定頻率下發(fā)至客戶端 ====>客戶端根據(jù)收到的狀態(tài)熬甚,渲染游戲畫面
? 值得注意的是,狀態(tài)同步是一種不嚴謹?shù)耐?/strong>肋坚,只是保證了各種狀態(tài)的一致性乡括,并不能保證各客戶端在同一時刻顯示同樣的畫面。比如子彈擊中坦克智厌,那么狀態(tài)同步能保證子彈確實擊中了坦克粟判,但有可能有的客戶端先看到擊中的畫面,有的客戶端后看到峦剔。所以狀態(tài)同步對網(wǎng)絡(luò)的要求不高档礁。還有一點,服務(wù)器實現(xiàn)游戲邏輯的計算吝沫,安全性相對客戶端實現(xiàn)游戲邏輯計算高很多呻澜。
? 幀同步: 同步的是操作指令,指令包含當(dāng)前的幀索引惨险「遥客戶端上傳指令至服務(wù)器,服務(wù)器通過廣播下發(fā)至每個客戶端辫愉, 客戶端再根據(jù)指令栅受,本地計算游戲邏輯并進行渲染。幀同步確保服務(wù)器每幀下發(fā)的指令是一致的。如果出現(xiàn)網(wǎng)絡(luò)不好的情況屏镊,導(dǎo)致幀在本地積壓依疼,或者客戶端同時收到多個幀信息,則選擇加快速度渲染而芥,或者直接跳至最新的一幀(不建議律罢,除非斷線重連)。
? 實現(xiàn)幀同步的一般流程是:
? 同步隨機數(shù)種 (用于游戲中暴擊等計算) ====> 客戶端上傳操作指令 ====> 服務(wù)器按固定頻率廣播所有客戶端的操作 ====> 客戶端根據(jù)收到的操作棍丐,進行邏輯運算并渲染游戲畫面
? 嚴格的幀同步每個幀都會等待所有的玩家上傳操作至服務(wù)器后误辑,才會廣播指令至各客戶端,這樣的就能嚴格保證各個玩家的操作是同步的歌逢,但是巾钉,這樣做的壞處也是顯而易見的,這局游戲的網(wǎng)絡(luò)延遲秘案,就是網(wǎng)絡(luò)最差的那個玩家的網(wǎng)絡(luò)延遲睛琳,這樣對網(wǎng)絡(luò)好的玩家顯得不公平,而且游戲的操作手感也不好踏烙。所以延伸出來的樂觀鎖幀同步方式师骗,就是用來保證網(wǎng)絡(luò)好的玩家不受延遲高的玩家的影響。樂觀鎖就是默認在定時器觸發(fā)的那一刻讨惩,所有的玩家操作已經(jīng)上傳辟癌,然后再廣播至各個玩家。對于延遲高的玩家荐捻,會感覺操作延遲黍少,但對于網(wǎng)絡(luò)情況良好的玩家,會感覺很順暢处面。至于說這對于網(wǎng)絡(luò)不好的玩家不公平厂置,只能說網(wǎng)絡(luò)不好就不要玩這種對于網(wǎng)絡(luò)要求高的游戲嘛,當(dāng)然得優(yōu)先保證大多數(shù)玩家的體驗了魂角。
? 對于坦克大戰(zhàn)采用狀態(tài)同步的考量:
? 對于多人在線小游戲來說昵济,其實這兩種同步方式都可以。但考慮到實際應(yīng)用場景野揪,我們還是選擇了狀態(tài)同步访忿。第一、游戲邏輯在服務(wù)端實現(xiàn)斯稳,所以我們更新一款游戲海铆,直接更新服務(wù)端就好了。第二挣惰、對于移動端的網(wǎng)絡(luò)不穩(wěn)定卧斟,所以選擇對網(wǎng)絡(luò)要求稍微低的狀態(tài)同步殴边。
? 對于坦克大戰(zhàn)我們也把一些游戲邏輯交給了客戶端,我們選擇將坦克位置由服務(wù)器廣播至客戶端珍语,而子彈這個不是很關(guān)鍵的信息锤岸,我們是直接將開火指令廣播至客戶端,客戶端直接本地渲染廊酣,直到收到來自服務(wù)器的生命周期結(jié)束的訊息為止(擊中了障礙物)能耻。這部分放到客戶端直接渲染赏枚,是因為像子彈這種類型的元素亡驰,方向跟運動速度不變,除非碰到障礙物消失饿幅。所以凡辱,沒有必要將子彈的信息,也通過服務(wù)端廣播至客戶端栗恩。一個游戲子彈有很多透乾,僅僅附帶位置信息,那也是很占用帶寬的磕秤。其實后續(xù)可以考慮乳乌,子彈生命結(jié)束也由客戶端控制。至于他擊中的物體會怎么樣市咆,這個由服務(wù)端判斷
二汉操、客戶端與服務(wù)器時間的校準(zhǔn)
? 多人對戰(zhàn)游戲,需要一個統(tǒng)一的時間軸來運轉(zhuǎn)整個游戲世界蒙兰。而這個時間軸磷瘤,就是服務(wù)器的時間軸。所以CS之間的時間同步搜变,對整個游戲的運行是至關(guān)重要的采缚。
?
? 服務(wù)端時間 = 客戶端時間 + RTT/2 + difftime
這里t1 為客戶端發(fā)送請求的時間, t2為服務(wù)端收到請求的時間挠他,t3為客戶端收到ack的時間扳抽。則有:
? t3-t1 = RTT
? t1 + RTT/2 + diff = t2
那么根據(jù)以上公式可以算出,客戶端與服務(wù)端的時間差diff.這樣就同步了客戶端與服務(wù)端的時間殖侵。
這里默認客戶端到服務(wù)端的延遲跟服務(wù)端到客戶端的延遲是一樣的摔蓝,可以多做幾次,取平均值愉耙。
?
三贮尉、客戶端的狀態(tài)同步的實現(xiàn)
? 不同于單機游戲,多人對戰(zhàn)游戲需要同步各個客戶端的信息朴沿,因此理論上來說沒法做到像單機游戲那種立即操作立即顯示猜谚“苌埃客戶端的操作需要經(jīng)過服務(wù)端的確認才能顯示。整個過程是這樣的:
? 客戶端上傳至服務(wù)端
? 服務(wù)端廣播至客戶端
? 這里是分開兩個過程魏铅,客戶端的操作不會立即對客戶端渲染產(chǎn)生影響昌犹,可以對比下單機游戲的過程是這樣的:
? 操作 ==> 本地進行游戲邏輯運算 ==> 渲染游戲畫面==>操作 ...
這里的網(wǎng)絡(luò)數(shù)據(jù)包包括 狀態(tài) + 操作 + 服務(wù)端時間, 狀態(tài)包括坦克的位置览芳、方向斜姥、速度, 操作包括坦克開火沧竟、移動铸敏、停止,每發(fā)生一個動作悟泵,就將動作push到隊列里等待發(fā)送杈笔。
? 可以注意到,只有確保服務(wù)端收到一個操作之后糕非,客戶端才會從隊列里取出下一個操作發(fā)送蒙具。如果不用隊列進行緩存,則有可能出現(xiàn)在一個周期內(nèi)發(fā)送多個操作的情況朽肥。
?
四禁筏、客戶端做插值平滑游戲畫面
? 由于服務(wù)端廣播至客戶端的幀為15幀/s, 如果按照這樣的頻率做本地渲染衡招,這樣得到的效果是讓人無法接受的呀打〔肝兀看到的游戲畫面是不連續(xù)的椭懊。也許有人認為視頻25幀/s就已經(jīng)非常流暢了垦缅,那么把服務(wù)端的廣播頻率設(shè)為25幀/s就可以了。其實窘茁,視頻的顯示與游戲的顯示機制是不一樣的怀伦,視頻可以由一幅幅畫連續(xù)播放得到,而這幅畫是由連續(xù)曝光得到山林,所以是一段時間的信息房待,而游戲是直接顯示,就是一個時間點的信息驼抹。所以游戲的幀率太少會讓人覺得很卡桑孩,一頓一頓的。
? 要平滑的顯示游戲畫面框冀,會選擇一個插值的操作流椒。就是基于兩個已知的狀態(tài)做插值,讓本來的兩幀數(shù)據(jù)明也,在渲染的時候宣虾,細分成多幀數(shù)據(jù)惯裕。服務(wù)端廣播幀率為15幀/s, 而本地渲染的是大約60幀/s.我們做個完美的假設(shè)绣硝,假設(shè)收到來自服務(wù)端的數(shù)據(jù)蜻势,倒數(shù)第二幀坦克位置為(X0,Y0); 最新收到的一幀中坦克的位置為(X1,Y1)鹉胖;之間相隔1/15s握玛。由于客戶端幀率是60幀/s, 故理論上可以服務(wù)端的一幀變?yōu)榭蛻舳说乃膸5歉Σぃ瑫l(fā)現(xiàn)由于js是單線程的挠铲,其定時器無法做到完美的定時,js的定時器也是一個坑淑蔚。它每隔dt個時間段市殷,渲染一次游戲畫面愕撰,而dt是小范圍波動的刹衫。所以有:
? 其中 Xnow 是現(xiàn)在需要渲染的x坐標(biāo)位置,ts是兩個消息自帶的時間戳的差值搞挣,基本為66ms, 而dt的累加永遠要小于等于ts,當(dāng)dt的累加大于ts時带迟,說明渲染時間過長,而坦克的x坐標(biāo)已經(jīng)到達X1的位置了囱桨,因此不需要再移動仓犬,故當(dāng)dt的累加大于ts時做無效處理。
五舍肠、過程中可改進的點
1.傳輸?shù)男畔]有進一步壓縮
? 用的都是json格式搀继,而且玩家的數(shù)目與需要廣播的消息是成線性關(guān)系的,所以需要進一步壓縮翠语,才能更好的支持數(shù)量很多的玩家叽躯。也是節(jié)省玩家流量的一個措施。
2.可以提前渲染一至兩幀的圖像
? 目前是完全根據(jù)服務(wù)端的信息來進行渲染肌括,包括自己操縱的那輛坦克点骑。可以通過客戶端預(yù)測和與服務(wù)端的協(xié)調(diào)谍夭,來提前一兩幀顯示自己操縱的那輛坦克的位置黑滴。為何不走另外一個極端,即馬上操作紧索,馬上得到袁辈?因為這不是單機游戲,需要與服務(wù)器保持一致珠漂,只能提前顯示一至兩幀晚缩,也就是最多提前顯示120ms的畫面葛菇,再多的話就有問題,會導(dǎo)致坦克在服務(wù)端的位置與客戶端的位置相差過大橡羞,進一步導(dǎo)致整個游戲時間的不一致眯停。
3.幾乎同時收到服務(wù)端多個幀的數(shù)據(jù)
? 這個問題在mac 本上出現(xiàn)過,抓包發(fā)現(xiàn)本來間隔66ms的幀數(shù)據(jù)卿泽,有時候會只間隔幾毫秒莺债。這樣導(dǎo)致的現(xiàn)象就是,坦克會一頓一頓签夭,出現(xiàn)閃現(xiàn)齐邦。對于該問題偶然出現(xiàn)會有辦法改進,假如短時間內(nèi)收到3個包第租,那么直接丟棄前兩個包措拇。這樣坦克的畫面會突然加快,但總比閃現(xiàn)效果要好慎宾。如果經(jīng)常出現(xiàn)丐吓,則目前是無解的,客戶端沒收到來自服務(wù)端的幀數(shù)據(jù)趟据,是做等待處理的券犁,看起來是閃現(xiàn)的效果。只能通過提高網(wǎng)絡(luò)質(zhì)量或者網(wǎng)卡設(shè)備來避免這個問題汹碱。