title: 2020游戲開發入門-05(游戲邏輯,主要是狀態同步和幀同步)
date: 2020-05-31 23:09:24
tags:
- 游戲開發
- Unity3D
- Python
- 服務端
categories: 游戲開發
目錄
概述
-
客戶端項目地址:DTSGameClient
-
服務端項目地址:DTSGameServer
Unity3D + C# +Python 2.7 。服務端框架都是自己寫的。啥第三方庫都沒有。資源文件太大。客戶端項目里面是Assest/script
文件夾下面的代碼。完整項目在里面有個雲盤鏈接。
在windows下直接打開客戶端。如果有python環境(我測試的時候是py 2.7。理論上3也可以只是我沒全面測試)也可以跑起來服務端。然后就可以登入進去玩了。
玩法大概就是登入后在一個匹配房間。點匹配會在服務端的匹配列表里面。人夠了就一起丟到一個場景。按吃雞的規則最后一個活下來的勝利。
ps: 初學者寫的框架。python 2.7 寫的。性能不好。僅供學習使用。
游戲邏輯
仔細想想游戲邏輯其實很多也很雜。基本沒什么需要特別講的。無非就是模塊划分。就講幾個我寫代碼的時候的坑點吧。
推薦一本書:
書名:游戲編程模式作者:[美]Robert Nystrom 尼斯卓姆
主要講設計模式在游戲方面的應用。
我的角色行為狀態機模式就是參考里面的算法。
Update函數里面的輸入。前進后退什么的。壓成二進制int的某一位。然后作為狀態機的輸入。
然后按狀態機寫個狀態機順帶維護下動畫系統就好了。
還有就是注意不要在Update里面new對象。大量new對象會很影響性能的
服務端那邊用共享內存。運行時維護下玩家數據。房間數據好像也沒啥了。
然后主要是同步算法了。
游戲同步算法
游戲同步算法保證的是多客戶端的表現一致。
比如在我搞的這個吃雞Demo里面。需要同步的就是玩家所在的位置(Transform屬性)。角色做動作,射擊,蹲下,跳躍,所有客戶端都要反饋。A玩家打了B文件一槍扣血要能正確計算。
游戲同步算法主要分狀態同步和幀同步。
我的理解:
狀態同步同步狀態。幀同步同步操作。
玩家A從P1點向前走到P2點。
狀態同步需要直接告訴服務端玩家A現在在P2點了。然后所有客戶端都知道A在P2點了。
幀同步需要告訴客戶端玩家A向前走這個操作。然后服務端廣播操作。所有服務端都知道玩家A向前走了。
下面是一些游戲用到的同步算法。
到底用那種同步和游戲有關。但是也不絕對。 比如星際爭霸的RTS。如果服務端維護所有控制單位的位置。那網絡帶寬就不夠了。如果用幀同步,同步鼠標從哪里拉到哪里。點了哪里。需要同步的數據就少很多。
同步頻率:我寫的時候是15個包。也就是66ms一個包。我了解了下大概一秒10~15個包把。這個要自己測。我感覺差不多。
寫累了,把我畢設論文粘過來了。。。o(︶︿︶)o
同步算法的存在是為了減少網絡波動對於玩家游戲體驗的影響,在對實時性有着較高要求的游戲中有着較大的影響。在同步算法的划分上主要分為兩種。一種是狀態同步,另外一種是幀同步。
狀態同步,指的是客戶端向服務端上報玩家的狀態,可能是血量,空間坐標等信息。這些信息可能由於玩家操作而改變,但是客戶端只對最終的狀態進行上報。由服務端對所有玩家的數據進行維護,計算。在同步給所有的客戶端。計算量大部分在服務端。
幀同步,指的是客戶端上報玩家操作,稱為邏輯幀。服務端收集所有客戶端在某一幀的操作。然后廣播給所有客戶端。只要所有客戶端接收的指令相同,那么所有玩家的表現就是一致的。其中大部分的計算量落在了客戶端。只要保證在不同客戶端環境下接受相同的指令。最終的計算結果一致即可。因此一般需要做到以下幾點。
多客戶端在使用隨機數之前,需要同步相同的隨機種子。
保證不同客戶端物理引擎的計算結果一致。Unity3D的物理引擎是英偉達公司的Phys X,是無法保證這一點的。
不能因為某些無序容器的不一致導致結果不一致。比如哈希表在不同客戶端的某些算法下哈希碼的計算差異性。
浮點數運算本身就存在一定的誤差。需要實現定點浮點數。
以上是對於狀態同步算法和幀同步算法的概述。但是實際的同步算法一定是和業務息息相關的。下面開始接受本系統使用的同步算法與變化。由於以上四個條件客戶端無法全部滿足,本系統不適用完全的幀同步算法。
首先是渲染幀和邏輯幀的概念上的不同。本文渲染幀指的是客戶端的畫面在渲染上更新的頻率。更新率越高畫面就越流暢。比如60FPS,表示每秒更新60次畫面。但是我們的網絡數據包不能以這么高的頻率更新。於是一些地方提出了邏輯幀的概念。比如每秒發10個消息。那么兩次發包的時間間隔就是100毫秒。這個間隔與網絡狀況和網絡類型,以及服務器實現都有着關系。本系統經過測試將使用66毫秒作為發包的間隔。也就是每秒15個數據包。
在幀同步類中,維護着兩個字典。放在一個大小為2的列表中。當前幀指針值為0或1,通過與1異或來切換。當前邏輯幀接受所有用戶上報的操作,當操作接收滿后當前幀指針異或1。同時服務器維護的邏輯幀增加。當前幀變為上一幀。收集完全的所有操作現在允許被所有用戶查詢。
注意只有當服務端收到所有客戶端某一幀的操作時才運行當前幀的查詢。只有在客戶端查詢到了某一幀后才會進入下一幀的上報。而在一幀的上報未完成之前,服務端就不會允許客戶端的查詢操作。客戶端處於哪一邏輯幀會在與服務端的不斷通信中被修正。上報快的客戶端的操作會被忽略。而上報慢的客戶端,則會引起所有客戶端的等待,因此客戶端沒有操作時也需要發一個沒有操作的包。但是當客戶端卡頓時,包沒有發送出去,還是會出現卡頓現象。這確實是一個幀同步算法本生存在的一個缺點。
總體來說,上述算法有兩個可能存在的問題。
一個是忽略用戶某些操作幀是否會給玩家帶來影響。答案是影響很小,因為邏輯幀的頻率是66毫秒。具體體現為在這66毫秒內玩家沒有操作。但是需要對一些操作做插值平滑化處理。
第二個客戶端卡頓造成的影響,當某一個客戶端遲遲沒有發送數據包時,所有的玩家都會停止等待。運行較快的客戶端操作會被忽略,而等待卡頓玩家上報操作。
對於卡頓帶來的影響,本系統通過以下幾個手段進行了優化。
使用心跳包將卡頓玩家踢出房間。客戶端需要每隔一段時間上報自己任然在線。當在誤差范圍時間限制之外,且沒收到某個客戶端消息時,將其踢出房間。清空所有內存中的用戶數據。戰斗系統將不再等待該玩家。當客戶端再次連接上來后。發現服務端將自己踢出房間。則顯示網絡連接中斷。
不使用完全的幀同步,在角色Transform屬性選擇直接廣播給所有用戶。由於客戶端物理引擎無法保證多客戶端計算結果的一致性,也不能使用完全的幀同步。玩家的操作,影響角色行為狀態機。直接影響角色移動。而其他玩家同步其操作后,只表現角色動畫效果,不影響位置。位置信息直接通過獲取Transform屬性來修改。這樣,在其他玩家卡頓時,當前玩家的操作依舊能夠直接執行。卡頓的客戶端在偶然的網絡波動后,會重新開始同步數據。
到此,我們的同步算法已經成型。每個玩家在加入游戲的時候,通過上文提到的游戲控制器。請求服務端分配游戲房間。服務端以每一個游戲房間為單位。划分戰斗系統。同步玩家數據。客戶端通過一定頻率發送數據包。主要為玩家操作數據包,和玩家位置信息數據包。服務端收集數據,並允許客戶端查詢。
客戶端以Json格式上報位置的請求參數如下。
{
"position": "0.00;0.00;0.00",
"rotation": "0.00;0.00;0.00",
"user_id": "1",
"time": "0"
}
其中position, rotation是Unity3D GameObject 中的Transform 組件的數據。由客戶端保留兩位小數后,帶上角色信息user_id。不斷更新服務端所存儲的角色位置。
用戶同步的操作信息,請求響應數據包如下所示。
請求數據包: {"action": 10, "frame": 0, "user_id": "1"}
響應數據包: {"frame": 0, "err_msg": "", "ret": 0}
在請求數據包中。ret和err_msg參數表示請求是否出現未知錯誤。frame表示發送的操作發生在哪一邏輯幀。響應數據包的frame數值為服務端所在的邏輯幀值。用於修正中途加入的玩家的所在邏輯幀。
action是一個整形值。用戶每一幀操作,經過上文的用戶角色狀態機中提到的壓縮算法。將用戶行為壓縮到一個actionSign的客戶端整形變量。在網絡傳輸階段以數據包的action值表現。
我開始寫同步的時候。
玩家在加載出來的時候,當前玩家會掛一個腳本,按66ms的數據向服務端上報 transform的position, rotation。
如果是其他玩家角色。會掛在另一個腳本。按照66ms的數據查transform。
這樣其實就能移動了。不過只能動。。。
然后兩個思路。
按狀態同步。應該是比如A點走到了B點。我拿到的是先后兩個坐標。然后我再去算他移動的方向。去驅動他的動畫系統???高度變高了播放下跳躍動畫。
好像有點麻煩。,,誒
然后我就按幀同步寫了。主要是試一試。
在Update函數里面:
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
四個值傳給服務端。然后WASD,空格,蹲之類的行為壓成一個int。省帶寬。一樣傳給客戶端。
客戶端維護一個第幾幀,服務端也有一個第幾幀。
所有客戶端在第一幀做的操作要全部告訴服務端后。服務端才允許客戶端上報下一幀。太快的客戶端報快了怎么辦。服務端要忽略。另一個是客戶端同一幀可以控制下別上報兩次。
而服務端查到所有客戶端第n幀上報完后。客戶端才會做第n幀的事情。然后把第n+1幀的操作上報。客戶端是不會跳幀上報的。
客戶端在上報第n幀的時候,客戶端另一個接口一直在查其他客戶端第n幀的操作。但是如果所有客戶端沒上報完。服務端不讓他查。就查失敗了,就一直查。直到成功。成功后,服務端告訴它下一幀是那一幀(而不是客戶端自己幀數加一,這樣也解決了中途加入游戲的問題了)
另外的問題的卡頓。萬一某個客戶端卡了一下,卡0.5秒都很明顯的。還有就是鼠標視角變換如果是服務端同步完才響應影響會很明顯。
所以我又改了一點。當前玩家的操作直接影響角色狀態機。反正最終角色的坐標是直接同步位置的。這樣某個玩家卡了,對當前玩家沒影響。只是它眼中的其他玩家全卡了一下而已。另外,卡太久的玩家可以直接踢下線。用心跳包機制。