關於Unity的網絡框架


  注:Unity 5.1里引入了新的網絡框架,未來目標應該是WOW那樣的,現在還只是個P2P的架子。

  網絡的框架,無非是如何管理網絡數據的收發,通信雙方如何約定協議。之前做的框架與GameObject無關,網絡是看不到GameObject這一層的,看到的是事務,比如例如進入場景等。在Unity里,GameObject自己可以有網絡數據的處理能力,網絡行為基於GameObject搭建。最大的不同是客戶端服務器運行的是同一套機制,服務器上有客戶端有的所有東西。uLink相比Unity自己那套東西來說,實現了更多的功能。這里打算對這類的網絡進行下分析。

  Unity規定,網絡行為只能有RPC和狀態同步兩種機制,非此即彼。Unity的networkview組件實現了這種機制,從Networkview的編輯界面上看,會有疑惑,因為看起來networkview監聽的是一個component,那是不是networkview監聽的是腳本里的RPC和狀態同步數據呢,答案是非也。If you are not directly sending data, just using RPC calls, then you can turn off synchronization (no data directly sent) and nothing needs to be set as the Observed property.這句話的意思就是說,networkview里設置observed component,只是為狀態同步的。那么RPC機制是如何做的?RPC calls just need a single network view present so you don’t need to add a view specifically for RPC if a view is already present.意思就是說RPC調用不需要專門給gameobject加一個netview,只要有一個就行了,也許是某個用來做狀態同步的Netview。

If you want to use Remote Procedure Calls in your script all you need is a NetworkView component present in the same GameObject the script is attached to. The NetworkView can be used for something else, or in case it’s only used for sending RPCs it can have no script observed and state synchronization turned off.

由於Unity是C/S同構,同一個GameObject存在於C和S上,C可以直接調用

workView.RPC ("PlayerFire", RPCMode.All);

同時,必須要提供一個帶[RPC]表示的函數

    [RPC]
    void PlayerFire () {
    }

這樣,收到RPC調用的端,被告知說有一個GameObject上id為xxx的netview,其component里有一個名稱是PlayerFire的RPC,你去找到它,並執行。

對於viewID,Unity這種可以通過控制實例化GameObject,來確保gameobject在網絡上的同時存在,從而可以確保同一個gameobject,他的viewID相同。uLink里的實現是The viewID is used internally in uLink to identify the sender and target game object.說明viewID和gameobject是一一對應的。關於ID的分配方式一部分靜態,雙方約定;一部分是動態,由服務器分配。uLink是這么解釋手動分配ID,即靜態的ID:The other option is to assign manual viewIDs to game objects in the scene.Make sure you assign the same viewID to the game object in the client scene and the corresponding game object in the server scene. This is commonly used for static objects like chat windows, doors, elevators and trigger zones.

關於RPC到底是監聽一個腳本還是一個GameObject?uLink里有這么一句話:The RPC calling code will use the attached uLinkNetworkView component to invoke all PrintText() functions in any scripts attached to this same GameObject on all other clients (and the server) if this code is executed on one of the clients.所以至少uLink是會掃描整個GameObject的。

uLink里不推薦在一個gameobject上使用多個netview,目的有兩個,一是為了降低出bug的風險,另外是簡化組件結構,便於維護。要監聽多個component,它提供了一個observerList。

uLink里怎么做狀態同步呢?其增加了一個observerList的組件,可以往里面放置一組component,然后會為每個component生成一個處理器。當收到一個Netview id的同步請求后,ulink找到這個netview id,調用對應的netview的observerList里的托管函數,把bitstream傳進去,注意這個bitstream,他是順序放置數據的,例如有三條數據,那么這個流里面是順序擺放,然后逐個解析。

  為了搞明白一些細節,這里分析兩個demo,一個是LerpzAuthServer,另一個是snowbox。

  LerpzAuthServer這個例子是為了演示權威服務器,實現了角色之間的同步,聊天。此工程下有兩個場景,可以理解為導出兩個不同的exe程序。一個玩家登錄后,會存在三個概念,ower,proxy,creator,分別代表自己,自己在其他玩家機器上的鏡像,自己在服務器上的鏡像。他們的區別在於,通過對應腳本的加減實現了不同的功能組合,ower支持操作控制,可以播放動畫;creator不支持操作控制,也不播放動畫;proxy是支持播放動畫,但不支持操作控制。共同點在於,都有networkview腳本,

從場景結構上看,客戶端與服務器的區別是兩個GameObject節點,SpawnPoint和StartServer,前一個節點上掛了AuthSpawnPrefab腳本,此腳本重寫了uLink_OnPlayerConnected方法,當一個玩家連接成功后,執行這個方法,這里執行的就是實例化玩家。這個過程在權威服務器上只能由服務器來做。StartServer節點上掛載了一個uLinkSimpleServer腳本,啟動時會執行uLink.Network.InitializeServer方法初始化服務器,配置的參數是監聽的端口號和最大的連接數。在socket初始化結束后,將調用uLink_OnServerInitialized方法。玩家斷開連接后,將執行uLink_OnPlayerDisconnected方法,這里將執行對斷開連接玩家的清理工作

uLink.Network.DestroyPlayerObjects(player);
uLink.Network.RemoveRPCs(player);

關於狀態同步,即角色位置的同步,有兩種情況,一種是玩家自身和服務器的位置同步,另一種是和玩家鏡像的同步。

實例化出來的鏡像ower,proxy,creator都會有一個腳本uLinkSmoothCharacter,其中調用了InvokeRepeating方法實現定期調用SendToServer這個方法,此方法將調用move的RPC方法,服務器執行這個move方法實現了服務器上角色位置的更新(權威服務器),實現了自己和服務器的狀態同步。

該腳本里面重寫了uLink_OnSerializeNetworkView方法實現了狀態同步,主要同步了位置,速度,朝向三個變量。服務器將定時執行這個方法發送移動了的玩家位置等信息,代理對象收到這個信息后,移動代理的位置,從而實現了對所有代理對象(proxy)的資料更新。

  snowbox工程,實現有客戶端預測,插值,權威服務器,大廳服務器,玩家切換服務器等。

  在角色的位移同步上,ower,proxy,creator上都掛了一個uLinkStrictPlatformer腳本,為了實現平滑的移動,此腳本里會對鏡像的行為做修正和模擬,鏡像會存儲最近一組的玩家的位置,朝向等信息,通過這組信息進行插值計算當前鏡像的位置。如果長時間沒受到數據更新,鏡像會根據最近一次鏡像的狀態數據模擬鏡像的下一個位置和朝向。

  服務器將模擬所有的物理行為包括重力,並檢查角色是否有作弊行為。角色移動時將發送一個rpc,如果檢測到玩家作弊,服務器會給客戶端一個修正命令。如果玩家行為正常,就告知客戶端,玩家行為正常。服務器收到角色位置,速度,旋轉值,計算出一個位移值,並把玩家移動的位置存儲起來,在lateupdate里,對玩家實際的目標點嘗試進行修正。

  關於仍雪球。當owner有鼠標點擊行為后,將調用服務器的throw 這個RPC方法,告知服務器雪球的方向,服務器收到這個行為后,將實例化雪球並模擬仍雪球的行為,並廣播給其他玩家。關於雪球實體,玩家觸發雪球后,前后端都會實例化雪球,都掛載有Snowball腳本,利用膠囊體碰撞實現被擊效果,區別是客戶端(owner和Proxy)會掛一個TimedTrailRenderer這樣的渲染相關的腳本來實現雪球拖尾的效果。這個意思就是,服務器上的gameobject可以不加載哪些不需要進行判定的行為。

  關於切換服務器(非切場景)。支持creator切換到另外一個進程,由另外一個進程的服務器管理。uLink的P2P更多的是在服務器端的架構,不需要服務器端再維護一個中央服務器來協調各個服務器之間的資源(C/S模型)。uLink文檔上說更多的是適用於服務器端的局域網直連。沒看到說客戶端在不同局域網之間的連接,所以默認它不具備打洞的功能。

  建立P2P,給serverA 的一個gameobject上掛載一個uLinkNetworkP2P腳本,表示此server可以接受別的server的連接,給serverB上的一個GameObject上掛載一個uLinkNetworkP2PConnector腳本,同時將自動加上uLinkNetworkP2P腳本,他的邏輯是說,我要連接別人,那我也一定要能接受別人連我。服務器上執行的creator,在場景之間的切換,只需要調用一個HandoverPlayerObjects方法就可以。

   uLink里有個uLobby的概念,游戲大廳,從功能上看處理用戶的連接,然后找一台合適的服務器把玩家扔進去,支持P2P方式的連接,即玩家也可以開房間,讓其他玩家加入,這樣看起來暗黑3里也有這樣的,比如開一個公共游戲,然后邀請別人加入進來,從一定意義上看就是網關。uLobby是運行在一個單獨的U3D工程下,可以讓其他的client連接。

   uLink里的uZone,如下圖:

這張圖里有幾個關鍵的概念

uZone Master是uZone network系統里的核心,當node連接到uZone network上之后,master將負責跟蹤這些node信息,master也會管理uZone Client的連接。

上圖里的node是個應用程序,保存node到master的session, 負責管理Instance,所謂Instance都可以認為是進程。node和node之間互相獨立,node同時只能連接一個Master。一般情況,每台機器上只會有一個Node,一個uZone里會有多個Node。這里的Client很不一樣,只有使用了uZone client api的才能稱之為uZone client,這種client一般都是和uLobby合作的某種模式的服務器,當然也有別的,像負載均衡,分布式計算,分布式測試或者監控。

client請求master啟動一個功能,master將會通知相應的node啟動一個instance,也就是process(進程)。正常情況下一個instance就是一個進程。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM