在競技類網(wǎng)絡(luò)游戲比較火刹枉,市面上也出現(xiàn)了很多這種類型的游戲競賽,提到網(wǎng)絡(luò)游戲就回避不了一個問題:同步技術(shù)岩饼,多個人在一個游戲場景圍攻一個怪物或者說多人組隊(duì)?wèi)?zhàn)斗等等氓辣。
狀態(tài)同步
現(xiàn)在在移動端的游戲由于帶寬的限制,一般采用實(shí)時(shí)同步的方式是狀態(tài)同步账蓉,也就是說角色的狀態(tài)發(fā)生改變枚碗,才會去發(fā)送消息。舉個例子:
3D角色一般的動作狀態(tài)有:Idle铸本,walk肮雨,run,attack等箱玷,玩家操作鍵盤或者觸摸屏按鈕怨规,會觸發(fā)這些動作,一個游戲場景中會有多個角色锡足,每個角色都有自己的動作狀態(tài)波丰,為了讓玩家能夠看到其他玩家在做什么,需要同步舶得,玩家默認(rèn)狀態(tài)是idle掰烟,玩家剛出現(xiàn)時(shí)是idle狀態(tài),這個時(shí)候,客戶端會把玩家的狀態(tài)纫骑,位置蝎亚,方向傳送給服務(wù)器,其他玩家也是一樣的先馆,服務(wù)器接收到信息后颖对,會把這些信息發(fā)送給除了它本人之外的其它玩家,這樣我們就可以看到其他玩家的狀態(tài)了磨隘。如果玩家從idle狀態(tài)轉(zhuǎn)化到walk狀態(tài)缤底,這表明玩家的動作狀態(tài)發(fā)生了變化,這也需要將信息發(fā)給服務(wù)器番捂,服務(wù)器進(jìn)行群發(fā)給其他玩家个唧,這樣其他玩家就可以看到角色開始walk了。接下來如果玩家繼續(xù)走设预,客戶端就不發(fā)送消息給服務(wù)器了徙歼,因?yàn)闋顟B(tài)沒發(fā)生變化,等狀態(tài)再變化時(shí)才會發(fā)送消息給服務(wù)器鳖枕,然后服務(wù)器再群發(fā)消息魄梯,在此過程中,其他客戶端會通過插值的方式把兩個狀態(tài)之間的距離實(shí)現(xiàn)出來宾符,以此類推酿秸。。魏烫。辣苏。。哄褒。這就是所說的狀態(tài)同步模式稀蟋。
幀同步模式
幀同步含義游戲客戶端接受來自網(wǎng)絡(luò)的多個客戶端的操作,如果這些操作在各個客戶端是一樣的呐赡,那么多個客戶端的顯示也就一樣了退客,這就帶來了“同步”的效果。所以在這種情況下链嘀,各個客戶端的運(yùn)算要絕對一致萌狂,不能依賴諸如本地時(shí)間、本地隨機(jī)數(shù)等等“輸入”管闷,而要一切以網(wǎng)絡(luò)來的操作數(shù)據(jù)為主粥脚。
一般來說,大多數(shù)的游戲客戶端引擎包个,都會定時(shí)調(diào)用一個接口函數(shù)刷允,這個函數(shù)由用戶填寫內(nèi)容冤留,用來修改和控制游戲中各種需要顯示的內(nèi)容。比如在在Unity里面叫Update()树灶,這類函數(shù)通常會在每幀畫面渲染前調(diào)用纤怒,當(dāng)用戶修改了游戲中的各個角色的位置、大小后天通,就在下一幀畫面中顯示出來泊窘。而在幀同步的游戲中,這個Update()函數(shù)依然是存在像寒,只不過里面大部分的內(nèi)容烘豹,需要挪到另外一個類似的函數(shù)中,我們可以稱之為UpdateNet()函數(shù)——由網(wǎng)絡(luò)層不斷的接收服務(wù)器發(fā)來的“網(wǎng)絡(luò)幀”數(shù)據(jù)包诺祸,每收到一個這樣的數(shù)據(jù)包携悯,就調(diào)用一次這個UpdateNet()函數(shù),這樣游戲就從通過本地CPU的Update()函數(shù)的驅(qū)動筷笨,改為根據(jù)網(wǎng)絡(luò)來的UpdateNet()函數(shù)驅(qū)動了憔鬼。顯然,網(wǎng)絡(luò)發(fā)過來的同步幀速度會明顯比本地CPU要慢的多胃夏,這里就對我們的游戲邏輯開發(fā)提出了更高的要求——如何同步的同時(shí)轴或,還能保證流暢?
實(shí)現(xiàn)UpdateNet函數(shù)內(nèi)容仰禀,其實(shí)就是定義一個堆棧用于存放網(wǎng)絡(luò)發(fā)過來的消息照雁,通過幀監(jiān)測將其數(shù)據(jù)拿出來使用,因?yàn)閁pdate函數(shù)明顯比UpdateNet快的多悼瘾,這就需要我們定義一個時(shí)間間隔用于消息的發(fā)送囊榜,比如50毫秒或者100毫米等审胸。
private float AccumilatedTime = 0f;
private float FrameLength = 0.05f; //50 miliseconds
//called once per unity frame
public void Update()
{
//Basically same logic as FixedUpdate, but we can scale it by adjusting FrameLength
AccumilatedTime = AccumilatedTime + Time.deltaTime;
//in case the FPS is too slow, we may need to update the game multiple times a frame
while (AccumilatedTime > FrameLength)
{
GameFrameTurn();
AccumilatedTime = AccumilatedTime - FrameLength;
}
}
private void GameFrameTurn()
{
//first frame is used to process actions
if (GameFrame == 0)
{
if (LockStepTurn())
{
GameFrame++;
}
}
else
{
//update game
SceneManager.Manager.TwoDPhysics.Update(GameFramesPerSecond);
List<IHasGameFrame> finished = new List<IHasGameFrame>();
foreach (IHasGameFrame obj in SceneManager.Manager.GameFrameObjects)
{
obj.GameFrameTurn(GameFramesPerSecond);
if (obj.Finished)
{
finished.Add(obj);
}
}
foreach (IHasGameFrame obj in finished)
{
SceneManager.Manager.GameFrameObjects.Remove(obj);
}
GameFrame++;
if (GameFrame == GameFramesPerLocksetpTurn)
{
GameFrame = 0;
}
}
}
幀同步游戲中亥宿,由于需要“每一幀”都要廣播數(shù)據(jù),所以廣播的頻率非常高砂沛,這就要求每次廣播的數(shù)據(jù)要足夠的小烫扼。最好每一個網(wǎng)絡(luò)幀,能在一個MTU以下碍庵,這樣才能有效降低底層網(wǎng)絡(luò)的延遲映企。同樣的理由,我們?yōu)榱颂岣邔?shí)時(shí)性静浴,一般也傾向于使用UDP而不是TCP協(xié)議堰氓,這樣底層的處理會更高效。但是苹享,這樣也會帶來了丟包双絮、亂序的可能性。因此我們常常會以冗余的方式——比如每個幀數(shù)據(jù)包,實(shí)際上是包含了過去2幀的數(shù)據(jù)囤攀,也就是每次發(fā)3幀的數(shù)據(jù)软免,來對抗丟包。也就是說三個包里面只要有一個包沒丟焚挠,就不影響游戲膏萧。
幀同步實(shí)現(xiàn)的過程有個很重要的地方就是邏輯層和表現(xiàn)層一定要分開,表現(xiàn)層先行蝌衔,邏輯層等發(fā)到服務(wù)端的指令再處理榛泛。幀與幀之間的播放頻率,則由服務(wù)器統(tǒng)一控制噩斟,但由于網(wǎng)絡(luò)抖動等影響挟鸠,幀的頻率并不是太穩(wěn)定,為避免播放抖動亩冬,幀數(shù)控制器需要進(jìn)行一定的平滑處理艘希。
網(wǎng)絡(luò)抖動的產(chǎn)生原因:在網(wǎng)絡(luò)游戲中,各個客戶端的運(yùn)行條件和環(huán)境往往千差萬別硅急,有的硬件好一些覆享,有的差一些,各方的網(wǎng)絡(luò)情況也不一致营袜;時(shí)不時(shí)玩家的網(wǎng)絡(luò)還會在游戲過程中撒顿,發(fā)生臨時(shí)的擁堵,我們稱之為“網(wǎng)絡(luò)抖動”荚板》锉冢可能導(dǎo)致客戶端收到“過去時(shí)間”里的一堆網(wǎng)絡(luò)幀,客戶端需要拿出一定的時(shí)間去處理這些堆積的網(wǎng)絡(luò)幀跪另,因此拧抖,客戶端必須要有處理這些堆積起來的網(wǎng)絡(luò)數(shù)據(jù)的能力。
實(shí)時(shí)同步游戲最重要的是流暢免绿,然而影響游戲流暢的因素很多唧席,網(wǎng)絡(luò)帶寬的限制,CPU運(yùn)算和渲染效率的限制嘲驾。一般玩家控制的角色的動作淌哟,包括當(dāng)前客戶端控制的角色,還是應(yīng)該從網(wǎng)絡(luò)幀里面獲得行為數(shù)據(jù)辽故,因?yàn)槿绻婕覑劭刂平巧灰恢碌奶嗤讲郑麄€游戲場面就會差更多。很多游戲中的怪物AI都是根據(jù)玩家角色來設(shè)定的誊垢,所以一旦玩家角色的行為是同步的掉弛,那么大多數(shù)的怪物的表現(xiàn)還是一致的喻杈。
幀同步游戲技術(shù),并不存在一種可以讓游戲流暢的通用做法狰晚,而是需要和游戲具體做很多結(jié)合筒饰,在減少數(shù)據(jù)包,優(yōu)化游戲快進(jìn)體驗(yàn)壁晒,控制發(fā)包速度上盡量調(diào)優(yōu)瓷们。同時(shí)還需要和游戲產(chǎn)品策劃一起,平衡一致性秒咐、實(shí)時(shí)性谬晕、公平性的策略,才能真正達(dá)到流暢游戲的目的携取。