Unity3D 網(wǎng)絡(luò)組件UNet詳解

前段時(shí)間,研究了一下UNet,經(jīng)過項(xiàng)目實(shí)踐,大致整理了下遇到的問題。

UNet常見概念簡(jiǎn)介

  • Spawn:簡(jiǎn)單來說,把服務(wù)器上的GameObject,根據(jù)上面的NetworkIdentity組件找到對(duì)應(yīng)監(jiān)視連接,在監(jiān)視連接里生成相應(yīng)的GameObject.

  • Command:客戶端調(diào)用,服務(wù)器執(zhí)行,這樣客戶端調(diào)用的參數(shù)必需要UNet可以序列化,這樣服務(wù)器在執(zhí)行時(shí)才能把參數(shù)反序列化。需要注意,在客戶端需要有權(quán)限的NetworkIdentity組件才能調(diào)用Command命令。

  • ClientRpc:服務(wù)端調(diào)用,客戶端執(zhí)行,同上,服務(wù)端的參數(shù)序列化到客戶端執(zhí)行,一般來說,服務(wù)端會(huì)找到上面的NetworkIdentity組件,確定那些客戶端在監(jiān)視這個(gè)NetworkIdentity,Rpc命令會(huì)發(fā)送給所有的監(jiān)視客戶端。

  • Server/ServerCallback:只在服務(wù)器端運(yùn)行,Callback是Unity內(nèi)部函數(shù)。

  • Client/ClientCallback:同上,只在客戶端運(yùn)行,Callback是Unity內(nèi)部函數(shù)。

  • SyncVar:服務(wù)器的值能自動(dòng)同步到客戶端,保持客戶端的值與服務(wù)器一樣??蛻舳酥蹈淖儾⒉粫?huì)影響服務(wù)器的值。

上面的大部分特性都會(huì)轉(zhuǎn)化成相應(yīng)的MsgType,其中服務(wù)器調(diào)用,客戶端執(zhí)行對(duì)應(yīng)MsgType有如Spawn,ClientRpc,SyncVar對(duì)應(yīng)的MsgType分別為ObjectSpawn,Rpc,UpdateVars,這些都是NetworkServer調(diào)用,客戶端得到相應(yīng)消息,執(zhí)行相應(yīng)方法。客戶端調(diào)用,服務(wù)器執(zhí)行的MsgType有如Command,客戶端發(fā)送,服務(wù)器檢測(cè)到相應(yīng)消息后執(zhí)行。

UNet主要類介紹

  1. NetworkIdentity組件介紹:網(wǎng)絡(luò)物體最基本的組件,客戶端與服務(wù)器確認(rèn)是否是一個(gè)物體(netID),也用來表示各個(gè)狀態(tài),如是否是服務(wù)器,是否是客戶端,是否有權(quán)限,是否是本地玩家等。一個(gè)簡(jiǎn)單例子,A是Host(又是服務(wù)器,又是客戶端),B是一個(gè)Client,A與B分別有一個(gè)玩家PlayA與PlayB.在機(jī)器A上,playA與playB isServer為true,isClent為true,其中playA有權(quán)限,是本地玩家,B沒權(quán)限,也不是本地玩家。在機(jī)器B上,playA與playB isServer為false,isClent為true,其中playB有權(quán)限,是本地玩家,A沒權(quán)限,也不是本地玩家。A與B上的PlayA的netID相同,A與B上的PlayB的netID也相同,其中netID用來表示他們是同一網(wǎng)絡(luò)物體在不同的機(jī)器上。

在下面用網(wǎng)絡(luò)物體來表示帶有NetworkIdentity組件的GameObject.

  1. NetworkConnection:定義一個(gè)客戶端與服務(wù)器的連接,包含當(dāng)前客戶端監(jiān)視那些服務(wù)器上的網(wǎng)絡(luò)物體,以及封裝發(fā)送和接收到服務(wù)器的消息。

  2. NetworkClient:主要持有當(dāng)前NetworkConnection對(duì)象與所有NetworkClient列表的靜態(tài)對(duì)象,處理一些默認(rèn)客戶端的消息。

  3. 網(wǎng)絡(luò)物體上的監(jiān)視者就是一個(gè)或多個(gè)NetworkConnection,用來表示一個(gè)或多個(gè)客戶端對(duì)這個(gè)網(wǎng)絡(luò)物體保持監(jiān)視,那么當(dāng)這個(gè)網(wǎng)絡(luò)物體在服務(wù)器上更新后,會(huì)自動(dòng)更新對(duì)所有監(jiān)視者的對(duì)應(yīng)的網(wǎng)絡(luò)物體。

  4. NetworkScene:簡(jiǎn)單來說,1Server與Client需要維護(hù)一個(gè)網(wǎng)絡(luò)物體列表,Server可以遍歷所有網(wǎng)絡(luò)物體發(fā)送消息等,并且維持Server與Client上的網(wǎng)絡(luò)物體保持同步,并且客戶端記錄需要注冊(cè)的prefab列表.其中NetworkServer與ClientScene都包含一個(gè)NetworkScene對(duì)象,引用網(wǎng)絡(luò)物體列表。

  5. NetworkServer:主要持有一個(gè)NetworkScene并且做一些只有在服務(wù)器上才能對(duì)網(wǎng)絡(luò)服務(wù)做的事,如spawn, destory等。以及維護(hù)所有客戶端連接。

  6. ClientScene:主要持有一個(gè)靜態(tài)NetworkScene對(duì)象,用于注冊(cè)網(wǎng)絡(luò)物體的prefab列表,以及客戶端場(chǎng)景上已經(jīng)有的網(wǎng)絡(luò)物體列表,處理SyncVar,Rpc,SyncEvent特性等,還有以及ObjectSpawn,objectDestroy,objectHide消息等。

UNet用時(shí)想到的問題

問題1 spawn發(fā)生了什么,客戶端為什么要注冊(cè)相應(yīng)的prefab.

  1. 當(dāng)服務(wù)器spawn一個(gè)網(wǎng)絡(luò)物體時(shí),網(wǎng)絡(luò)物體調(diào)用OnStartServer,分配netID.并注冊(cè)到相應(yīng)服務(wù)器上的的NetworkScene的網(wǎng)絡(luò)物體列表中,更新如isServer為true等信息。

  2. 查找所有客戶端連接,查看每個(gè)客戶端連接是否需要監(jiān)視這個(gè)網(wǎng)絡(luò)物體,如果為true,那么給這個(gè)客戶端上一個(gè)消息MsgType.ObjectSpawn或是MsgType.ObjectSpawnScene(這種一般是服務(wù)場(chǎng)景變換后自動(dòng)調(diào)用),并傳遞上面的netID.

  3. 當(dāng)客戶端接受到ObjectSpawn消息,會(huì)在注冊(cè)的prefab里查找,查找到后Instantiate個(gè)網(wǎng)絡(luò)物體,當(dāng)接受到ObjectSpawnScene時(shí),會(huì)在場(chǎng)景里查找這個(gè)網(wǎng)絡(luò)物體,然后都注冊(cè)到ClientScene里的NetworkScene的網(wǎng)絡(luò)物體列表中,并更新netID與服務(wù)器的一樣。更新如isClent為true等信息。
    我們手動(dòng)spawn一個(gè)物體時(shí),調(diào)用的是ObjectSpawn消息,客戶端接到這個(gè)消息處理得到一個(gè)assetID,我們要根據(jù)prefabe實(shí)例一個(gè)新對(duì)象,只有客戶端注冊(cè)了相應(yīng)的prefabe信息才能根據(jù)對(duì)應(yīng)的assetID找到prefabe.

問題2 NetworkIdentity的netID表示什么,那個(gè)時(shí)候分配。

當(dāng)服務(wù)器與客戶端的netID相同,表示他們是同一物體,相應(yīng)標(biāo)示如SyncVar,服務(wù)器變了,對(duì)應(yīng)客戶端上相同的netID的網(wǎng)絡(luò)物體,更新成服務(wù)器上的數(shù)據(jù),Rpc,Commandg 一般也是相同的netID之間調(diào)用。

分配一般發(fā)生在服務(wù)器spawn一個(gè)網(wǎng)絡(luò)物體時(shí),網(wǎng)絡(luò)物體調(diào)用OnStartServer時(shí)發(fā)生產(chǎn)生netID。

在客戶端接受相應(yīng)的ObjectSpawn消息,會(huì)把服務(wù)器上的對(duì)應(yīng)物體的netID傳遞過來,產(chǎn)生新的網(wǎng)絡(luò)物體并賦這個(gè)netID。

問題3 NetworkIdentity的sceneID是什么,在場(chǎng)景里已經(jīng)有NetworkIdentity組件的物體是如何在客戶端與服務(wù)器聯(lián)系的。

當(dāng)網(wǎng)絡(luò)物體并不是spawn產(chǎn)生在服務(wù)器與客戶端,而是在服務(wù)器與客戶端場(chǎng)景本身就有時(shí),我們也需要在服務(wù)器與客戶端之間建立聯(lián)系,這種物體會(huì)有一個(gè)sceneID來標(biāo)示,這種模型一般是服務(wù)器場(chǎng)景變換完成后,NetworkServer調(diào)用spawnObjects會(huì)把這種網(wǎng)絡(luò)物體與所有客戶端同步,當(dāng)spawn完成后過后,相應(yīng)客戶端會(huì)產(chǎn)生一個(gè)和服務(wù)端相同的netID。

問題4 服務(wù)器場(chǎng)景切換后,各個(gè)NetworkIdentity組件的物體如何與客戶端聯(lián)系。

如下順序因?yàn)橛挟惒讲僮鳎⒉荒艽_定,如下順序只是一般可能的順序。

  1. 服務(wù)器異步調(diào)用場(chǎng)景,發(fā)送給所有客戶端開始切換場(chǎng)景。MsgType.Scene

  2. 客戶端接受MsgType.Scene,開始切換場(chǎng)景。

  3. 服務(wù)器場(chǎng)景完成,會(huì)查找所有的網(wǎng)絡(luò)物體,然后spawn這些網(wǎng)絡(luò)物體,這樣各個(gè)網(wǎng)絡(luò)物體通過相同的netID聯(lián)系起來。

  4. 客戶端場(chǎng)景完成后,再次調(diào)用OnClientConnect,一般來說,不執(zhí)行任何操作。

問題5 客戶端為什么要網(wǎng)絡(luò)物體的權(quán)限,它有了權(quán)限能做什么。

一般來說,當(dāng)spawn某個(gè)服務(wù)器上的網(wǎng)絡(luò)物體后,服務(wù)器有它的權(quán)限,客戶端并不能更改這個(gè)網(wǎng)絡(luò)物體,或是說更改這個(gè)網(wǎng)絡(luò)物體相應(yīng)的屬性后并不能同步到服務(wù)器和別的客戶端上,只是本機(jī)上能看到改變。

那么我如果需要能改變這個(gè)網(wǎng)絡(luò)物體上的狀態(tài),并能同步到所有別的客戶端上,我們需要擁有這個(gè)網(wǎng)絡(luò)物體的權(quán)限,因?yàn)檫@樣才能在本機(jī)上發(fā)送Command命令,才能告訴服務(wù)器我改變了狀態(tài),服務(wù)器也才能告訴所有客戶端這個(gè)網(wǎng)絡(luò)物體改變了狀態(tài)。

其中本地player在創(chuàng)建時(shí),當(dāng)前客戶端對(duì)本地player有權(quán)限??蛻舳松嫌袡?quán)限的網(wǎng)絡(luò)物體上的SyncVar改變后,也并不會(huì)能同步到服務(wù)器,服務(wù)器根本沒有注冊(cè)UpdateVars消息,這種還是需要客戶端自己調(diào)用Command命令。

問題6 UNet常見的封裝狀態(tài)同步處理有那些,其中NetworkTransform與NetworkAnimator分別怎樣通信,如果是客戶端權(quán)限的網(wǎng)絡(luò)物體又是怎么通信的了。

UNet常見的封裝狀態(tài)同步狀態(tài)方法有二種。

  • 一是通過ClientRpc與Command是封裝發(fā)送消息??蛻舳伺c服務(wù)端一方調(diào)用,然后序列化相應(yīng)的參數(shù),然后到服務(wù)器與客戶端反序列化參數(shù)執(zhí)行。

  • 二是網(wǎng)絡(luò)內(nèi)置的序列化與反序列化,序列化服務(wù)器的狀態(tài),然后客戶端反序列化相應(yīng)的值,如SyncVar通過相應(yīng)的OnSerialize,OnDeserialize.這種只能同步服務(wù)器到客戶端。

這二種本質(zhì)都是客戶端與服務(wù)器互相發(fā)送MsgType消息,對(duì)應(yīng)的服務(wù)器與客戶端注冊(cè)相應(yīng)消息處理。NetworkAnimator 服務(wù)器上的動(dòng)畫改變,會(huì)發(fā)消息通知所有客戶端相應(yīng)狀態(tài)改變了,如Rpc。NetworkTransform 服務(wù)器通過OnSerialize序列化相應(yīng)的值,然后客戶端反序列化相應(yīng)的值。

如果客戶端有對(duì)應(yīng)NetworkTransform與NetworkAnimator網(wǎng)絡(luò)物體的權(quán)限。NetworkAnimator 相應(yīng)客戶端提交狀態(tài)到服務(wù)器上,然后分發(fā)到所有客戶端,相當(dāng)于調(diào)用了Command,并在Command里調(diào)用了Rpc方法。NetworkTransform 相應(yīng)客戶端發(fā)送消息到服務(wù)器上,服務(wù)器更新相應(yīng)位置,方向。然后通過反序列化到所有客戶端。

所以如果客戶端有授權(quán),那么NetworkAnimator與NetworkTransform在服務(wù)器或是有授權(quán)的客戶端的狀態(tài)改變都能更新到所有客戶端,注意這二個(gè)組件對(duì)localPlayerAuthority的處理不同,在NetworkTransform中,localPlayerAuthority為false時(shí),客戶端不能更新到所有客戶端,在NetworkAnimator中,localPlayerAuthority為true時(shí),服務(wù)器不能更新到客戶端上。

其中注意SyncVar特性,就算客戶端授權(quán),客戶端改變后,也不會(huì)同步到別的機(jī)器上。

所以如果我們自己設(shè)計(jì)類似的網(wǎng)絡(luò)組件,需要考慮客戶端授權(quán)的相應(yīng)處理,就是差不多添加一個(gè)Command命令。

問題7 客戶端授權(quán)與本地player授權(quán)有什么區(qū)別。

一般物體的權(quán)限都在服務(wù)器上,如果要對(duì)網(wǎng)絡(luò)物體授權(quán)給客戶端,一般通過SpawnWithClientAuthority實(shí)現(xiàn),這樣在相應(yīng)客戶端上的hasAuthority為true,其中相應(yīng)的playerControllerID為-1。

而本地player授權(quán)l(xiāng)ocalPlayerAuthority,在相應(yīng)的網(wǎng)絡(luò)物體上的Local Player Authority勾選上,在對(duì)這個(gè)網(wǎng)絡(luò)物體的所有監(jiān)視客戶端上,本地player授權(quán)都是true,這種一般用于玩家,或是玩家控制位移的物體,playerControllerID大于等于0。

所以客戶端授權(quán)針對(duì)是某個(gè)客戶端,在這個(gè)客戶端上的這個(gè)網(wǎng)絡(luò)物體的hasAuthority為true,而本地player針對(duì)是某個(gè)網(wǎng)絡(luò)物體,在所有客戶端上的這個(gè)網(wǎng)絡(luò)物體的localPlayerAuthority都為true.

問題8 UNet怎么實(shí)現(xiàn)迷霧地圖

通過NetworkProximityChecker,這樣每楨檢測(cè)當(dāng)前網(wǎng)絡(luò)物體的監(jiān)視連接,確定那些客戶端需要這個(gè)網(wǎng)絡(luò)物體。同樣,想實(shí)現(xiàn)更復(fù)雜的可以自己實(shí)現(xiàn)類似。

問題9 NetworkServer.Destroy做了啥

必須是網(wǎng)絡(luò)物體,且最好能在服務(wù)器調(diào)用,調(diào)用時(shí),發(fā)給所有的監(jiān)視Connect,銷毀對(duì)應(yīng)網(wǎng)絡(luò)物體,然后服務(wù)器銷毀。請(qǐng)看MsgType.ObjectDestroy消息流程.

需要注意的是在服務(wù)器中,Destroy某網(wǎng)絡(luò)物體,會(huì)自動(dòng)調(diào)用NetworkServer.Destroy。代碼在NetworkIdentity.OnDestroy.

問題10 服務(wù)器添加角色時(shí)做了那些事。

當(dāng)客戶端連接服務(wù)器時(shí),設(shè)置自動(dòng)創(chuàng)建角色后,會(huì)自動(dòng)創(chuàng)建角色。

1 服務(wù)器添加一個(gè)player,設(shè)定playercontrollerID

2 設(shè)置當(dāng)前conn的ready為true.然后檢測(cè)當(dāng)前的conn是否需要監(jiān)視服務(wù)器上NetworkScene的網(wǎng)絡(luò)物體列表的各個(gè)網(wǎng)絡(luò)物體,其中客戶端上的isspawnFinished表示NetworkScene的網(wǎng)絡(luò)物體列表是否檢測(cè)完成。

3 把服務(wù)器的player的spawn下去,設(shè)定對(duì)應(yīng)網(wǎng)絡(luò)物體記錄的本地權(quán)限客戶端為當(dāng)前客戶端,相應(yīng)的playercontrollerid發(fā)送到客戶端。

問題11 NetworkClient與networkServer的active表示什么,那些時(shí)候用

networkServer開始監(jiān)聽后,設(shè)定active為true。

networkClient連接上服務(wù)器后,設(shè)定為true。

當(dāng)有些消息發(fā)送,或是Rpc與Command等的調(diào)用時(shí),時(shí)機(jī)可能會(huì)在active之前,引發(fā)錯(cuò)誤。

問題12 網(wǎng)絡(luò)的Update做些啥。

1 服務(wù)器更新,處理一些如客戶端鏈接與丟失鏈接,還有接收消息并找到對(duì)應(yīng)事件處理,以及序列化服務(wù)器網(wǎng)絡(luò)物體要更新的數(shù)據(jù)。

2 客戶端更新,如上服務(wù)器的處理,主要也是相應(yīng)消息處理。

3 檢查服務(wù)器與客戶端的場(chǎng)景是否加載完成。

最后,想象一下,在網(wǎng)絡(luò)環(huán)境下,我們拉開弓箭,生成箭,箭在客戶端上緩緩拉開,我們應(yīng)該如何做?

首先弓箭要讓所有客戶端看的到,我們要在服務(wù)器上生成,然后spawn分發(fā)到相應(yīng)多個(gè)客戶端,然后當(dāng)前客戶端還需要當(dāng)前箭的權(quán)限,這樣當(dāng)前用戶才能控制這把箭,并把當(dāng)前用戶控制箭產(chǎn)生的新位置同步給所有的客戶端。

其次如果采用Valve的LabRender渲染器,需要在開始服務(wù)器時(shí)關(guān)閉,等到對(duì)應(yīng)的角色加載后,再通過localplayer打開各自對(duì)應(yīng)的valveCamer,不然服務(wù)器上的valveCamer可能得不到正確的陰影圖。

如果有分析不對(duì)的地方,歡迎大家指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容