狀態(tài)同步
狀態(tài)同步是從服務(wù)器到遠程客戶端完成的。本地客戶端沒有它的串行化數(shù)據(jù),因為它與服務(wù)器共享場景。串行到本地客戶端的任何數(shù)據(jù)都將是冗余的。然而,在本地客戶端上調(diào)用數(shù)據(jù)與SyncVar掛鉤。
數(shù)據(jù)未從遠程客戶端同步到服務(wù)器。這是指令的工作。
SyncVars
SyncVars是從服務(wù)器同步到客戶端的NetworkBehaviour腳本的成員變量。當一個對象被產(chǎn)生或者一個新玩家加入正在進行的游戲時,它們將接收到視域中所有網(wǎng)絡(luò)物體的SyncVars的最新狀態(tài)。在調(diào)用OnStartClient()之前,SyncVars的狀態(tài)應(yīng)用于客戶端上的對象,因此該對象的狀態(tài)保證在OnStartClient()內(nèi)是最新的。SyncVars可以是基本數(shù)據(jù)類型,如整數(shù),字符串和浮點數(shù)。它們也可以是Unity類型,例如Vector3和用戶定義的結(jié)構(gòu),但是對于SyncVars的更新作為單片更新發(fā)送,而不是在結(jié)構(gòu)中的字段更改時的增量更改。在單個NetworkBehaviour腳本中最多可以有32個SyncVars,這包括SyncLists。
當SyncVar的值更改時,服務(wù)器會自動發(fā)送SycnVar更新。沒有必要對SyncVars執(zhí)行任何手動清理字段。
同步列表
SyncLists類似于SyncVars,但它們是值列表,而不是單個值。SyncList內(nèi)容包括在具有SyncVar狀態(tài)的初始狀態(tài)更新中。SyncLists不需要SyncVar屬性,它們是特定的類。對于基本類型,有內(nèi)置的SyncList類型:
? SyncListString
? SyncListFloat
? SyncListInt
? SyncListUInt
? SyncListBool
還有SyncListStruct,可以用于用戶定義的結(jié)構(gòu)列表。使用的結(jié)構(gòu)SyncListStruct派生類可以包含基本類型,數(shù)組和常見Unity類型的成員。它們不能包含復(fù)雜類或通用容器。
SyncLists具有名為Callback的SyncListChanged委托,允許在列表內(nèi)容更改時通知客戶端。將使用發(fā)生的操作類型和操作所針對的項目的索引來調(diào)用此代理。
自定義串行化函數(shù)
通常,使用SyncVars足以讓腳本將其狀態(tài)串行化到客戶端,但在某些情況下需要更復(fù)雜的串行化代碼。開發(fā)人員可以利用NetworkBehaviour上用于SyncVar串行化的虛擬函數(shù)來執(zhí)行自己的自定義串行化。這些功能是:
public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
public virtual void OnDeSerialize(NetworkReader reader, bool initialState);
initialState標志用于區(qū)分對象第一次串行化和可以發(fā)送增量更新時。第一次將對象發(fā)送到客戶端時,它必須包括完整狀態(tài)快照,但后續(xù)更新可以通過僅包括增量更改來節(jié)省帶寬。請注意,當initialState為true時,不調(diào)用SyncVar鉤子函數(shù),僅用于增量更新。
如果一個類有SyncVars,那么這些函數(shù)的實現(xiàn)會自動添加到類中。因此,具有SyncVars的類也不能具有自定義串行化函數(shù)。
OnSerialize函數(shù)應(yīng)返回true時指示發(fā)送更新。如果它返回true,那么該腳本的臟位被設(shè)置為零,如果它返回false,那么臟位不改變。這允許對腳本的多個更改隨時間累積,并在系統(tǒng)就緒時發(fā)送,而不是每個幀。
串行化流
具有NetworkIdentity組件的游戲?qū)ο罂梢跃哂性醋訬etworkBehaviour的多個腳本。串行化這些對象的流程是:
在服務(wù)器上:
? 每個NetworkBehaviour有一個臟面具。此掩碼在OnSerialize中作為syncVarDirtyBits可用
? NetworkBehaviour腳本中的每個SyncVar在臟掩碼中分配一位。
? 更改SyncVars的值會使該SyncVar的位在臟掩碼中設(shè)置
? 或者,調(diào)用SetDirtyBit()直接寫入臟掩碼
? NetworkIdentity對象在服務(wù)器上作為其更新循環(huán)的一部分被檢查
? 如果NetworkIdentity上的任何NetworkBehavours是臟的,則為該對象創(chuàng)建一個UpdateVars包
? 通過在對象上的每個NetworkBehavail上調(diào)用OnSerialize來填充UpdateVars數(shù)據(jù)包
? 沒有臟的NetworkBehavours向它們的臟位寫入一個零
? 臟的NetworkBehavours寫它們的臟掩碼,然后更改SyncVars的值
? 如果OnSerialize對于NetworkBehaviour返回true,則臟掩碼將針對該NetworkBehaviour重置,因此它將不再發(fā)送,直到它的值更改。
? UpdateVars數(shù)據(jù)包發(fā)送到正在觀察對象的就緒客戶端
在客戶端:
? 接收對象的UpdateVars分組
? 對對象上的每個NetworkBehaviour腳本調(diào)用OnDeserialize函數(shù)
? 對象上的每個NetworkBehaviour腳本讀取臟掩碼。
? 如果NetworkBehaviour的臟掩碼為零,OnDeserialize函數(shù)將不再讀取返回
? 如果臟掩碼為非零值,則OnDeserialize函數(shù)讀取與所設(shè)置的臟位相對應(yīng)的SyncVars的值
? 如果有SyncVar鉤子函數(shù),那些被從流讀取的值調(diào)用。
如果NetworkBehaviour有一個基類也有串行化函數(shù),基類函數(shù)也應(yīng)該被調(diào)用。
注意,為對象狀態(tài)更新創(chuàng)建的UpdateVar分組可以在被發(fā)送到客戶端之前聚合在緩沖器中,因此單個傳輸層分組可以包含多個對象的更新。
遠程操作
網(wǎng)絡(luò)系統(tǒng)具有在網(wǎng)絡(luò)上執(zhí)行動作的方法。這些類型的操作有時稱為遠程過程調(diào)用。在網(wǎng)絡(luò)系統(tǒng)中有兩種類型的遠程過程調(diào)用:Commands - 從客戶端調(diào)用并在服務(wù)器上運行; 和ClientRpc調(diào)用 - 它們在服務(wù)器上調(diào)用并在客戶端上運行。
下圖顯示了遠程操作采取的方向:

Commands
命令從客戶端上的Player對象發(fā)送到服務(wù)器上的Player對象。為了安全起見,命令只能從你的玩家對象發(fā)送,所以你不能控制其他玩家的對象。要將某個函數(shù)轉(zhuǎn)換為命令,請向其中添加[Command]定制屬性,并添加“Cmd”前綴。此函數(shù)現(xiàn)在將在服務(wù)器上在客戶端上調(diào)用時運行。任何參數(shù)將自動使用命令傳遞到服務(wù)器。
命令函數(shù)必須具有前綴“Cmd”。當讀取調(diào)用命令的代碼時,這是一個提示 - 這個函數(shù)是特殊的,不象普通函數(shù)那樣在本地調(diào)用。
小心從客戶端每幀發(fā)送命令!這可能會導(dǎo)致大量的網(wǎng)絡(luò)流量。
默認情況下,命令在通道0(默認可靠通道)上發(fā)送。因此,默認情況下所有命令都可靠地發(fā)送到服務(wù)器。這可以使用[Command]自定義屬性的“Channel”參數(shù)進行自定義。此參數(shù)應(yīng)為一個整數(shù),表示通道號。
通道1也默認設(shè)置為不可靠通道,因此要使用此參數(shù),請在Command屬性中使用值1作為參數(shù),如下所示:
???????? [Command(channel=1)]
從Unity 5.2版開始,可以從具有客戶端權(quán)限的非玩家對象發(fā)送命令。這些對象必須已使用NetworkServer.SpawnWithClientAuthority衍生,或者使用NetworkIdentity.AssignClientAuthority設(shè)置權(quán)限。從這些對象發(fā)送的命令在對象的服務(wù)器實例上運行,而不是在客戶端的關(guān)聯(lián)Player對象上運行。
ClientRpc調(diào)用
ClientRpc調(diào)用從服務(wù)器上的對象發(fā)送到客戶端上的對象。它們可以從具有已生成的NetworkIdentity的任何服務(wù)器對象發(fā)送。由于服務(wù)器具有權(quán)限,因此服務(wù)器對象在發(fā)送這些調(diào)用時不存在安全問題。要將函數(shù)變成ClientRpc調(diào)用,請向其中添加[ClientRpc]定制屬性,并添加“Rpc”前綴。此函數(shù)現(xiàn)在將在客戶端上在服務(wù)器上調(diào)用時運行。任何參數(shù)將自動傳遞給ClientRpc調(diào)用的客戶端..
ClientRpc函數(shù)必須具有前綴“Rpc”。當讀取調(diào)用方法的代碼時,這是一個提示 - 這個函數(shù)是特殊的,不象普通函數(shù)那樣在本地調(diào)用。
當使用LocalClient作為主機運行游戲時,ClientRpc調(diào)用將在LocalClient上調(diào)用 - 即使它與服務(wù)器處于相同的進程。因此,LocalClients和RemoteClients的行為對ClientRpc調(diào)用是相同的。