實作成品,遊戲下載
最近研究了 Unity 提供的多人連線套件 Netcode for GameObjects,簡稱 NGO。用來做一些比較輕量的多人連線應用。我用這個套件做了個五子棋對戰遊戲,可以和遠端的朋友一起玩(你應該有朋友吧?)。只有連線對戰功能,不提供與 AI 對戰。另外遊戲裡沒有擋禁手,就當作休閒遊戲玩玩吧。
這次採用的架構是某一台電腦自己當主機開房間,讓其它玩家連線進來,也就是 Host 的模式。
快樂五子棋遊戲下載連結
使用套件介紹
使用的套件為 Netcode for GameObjects,簡稱 NGO。是 Unity 官方針對小型至中型多人遊戲所設計的網路解決方案,它不是為大規模 MMO 或 FPS 對戰遊戲設計的。適合的遊戲類型:下棋、麻將、回合制對戰、派對遊戲、休閒對戰遊戲。不適合的遊戲類型:大型 FPS、MMORPG、大規模即時戰略遊戲。理想的連線人數大概是 2 至 10 人左右,如果需求是更多人的連線遊戲,就要考慮其它的解法了,例如 Netcode for Entites、Mirror、Photon。
安裝 NGO 很簡單,啟動 Package Manager,搜尋 Netcode 就能找到套件,直接安裝就好了。
IP 連線方式
無論用哪種連線方式,都需要在專案裡建一個 NetworkManager 物件,掛上二個腳本:Network Manager 與 Unity Transport,並把 Unity Transport 的 Protocol Type 擇擇 Unity Transport。
使用 IP 的方式開房間。
使用 IP 的方式加入房間。
Relay 連線方式
前面提到使用 IP 連線的方式,這種方式適合所有的主機都在同一個網域內,例如大家都在同一個區域網路。但現實中的應用情境每台電腦通常都透過 NAT 連上網路,以 IP 的方式加入房間是找不到主機的 (除非在路由器裡設定了 Port Forwarding,這種進階操作一般人不會)。為了解決這個問題,Unity 提供了 Relay 的服務。簡單地說可以想像成網路上有一台 Relay 的主機,Host 與 Client 都連到這台 Relay 主機,在那裡做資料交換。
使用 Relay 建立房間,Host 端會得到一組 Join Code。要連線的 Client 端只要輸入這組 Join Code 就能連線到房間。當然這樣還是得透過別方式讓對方知道 Join Code 是什麼,還是不夠方便。能不能直接有個列表,列出現在線上有哪些房間,我選擇我想加入的房間就可以加入了?答案是可以。如果會寫後端程式的話這個功能可以自己做,不然 Unity 也提供了這個服務,就是 Lobby。不過我這次沒用到 Lobby 所以就不細說了。
要使用 Relay 功能,首先得設定專案連結。在 Edit --> Project Settings --> Services 這樣把專案連結好。
要呼叫使用 Relay 的功能前,得先初始化一些設定,執行一次即可。
使用 Relay 的方式開房間。
使用 Relay 的方式加入房間。
NetworkVariable
NetworkVariable 是 NGO 提供的一個功能,以它宣告出來的變數,資料會自動同步;並且在資料有異動的時候發出通知,因此可以做為一種資料驅動的方式做出回應,例如偵聽某個變數的值改變了,因而去修改場景物件的顏色。NetworkVariable 看起來很方便,但有一些限制。
- NetworkVariable 不可以宣告成 static。
- NetworkVariable 支援的資料型別有限制,只能是 int、float、enum、Vector3 這一類的基本型別,如果要用自訂型別,則該型別必須實作 INetworkSerializable。並且留意不能用 string。
有一個小地方要注意,在宣告 NetworkVariable 的同時就要把它 new 出來。不要想著先宣告,然後在 OnNetworkSpawn( ) 的時候再 new 出實體,這樣會破壞它的同步功能,這個有時會不小心踩坑。
RPC
全名是 Remote Procedure Call,簡稱 RPC。讓你從一台裝置去呼叫另一台裝置的某個 function。例如由 Client 去呼叫 Host 端執行某個 function;或是 Host 呼叫某個 (或全部) Client 去執行某個 function。可以把 RPC 視為一個「事件」發生。尤其是 Client 常常需要透過 RPC 去通知 Server 做一些事,例如修改 NetworkVariable 的值,因為只有 Server 端有修改權限。RPC 同樣有一些限制。
- RPC 只能在 NetworkBehaviour 中宣告。NetworkBehaviour 就像是 MonoBehaviour 一樣,是我們寫自訂腳本的地方,只是它又多了網路連線功能。
- RPC 沒有回傳值,而且它傳遞的資料型別有限制,一樣只能是 int、float、enum、Vector3 這一類的基本型別,或是實作了 INetworkSerializable 的自訂型別。
NetworkTransform
NetworkTransform 是一個用來自動同步物件的 Transform(位置、旋轉、縮放)的元件。例如玩家角色的移動、場景上一些物件的移動。為了節省頻寬,只同步有需要的屬性,不需要同步的屬性不要打勾。
如果有個物件需要輪流給不同的 Client 擁有 Transform 的控制權,則可以把 Network Transform 的 Authority Mode 改為 Owner。這樣只要把 Owner 指定給特定的 Client,它就能直接控制該物件的 Transform,然後自動同步給所有其它 Client。
類似 NetworkTransform 的類別還有 NetworkAnimator、NetworkRigidbody。
一些有點雜的筆記
- 加了 Rigidbody 的物件,如果要同步物理狀態,要加上 Network Rigidbody。它會自動把 Client 端的 Rigibody 設定為 Kinematic,並且主動加上 Network Transform 來同步物件的座標和旋轉。因此所有 Client 看到的物理效果都會一模一樣。如果沒加 Network Rigidbody,則 Client 端會用自己的環境計算碰撞,物件最後的位置與旋轉會與 Server 不同。
- 取得自己的 Player 物件:NetworkObject playerObj = NetworkManager.Singleton.LocalClient.PlayerObject;。
- 以 Client Id 取得 Player 物件:NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;。
- 改變 Network Object 的 Parent 關係會觸發 NetworkBehaviour.OnNetworkObjectParentChanged。
- NetworkLog.LogInfoServer("Hello World!"); 可以在 Server 端寫 log。
- NetworkBehaviour.OnNetworkPostSpawn( ) 會在所有的 NetworkBehaviour.OnNetworkSpawn( ) 之後被呼叫。
- NetworkManager 幫每個 Client 建立的 Prefab 物件不用自己放到 DontDestroyOnLoad,NGO 會自動處理這件事。
- NetworkObject 不能再包含有 NetworkObject 的子物件。說的更詳細點,有 NetworkObject 的父子物件,必須各別生成,手動設定為父子。就是不能一起生成。所以不能做成 Prefab,因為 Prefab 就是一起生成。只有動態 Spawn 出來的 Network Object 可以被放到另一個 Network Object 下面,而且這個作為父物件的 Network Object 也要是被動態 Spawn 出來的。感覺有點複雜,希望我沒有理解錯誤。
- NetworkBehaviour 必須加在同時有 NetworkObject 的物件上,或者父層有 NetworkObject 的物件上。
- 一旦斷線,除了自動生成的 Player 物件會自動消滅,所有動態生成的物件也會自動消滅。
- 使用 NetworkHide( ) 來隱藏物件時,對應的 Client 只要是變成看不見該物件,則 GameObject 會直接被消滅。但對 Host 端無效,因為 Host 端握有全部的資料,如果要能對 Host 端「隱形」的話,這個部份的邏輯要自行處理。
- 因為 Despawn(false) 對 Client 來說沒差,物件一樣被消滅,對 Host 端也沒差,物件一樣停在原地,沒有任何變化。因此,若要實現 Host / Client 都能用的 Pool,得自己處理物件的回收 (例如移到場外,停止移動)。
- 關於 AnticipatedNetworkVariable,Client 修改了預測值不會同步到 Server,所以 Server 也沒有機會去「修正」。要能同步到 Server,必須透過 Rpc 告知,再由 Server 端去更新這個值,再同步回 Client,但這樣做,Client 還是得等 Server 同步回來的值做更新,就沒有「預測」的效果了。所以 AnticipatedNetworkVariable 的用法就只是在 Client 端先寫入預測值,然後就直接套用這個預測值做後續的運算,但同步也要發 Rpc 讓 Server 端有機會「修正」預測值。結論就是 AnticipatedNetworkVariable 的設計和想像中的運作方式不一樣,有點多此一舉,官方也把它的範例移除了,感覺沒打算教怎麼使用,是不是官方也覺得這個東西根本沒啥用。
- 如果使用 Load Scene 來切換場景,場上的物件都會被消滅。因此主要的邏輯最好放在 Don't Destroy OnLoad 裡面,確保它一直活著。這是一個程式架構的建議。
- 實測 NetworkVariable 的同步到 Client 端比想像中還要久,不要預期下一個 Frame 就會同步完成。
工具套件
Multiplayer Play Mode。單機模擬多個 Instance,在開發時期很方便,不用每次測試都要先 Build 執行檔。
Multiplayer Tools。提供 Network Simulator,可以模擬各種網路情境,讓你預先看到在爛網路的狀態遊戲運作會變成什麼樣子。
Multiplayer Services。前面提過的 Relay 整合在這個套件裡,要用 Relay 就要裝。
4) Clumsy。在系統模擬不良的網路環境。僅列出來,我沒用它。
沒有留言:
張貼留言