本教程將詳細(xì)展示如何把 Unity Online Services(UOS)Multiverse 與 Netcode for GameObjects 相結(jié)合,完成入門級(jí)操作。
通過(guò)本教程,你不僅能學(xué)會(huì)如何將其構(gòu)建為專用服務(wù)器(Dedicated Server),并借助 Multiverse 進(jìn)行托管,還能掌握運(yùn)用 Matchmaking 為多人聯(lián)機(jī)游戲打造可定制化的對(duì)局匹配。此外,教程還會(huì)演示并講解將 Demo 工程部署至微信小游戲平臺(tái)時(shí)所需的各項(xiàng)設(shè)置,帶你一站式打通游戲開發(fā)與部署的關(guān)鍵流程!
大家可以通過(guò) UOS 官網(wǎng)提供的多人聯(lián)機(jī)服務(wù)選型參考,來(lái)了解 Dedicated Game Server 適用的游戲類型:
https://uos.unity.cn/product/multiplayer
本教程中涉及 UOS 服務(wù)包括:
服務(wù)器托管服務(wù) Multiverse:用于部署多人聯(lián)機(jī)服務(wù)端程序
服務(wù)器托管服務(wù) Multiverse
Multiverse,由 Unity 資深團(tuán)隊(duì)匠心打造的專業(yè)服務(wù)器托管解決方案,讓您的服務(wù)從上線到運(yùn)營(yíng)全程無(wú)憂。
它根植于 Agones 的強(qiáng)大基礎(chǔ),提供了更為可靠、豐富且高效的托管服務(wù)。借助 Multiverse,您的游戲能夠迅速啟動(dòng)并運(yùn)行于彈性縮擴(kuò)容的系統(tǒng)中,無(wú)論是面對(duì)幾百還是幾百萬(wàn)的玩家,都能輕松應(yīng)對(duì),實(shí)現(xiàn)游戲服務(wù)器的無(wú)憂托管,讓您全心投入到提升游戲的可玩性之中。
選擇 Multiverse 的卓越優(yōu)勢(shì):
高效可靠的伸縮能力:基于 Agones 的堅(jiān)實(shí)架構(gòu),Multiverse 確保了服務(wù)器資源能夠根據(jù)游戲負(fù)載進(jìn)行即時(shí)且高效的調(diào)度,同時(shí)提供可靠的伸縮機(jī)制,無(wú)論是面對(duì)突發(fā)的玩家涌入還是日常的流量波動(dòng),都能確保游戲運(yùn)行的平穩(wěn)與流暢。
靈活的 Docker 容器部署:借助 Docker 容器技術(shù)的強(qiáng)大功能,Multiverse 允許開發(fā)者以更加靈活和模塊化的方式組織服務(wù),同時(shí)簡(jiǎn)化了系統(tǒng)依賴的安裝與管理過(guò)程,使得服務(wù)的部署、升級(jí)與遷移變得更加便捷與高效。
多地域智能縮擴(kuò)容:為了滿足全球玩家的需求,Multiverse 支持在全球多個(gè)地域部署服務(wù)器,并根據(jù)實(shí)時(shí)流量數(shù)據(jù)自動(dòng)調(diào)整服務(wù)器資源,確保服務(wù)的經(jīng)濟(jì)性與高效性。這種智能化的縮擴(kuò)容策略不僅降低了運(yùn)營(yíng)成本,還提升了玩家的游戲體驗(yàn),讓您的游戲在全球范圍內(nèi)都能保持出色的性能與穩(wěn)定性。
教程視頻
教程學(xué)習(xí)大綱
Unity 項(xiàng)目工程的準(zhǔn)備工作
創(chuàng)建 UOS App 并啟用 Multiverse / Matchmaking 服務(wù)
使用 WebSocket 協(xié)議配置并啟動(dòng)項(xiàng)目
使用 Matchmaking 實(shí)現(xiàn)對(duì)局匹配
使用 KCP 協(xié)議配置并啟動(dòng)項(xiàng)目
構(gòu)建 MiniGame 項(xiàng)目,設(shè)置 WXSDK 打包參數(shù)
在開發(fā)者工具上運(yùn)行測(cè)試小游戲
更多功能介紹
教程案例工程源文件
教程內(nèi)學(xué)習(xí)用到的項(xiàng)目工程文件可以通過(guò)以下方式進(jìn)行下載:
教程示例 Demo 項(xiàng)目工程源文件下載鏈接:
https://unitychina.coding.net/public/uos/MultiverseNetcodeDemo/git/files
教程操作步驟
1. Unity 項(xiàng)目工程準(zhǔn)備工作
溫馨提醒:本教程的內(nèi)容同時(shí)適用于 Global Unity Editor 、中國(guó)版 Unity Editor 和 團(tuán)結(jié)引擎,當(dāng)前先以在中國(guó)版 Unity 引擎中的使用為示例,進(jìn)行講解演示。
為大家提供兩種準(zhǔn)備初始項(xiàng)目的方式:
方式一:直接下載我們提供鏈接里的 Demo 示例項(xiàng)目工程學(xué)習(xí),按章節(jié) 1.1 的步驟操作,可跳過(guò)章節(jié) 1.2。
方式二:從創(chuàng)建空項(xiàng)目工程開始,按章節(jié) 1.2 的教程步驟逐步學(xué)習(xí)。
無(wú)論選擇哪種方式,后續(xù)都從章節(jié) 2 繼續(xù)學(xué)習(xí)。
如果你想直接使用我們提供的示例 Demo 場(chǎng)景進(jìn)行學(xué)習(xí)操作的話,請(qǐng)先通過(guò)上面提供的 git 鏈接,下載 Demo 項(xiàng)目工程。
在教程中,我們先使用 中國(guó)版 Unity 2022.3.53 f1c1 版本來(lái)打開項(xiàng)目工程,然后等待項(xiàng)目工程的加載。
1.1.2 安裝 Netcode for GameObjects 資源包
當(dāng)前項(xiàng)目將使用 Multiverse 結(jié)合 Netcode 來(lái)制作 Demo 的效果,所以項(xiàng)目中需要先安裝 Netcode 資源包。
在 Package Manager 窗口中,我們可以看到當(dāng)前 Demo 已經(jīng)安裝好了 Netcode 資源包。無(wú)需再次安裝。
本教程 Demo 的功能是在 Netcode for GameObjects 提供的 Sample 示例的基礎(chǔ)上稍作的調(diào)整,大家可以自行查看原始的 Sample 示例代碼。
打開 Unity Hub,選擇「創(chuàng)建新項(xiàng)目」,教程這里使用的 Unity 編輯器版本是 2022.3.53f1c1 版本,大家可以自行選擇電腦上已安裝的版本。
項(xiàng)目模板可以選擇「3D(Built-in)」,自定義項(xiàng)目名稱和位置??梢怨催x選項(xiàng)「啟用游戲云服務(wù)」,勾選后會(huì)自動(dòng)為你的項(xiàng)目工程安裝 UOS Launcher 的,然后可以直接通過(guò) UOS Launcher 來(lái)啟用想要使用的服務(wù)即可。
1.2.2 安裝 Netcode for GameObjects 資源包
由于當(dāng)前項(xiàng)目將使用 Multiverse 結(jié)合 Netcode 來(lái)制作 Demo 的效果,所以項(xiàng)目中需要先安裝 Netcode 資源包。
可以打開「Window」→「Package Manager」窗口,然后選擇「Unity Registry」,搜索找到「Netcode for GameObjects」,然后點(diǎn)擊「Install」即可。
教程中我們會(huì)在 Netcode 提供的示例的 Sample 基礎(chǔ)上添加 Multiverse 和 Matchmaking 的功能。所以,我們先找到「Samples」,點(diǎn)擊「Import」,導(dǎo)入 Netcode 的示例場(chǎng)景。
1.2.3 查看 Sample 場(chǎng)景
找到 Project 窗口中的 Bootstrap.unity 場(chǎng)景并打開,運(yùn)行查看下當(dāng)前示例場(chǎng)景的效果。
在彈出的下面窗口中,點(diǎn)擊「Yes」,則會(huì)自動(dòng)將當(dāng)前場(chǎng)景添加到「File」→「BuildSettings」中。
運(yùn)行后,Game 場(chǎng)景畫面如下:
可以先在編輯器中點(diǎn)擊「Host」按鈕,會(huì)看到在場(chǎng)景中會(huì)生成一個(gè) Sphere 游戲?qū)ο?,點(diǎn)擊「Random Teleport」按鈕,就可以隨機(jī)一個(gè)新的位置。
1.2.4 場(chǎng)景中創(chuàng)建對(duì)局匹配和隨機(jī)位置的 UI 按鈕
由于場(chǎng)景中的按鈕是通過(guò) OnGUI 函數(shù)繪制出來(lái)的,為了后面方便操作 UI 對(duì)象,我們對(duì)場(chǎng)景做一些調(diào)整,使用 UGUI 來(lái)創(chuàng)建 Button。在 Hierarchy 窗口中,點(diǎn)擊「+」→「UI」→「Button - TextMeshPro」來(lái)創(chuàng)建一個(gè)按鈕。
在彈出窗口中,點(diǎn)擊「Import TMP Essentials」來(lái)導(dǎo)入相關(guān)資源:
然后設(shè)置 UI 自適應(yīng):找到 Canvas 對(duì)象上的 Canvas Scaler 組件,將「UI Scale Mode」設(shè)置為:Scale With Screen Size,分辨率暫時(shí)選擇 1920 *1080。
修改匹配按鈕的名字為:Button_StartMatch,大家可以自己調(diào)整想要的按鈕的大小、位置以及按鈕的顏色或者按鈕的背景貼圖等。
為了 UI 界面的美觀,大家可以自行更換按鈕上的貼圖和字體。在教程中,我們創(chuàng)建了一個(gè) Resources 文件夾,然后文件夾內(nèi)放入了自己想要使用的 UI 貼圖和字體了。UI 貼圖的格式,也已經(jīng)提前設(shè)置好 Sprite 格式了。
找到 Button 組件,將「Transition」類型改為「Sprite Swap」,在按鈕的「Normal 」狀態(tài)我們使用 Btn_MainButton_Blue 貼圖,在按鈕的「Disabled」禁用狀態(tài)使用 Btn_MainButton_Gray 這張貼圖。然后可以通過(guò)激活或者關(guān)閉 Interactable 屬性,測(cè)試按鈕的圖片效果。
默認(rèn) TextMeshProUGUI 提供的 FontAsset 是不支持輸入中文的「開始匹配」的,如果你想寫中文文字的話,請(qǐng)自行創(chuàng)建中文字體集,這里暫時(shí)先不講解 UGUI 的如何創(chuàng)建中文字體集的知識(shí)點(diǎn)了,就直接使用導(dǎo)入的創(chuàng)建好的字體集資源了。
選中 Button_StartMatch 按鈕復(fù)制一份,重命名為:Button_RandomTeleport,作為后面隨機(jī)小球位置的按鈕。移動(dòng)到左上角的位置,按鈕的 Text 先寫:RandomTeleport。
設(shè)置一下初始時(shí)場(chǎng)景中隨機(jī)位置的按鈕是隱藏的狀態(tài):
接下來(lái),我們就用剛才的新創(chuàng)建的項(xiàng)目工程繼續(xù)下面的步驟講解了。如果你是用我們提供的示例 Demo 的話,后續(xù)教程步驟也是一樣的。
1.2.5 設(shè)置 Network Prefabs Lists
找到場(chǎng)景中的 BootstrapManager 身上的 NetworkManager 組件,可以看到 Player Prefab 參數(shù)已經(jīng)設(shè)為 BootstrapPlayer 預(yù)制物體對(duì)象了,表示在客戶端連接上服務(wù)器時(shí),會(huì)克隆出一個(gè) BootstrapPlayer 對(duì)象。
NetworkPrefabsLists 參數(shù)用于指定在網(wǎng)絡(luò)同步過(guò)程中會(huì)使用到的預(yù)制體(Prefab)列表。在網(wǎng)絡(luò)多人游戲開發(fā)中,當(dāng)客戶端和服務(wù)器之間需要同步游戲?qū)ο髸r(shí),Unity 需要知道哪些預(yù)制體可以被實(shí)例化并在網(wǎng)絡(luò)上進(jìn)行同步。如果不指定這些預(yù)制體,當(dāng)服務(wù)器或客戶端嘗試實(shí)例化網(wǎng)絡(luò)對(duì)象時(shí),Unity 就無(wú)法識(shí)別這些對(duì)象,從而導(dǎo)致同步失敗。
在這里,我們將 Assets 文件夾下的 DefaultNetworkPrefabs 添加到 NetworkPrefabsLists 中。
然后再手動(dòng)添加一下場(chǎng)景中要網(wǎng)絡(luò)同步的預(yù)制物體游戲?qū)ο?BootstrapPlayer。
2. 創(chuàng)建 UOS App 并啟用服務(wù)
溫馨提示:當(dāng)前項(xiàng)目中已安裝好 UOS Launcher,不需要再次安裝了。大家可以參考之前的 的公眾號(hào)文章教程,來(lái)為你當(dāng)前的項(xiàng)目綁定你創(chuàng)建好的 UOS App 。
2.1 綁定 UOS App
點(diǎn)擊 Launcher 面板的「LinkApp」按鈕,在彈出窗口中選擇「By Unity project」,在「Select organization」這里選擇一個(gè)自己的項(xiàng)目組織,然后在「Select project 」選項(xiàng)這里,我們選擇「Create a new project 」,自行設(shè)置修改項(xiàng)目名字「Project name」。
教程中,我們就先選擇綁定創(chuàng)建好的 NetcodeAndMultiverse_Demo 應(yīng)用了。
2.2 開啟 Multiverse 服務(wù)
在編輯器內(nèi) Unity Online Services 窗口的下拉服務(wù)列表中,找到「Multiverse」,點(diǎn)擊「Enable」開啟服務(wù)。
如果自己項(xiàng)目中沒(méi)安裝過(guò)的話,可以點(diǎn)擊「Install」安裝 SDK 。當(dāng)前給大家提供的制作好的示例 Demo 項(xiàng)目已經(jīng)安裝過(guò) Multiverse SDK 了,無(wú)需再次安裝。
2.3 開啟 Matchmaking 服務(wù)
接著繼續(xù)在 UOS Launcher 的下拉服務(wù)窗口列表中,找到「Matchmaking Client」,點(diǎn)擊「Enable」開啟服務(wù)。
如果自己項(xiàng)目中沒(méi)安裝過(guò)的話,可以點(diǎn)擊「Install」安裝 SDK 。當(dāng)前給大家提供的制作好的示例 Demo 項(xiàng)目已經(jīng)安裝過(guò) Matchmaking Client SDK 了,無(wú)需再次安裝。
2.4 安裝 WebSocket 和 KCP 協(xié)議包
最終會(huì)將項(xiàng)目部署至微信小游戲平臺(tái),我們已經(jīng)做好了相關(guān)的協(xié)議適配,大家可以在項(xiàng)目中使用提供的 WebSocket 協(xié)議和 KCP 協(xié)議。當(dāng)前工程中,已經(jīng)安裝好了 WebSocket 協(xié)議和 KCP 協(xié)議。
如果是自己的項(xiàng)目工程的話,可以通過(guò)下面提供的 git 鏈接來(lái)安裝對(duì)應(yīng)的協(xié)議:在「Package Manager」窗口,點(diǎn)擊左上角的「+」,選擇「Add package from git URL」,輸入提供的 git 鏈接,點(diǎn)擊「Add」即可。
WebSocket Transport for Netcode for GameObjects 資源包的 git 安裝鏈接:
https://e.coding.net/unitychina/uos/NetcodeTransport.git?path=websocket
Kcp Transport for Netcode for GameObjects 資源包的 git 安裝鏈接:
https://e.coding.net/unitychina/uos/NetcodeTransport.git?path=kcp
2.5 安裝 Linux Build 平臺(tái)支持模塊包
稍后當(dāng)我們通過(guò) Multiverse SDK,在 Editor 中自動(dòng)構(gòu)建并上傳 Linux Dedicated Game Server 鏡像時(shí),會(huì)需要將平臺(tái)切換到 Linux 平臺(tái)。所以需要在當(dāng)前使用的 Unity 編輯器版本中,安裝 Linux 構(gòu)建平臺(tái)模塊包。
打開 Unity Hub ,找到左側(cè)的「安裝」,選擇自己使用的 Unity 編輯器版本,比如 2022.3.53f1c1,點(diǎn)擊「添加模塊」按鈕:
在彈出窗口中,同時(shí)勾選模塊 Linux Build Support(IL2CPP)、 Linux Build Support(Mono)、Linux Dedicated Server Build Support ,然后點(diǎn)擊「安裝」。
3. 使用 WebSocket 協(xié)議配置并啟動(dòng)項(xiàng)目 我們先來(lái)講解使用 WebSocket Transport 協(xié)議來(lái)運(yùn)行時(shí)的相關(guān)配置! 3.1 選擇 WebSocket 協(xié)議
先找到場(chǎng)景中的對(duì)象 BootstrapManager,需要移除 Netcode 示例場(chǎng)景中默認(rèn)為其添加好的 UnityTransport 組件。
然后在「Select transport...」這里選擇「WebSocketTransport」,添加 WebSocket 協(xié)議對(duì)應(yīng)的腳本組件,同時(shí)確保 NetworkManager 組件的 NetworkTransport 參數(shù)選擇的協(xié)議為:WebSocketTransport。
3.2 MultiverseSDK 的初始化
我們先來(lái)創(chuàng)建 Multiverse 的服務(wù)端程序。找到 BootstrapManager 對(duì)象上的 BootstrapManager.cs 腳本,先注釋掉或者刪除該腳本中的 OnGUI 函數(shù)中的所有的代碼,因?yàn)榻酉聛?lái)我們會(huì)一步一步講解要添加使用的代碼。
在這里,需要注意的是,Netcode 的示例代碼添加了自己的應(yīng)用程序集文件 Bootstrap.asmdef,所以想要在 BootstrapManager.cs 腳本中引用 UOS 封裝的相關(guān) API 時(shí),可做以下的兩種處理:
(1)直接刪除 Bootstrap/Scripts/Bootstrap.asmdef 文件即可。我們提供的示例 Demo 項(xiàng)目工程中,是已經(jīng)刪除了該文件,所以不需要 Bootstrap.asmdef 相關(guān)的配置。
(2)如果不想刪除,想保留的話,需要在 Bootstrap.asmdef 文件中添加 UOS 相關(guān)的引用,最后記得點(diǎn)擊右下角的「Apply」。
然后在腳本的 Start 方法中,會(huì)根據(jù)宏定義來(lái)檢測(cè)一下當(dāng)前運(yùn)行環(huán)境(服務(wù)器端還是客戶端),來(lái)執(zhí)行不同的初始化邏輯:
如果是 Linux Dedicated Server 服務(wù)端,會(huì)先進(jìn)行 Multiverse SDK 初始化,然后啟動(dòng)服務(wù)器,并標(biāo)記服務(wù)器為 Ready 就緒狀態(tài)。
如果是客戶端,則進(jìn)行 MatchmakingSDK 的初始化,并執(zhí)行玩家登錄的操作。_userId 將用于客戶端登錄時(shí)作為用戶的唯一標(biāo)識(shí)。
腳本中記得需要添加 UOS 相關(guān)的 namespace 的引用。
using System;
using Unity.UOS.Auth;
using Unity.UOS.Matchmaking;
using Unity.UOS.Multiverse;
using Unity.UOS.Multiverse.Exceptions;
using UnityEngine;
using Unity.Netcode;
public class BootstrapManager : MonoBehaviour
{
private readonly string _userId = Guid.NewGuid().ToString();
private async void Start()
{
#if !UNITY_EDITOR && UNITY_SERVER
// build 為 linux dedicated server 時(shí),執(zhí)行server端 multiverse sdk 初始化的代碼邏輯
Debug.Log("Server is starting");
try
{
// MultiverseSDK 的初始化
await MultiverseSDK.Initialize();
}
catch (MultiverseSDKException ex)
{
Debug.LogErrorFormat("Failed to initialize sdk. {0}", ex);
throw;
}
// 啟動(dòng) server
NetworkManager.Singleton.StartServer();
try
{
// 標(biāo)記 server 為 ready 狀態(tài)
await MultiverseSDK.Instance.ReadyAsync();
Debug.Log("server is ready");
}
catch (MultiverseSDKException ex)
{
Debug.LogErrorFormat("Failed to call mv sdk. {0}", ex);
throw;
}
#else
// build client端時(shí),則初始化 matchmaking sdk 和執(zhí)行玩家登陸操作
Debug.Log("match making sdk initialize");
MatchmakingSDK.Initialize();
Debug.Log("match making sdk initialized");
await AuthTokenManager.ExternalLogin(_userId);
#endif
}
}
由于在客戶端使用 AuthTokenManager.ExternalLogin 來(lái)驗(yàn)證玩家的登錄操作,所以場(chǎng)景中我們創(chuàng)建一個(gè)空對(duì)象,可命名為 AuthTokenManager,并掛載 AuthTokenManager.cs 腳本組件。
3.3 構(gòu)建并上傳 Multiverse 鏡像
Multiverse 是基于 Docker 鏡像來(lái)管理服務(wù)器程序的,系統(tǒng)需要將用戶上傳上來(lái)的服務(wù)器程序制作成 Multiverse 可用的鏡像程序,并基于鏡像來(lái)管理后續(xù)所有的配置。
而 UOS Launcher 提供了一鍵構(gòu)建并上傳 Multiverse 鏡像的功能。當(dāng)我們使用 Unity 構(gòu)建 Dedicated Server - Linux 且托管到 Multiverse 時(shí),推薦使用 Launcher 來(lái)簡(jiǎn)化鏡像構(gòu)建與上傳的流程。
步驟如下:
在 Multiverse 右側(cè)列表中,點(diǎn)擊「構(gòu)建鏡像」按鈕,此時(shí)則可以通過(guò) Multiverse SDK,在 Editor 中自動(dòng)上傳 Dedicated Game Server 鏡像到 UOS Dev Portal。
填寫配置:在彈出窗口中填寫配置:Target Directory 為鏡像存放的本地目錄,Image Tag 為鏡像標(biāo)簽。
構(gòu)建鏡像并上傳:點(diǎn)擊 Build Image 按鈕,等待鏡像構(gòu)建。構(gòu)建完成后將自動(dòng)上傳到關(guān)聯(lián)的 UOS APP 的 Multiverse 鏡像中。
打開 UOS 網(wǎng)頁(yè)端,在「啟動(dòng)配置」→「鏡像」這里,可以看到剛才構(gòu)建的服務(wù)端程序,已經(jīng)自動(dòng)上傳了。
在 Build Settings 窗口這里,可以看到在構(gòu)建鏡像時(shí),Platform 已經(jīng)自動(dòng)被切換至 Linux 平臺(tái)了。當(dāng)然,大家也可以手動(dòng)切換至 Linux 平臺(tái)。
3.4 創(chuàng)建啟動(dòng)配置
我們以 Multiverse 的按需模式為例,需要在「Multiverse -> 啟動(dòng)配置」頁(yè)面創(chuàng)建一個(gè)啟動(dòng)配置,啟動(dòng)配置是開啟服務(wù)的配置,包含開啟服務(wù)所需要 CPU、內(nèi)存限制,啟動(dòng)參數(shù),服務(wù)器端口,以及運(yùn)行程序入口等。
在「啟動(dòng)配置」頁(yè)面,點(diǎn)擊「立即創(chuàng)建」:
填寫配置名稱,示例為 mvdemo-websocket ,大家也可自定義,點(diǎn)擊「創(chuàng)建」。
點(diǎn)擊「添加已有鏡像」:
在彈出窗口中,為當(dāng)前的啟動(dòng)配置添加鏡像時(shí),首先選擇需要使用的鏡像,在這里我們選擇 image1-websocket 。
然后設(shè)置鏡像啟動(dòng)參數(shù):
CPU核數(shù):游戲服務(wù)器根據(jù)需要,可自行設(shè)置 CPU 核數(shù)。Unity 程序運(yùn)行時(shí)我們推薦設(shè)置為 0.5 核以上。
啟動(dòng)超時(shí)時(shí)長(zhǎng):如果 Multiverse 在這個(gè)時(shí)長(zhǎng)內(nèi)未收到 GameServer 調(diào)用 MultiverseSdk.Ready() 的信號(hào), 就會(huì)標(biāo)記該 Allocation 為 failed 狀態(tài)。
入口程序啟動(dòng)命令:填 server.x86_64,該參數(shù)指的是服務(wù)器可執(zhí)行文件在 zip 包中的相對(duì)路徑。
服務(wù)器端口:使用 WebSocket 時(shí),選擇 TCP 協(xié)議。游戲服務(wù)器端口默認(rèn)將填寫端口 9998,我們修改端口為 7777,端口號(hào)只要和 Unity 編輯器中自定義的端口號(hào)保持一致即可。
填寫完后,點(diǎn)擊「添加鏡像配置」,將該鏡像添加至啟動(dòng)配置上。
3.5 測(cè)試并應(yīng)用鏡像配置
鏡像配置添加成功后,建議點(diǎn)擊「立即測(cè)試配置」,以確保程序在剛剛的配置下可以正常運(yùn)行。
測(cè)試成功后可選擇「下一步」,并點(diǎn)擊「應(yīng)用鏡像配置」,點(diǎn)擊完成,進(jìn)入 mvdemo-websocket 的詳情頁(yè)面。
在詳情頁(yè)面,可以進(jìn)行添加鏡像配置、更新啟動(dòng)參數(shù)、測(cè)試配置、刪除配置等一系列操作。
注意:此處不建議應(yīng)用測(cè)試失敗的鏡像配置,如鏡像配置測(cè)試運(yùn)行失敗,請(qǐng)查看日志信息,并嘗試修改啟動(dòng)參數(shù)來(lái)修復(fù)其中的錯(cuò)誤。測(cè)試服務(wù)可手動(dòng)關(guān)閉,若不關(guān)閉測(cè)試,系統(tǒng)將在一小時(shí)后清除運(yùn)行的測(cè)試實(shí)例。
應(yīng)用鏡像配置會(huì)將當(dāng)前鏡像啟動(dòng)參數(shù)所使用的配置版本替換為該鏡像配置。一個(gè)啟動(dòng)配置只會(huì)有唯一一個(gè)應(yīng)用狀態(tài)的鏡像配置。
3.6 配置 Matchmaking
Matchmaking 是 UOS 提供的用于多人聯(lián)機(jī)游戲的可定制配對(duì)服務(wù)。通過(guò) Matchmaking,您可以創(chuàng)建自定義的匹配規(guī)則,來(lái)定義您的多人聯(lián)機(jī)游戲中的游戲?qū)值耐婕医M成和陣營(yíng)劃分,以及在對(duì)局匹配的過(guò)程中應(yīng)用靈活的調(diào)整策略來(lái)對(duì)匹配規(guī)則進(jìn)行動(dòng)態(tài)更新。
當(dāng)前綁定的 UOS App 已經(jīng)啟用了「Matchmaking」功能了。需要注意的是,Matchmaking 作為 Multiverse 的支持“玩家匹配"的功能,依賴于 Multiverse 的「房間管理」功能。因此,對(duì)于沒(méi)有啟用「房間管理」功能的應(yīng)用,在啟用「Matchmaking」后,會(huì)自動(dòng)啟動(dòng)「房間管理」功能。
啟用「Matchmaking」功能后,可以進(jìn)入「Matchmaking」的頁(yè)面,點(diǎn)擊「立即創(chuàng)建」按鈕就可以開始創(chuàng)建 Match 配置。
大家自行填寫啟動(dòng)配置 mvdemo-websocket 對(duì)應(yīng)的 Match 配置的名稱,并且可以對(duì)「超時(shí)時(shí)長(zhǎng)」配置項(xiàng)進(jìn)行調(diào)整。這里我們先使用默認(rèn)的超時(shí)時(shí)長(zhǎng) 60 秒。
然后會(huì)進(jìn)入模板選擇頁(yè)面,我們基于市面上常見的游戲玩法及其匹配機(jī)制為用戶事先準(zhǔn)備了一系列的 Match 配置的模板。
在本教程中,我們選擇「自由休閑類」的「快速啟動(dòng)」模板為例進(jìn)行講解。選擇了模板后,點(diǎn)擊「創(chuàng)建」。
在 Match 配置的詳情頁(yè)中,大家可以對(duì)配置模板所提供的匹配信息進(jìn)行調(diào)整,也可以更換新的模板,或者是對(duì)相關(guān)配置項(xiàng)進(jìn)行調(diào)整。
teamDefinitions 聲明了一局游戲的隊(duì)伍規(guī)格和屬性,示例模板中定義的該匹配規(guī)則的含義是:隊(duì)伍名為 quick-game 的一局對(duì)局,最少玩家數(shù)為 1,最大玩家數(shù)為 4,如果達(dá)到了 minPlayers, 則可認(rèn)為該隊(duì)伍滿足了匹配條件。
3.7 開啟地域
到這里,大家已經(jīng)通過(guò)教程創(chuàng)建了一個(gè)已應(yīng)用鏡像、并可以正常運(yùn)行的啟動(dòng)配置了,接下來(lái)點(diǎn)擊側(cè)邊欄的「房間/服務(wù)器」??梢钥吹剑藭r(shí)尚未啟用任何地域,也未分配服務(wù)器,點(diǎn)擊「啟用」。
并填寫最大服務(wù)數(shù),可直接點(diǎn)擊「確認(rèn)」,則會(huì)以默認(rèn)值 10 為最大服務(wù)數(shù)完成開啟。
在啟用地域「上海」后,意味著您接下來(lái)的服務(wù)器可以創(chuàng)建在上海地區(qū),不同地域可以滿足不同的部署需求。
此時(shí)已經(jīng)可以先在 Unity 編輯器中,點(diǎn)擊運(yùn)行進(jìn)行測(cè)試。會(huì)看到客戶端日志輸出信息,說(shuō)明客戶端完成了初始化 matchmaking sdk 和執(zhí)行了玩家登陸的操作。
4. 使用 Matchmaking 實(shí)現(xiàn)對(duì)局匹配
接下來(lái)看看使用 Matchmaking 是如何實(shí)現(xiàn)對(duì)局匹配功能的!
4.1 創(chuàng)建 Ticket 實(shí)現(xiàn)輪詢匹配
繼續(xù)在腳本 BootstrapManager.cs 中添加 OnStartMatchButton 方法,來(lái)實(shí)現(xiàn)當(dāng)點(diǎn)擊「開始匹配」的按鈕時(shí)要響應(yīng)的事件。
定義變量 startButton,表示開始匹配的 UI 按鈕。當(dāng)玩家點(diǎn)擊開始匹配的 UI 按鈕時(shí),代碼會(huì)進(jìn)行一系列操作,包括更新按鈕文字和狀態(tài)。UI 上文字將顯示「匹配中......」,并且此時(shí)按鈕變?yōu)椴豢牲c(diǎn)擊的狀態(tài)。
當(dāng)玩家想要開啟一局匹配時(shí),就需要在客戶端發(fā)起一個(gè) Ticket。在這里,客戶端會(huì)根據(jù) Matchmaking 的匹配配置 Id,調(diào)用 CreateTicketAsync 方法來(lái)創(chuàng)建一個(gè) Ticket ,發(fā)起匹配請(qǐng)求。需要傳入兩個(gè)參數(shù):Matchmaking 的匹配配置 Id,以及玩家列表 List。
稍后會(huì)給大家詳細(xì)解釋開啟協(xié)程去輪詢 GetTicket 等待匹配結(jié)果的方法 WaitForMatchResult,我們同時(shí)會(huì)使用 try...catch... 來(lái)處理捕獲到的異常情況。
//需要新引入的 namespace--------------------------------------
using UnityEngine.UI;
using TMPro;
using Unity.UOS.Matchmaking.Model;
using System.Collections.Generic;
using Unity.UOS.Matchmaking.Exception;
//需要新引入的 namespace--------------------------------------
namespace Unity.UOS.Multiverse.Samples.NetCode
{
public class BootstrapManager : MonoBehaviour
{
// 填寫Matchmaking的配置列表中的配置ID
public string matchmakingConfigId;
// 填寫Matchmaking配置中的玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng)
public int matchmakingTimeoutSeconds = 120;
private readonly string _userId = Guid.NewGuid().ToString();
public Button startButton; //開始匹配的UI按鈕
private Coroutine matchCoroutine;
// 點(diǎn)擊開始匹配按鈕的響應(yīng)事件
public async void OnStartMatchButton()
{
// Change text to "匹配中......"
startButton.transform.GetChild(0).GetComponent ().text = "匹配中......";
// Change button to non-clickable state
startButton.interactable = false;
var player = new Player()
{
id = _userId
};
try
{
//創(chuàng)建一個(gè) Ticket 開始匹配,傳入?yún)?shù):Matchmaking匹配配置Id,玩家列表
var ticketId =
await MatchmakingSDK.Instance.CreateTicketAsync(matchmakingConfigId, new List { player });
// 開啟一個(gè) coroutine 去輪詢 GetTicket 等待匹配結(jié)果
matchCoroutine ??= StartCoroutine(WaitForMatchResult(ticketId));
}
catch (MatchmakingClientException ex)
{
Debug.LogErrorFormat("Failed to create ticket, clientEx: {0}", ex);
throw;
}
catch (MatchmakingServerException ex)
{
Debug.LogErrorFormat("Failed to create ticket, serverEx: {0}", ex);
throw;
}
}
}
}
此時(shí)也是需要引入系統(tǒng)自帶的 TextMeshPro Package 資源包的應(yīng)用程序集,添加引用后,點(diǎn)擊「Apply」。
接著,我們要開啟一個(gè) coroutine 去輪詢 GetTicket 等待匹配結(jié)果了,封裝協(xié)程方法 WaitForMatchResult 來(lái)實(shí)現(xiàn)該功能。
將根據(jù)配置的玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng),也就是參數(shù) matchmakingTimeoutSeconds 設(shè)置的時(shí)長(zhǎng),每間隔 1 秒調(diào)用一次自定義封裝的 GetTicketAsync 方法,來(lái)輪詢 GetTicket 等待匹配結(jié)果。
添加異步方法 GetTicketAsync ,在方法內(nèi)實(shí)現(xiàn)查詢指定 tickerId 的 Ticket 信息。
每次查詢 Ticket 信息時(shí),要等待任務(wù)完成,如果檢查到任務(wù)出錯(cuò),就拋出異常并跳出循環(huán)。
然后獲取任務(wù)的結(jié)果,為了方便調(diào)試,我們可以打印一下當(dāng)前 Ticket 的狀態(tài)。如果檢查到 Ticket 的狀態(tài)為匹配完成或者錯(cuò)誤,則跳出循環(huán)、退出輪詢。
//需要新引入的 namespace--------------------------------------
using System.Collections;
using System.Threading.Tasks;
//-----------------------------------------------------------
namespace Unity.UOS.Multiverse.Samples.NetCode
{
private IEnumerator WaitForMatchResult(string ticketId)
{
// 輪詢 GetTicket 等待匹配結(jié)果
Ticket ticket = null;
for (var i = 0; i < matchmakingTimeoutSeconds; i++)
{
var task = GetTicketAsync(ticketId);//查詢指定 ticket id 的詳細(xì)信息
yield return new WaitUntil(() => task.IsCompleted);
if (task.IsFaulted)
{
if (task.Exception != null) throw task.Exception;
break;
}
ticket = task.Result;
Debug.Log($"Current ticket status: {ticket.status}");
if (ticket.status is MatchmakingSDK.TicketStatusMatched or MatchmakingSDK.TicketStatusError)
{
break;
}
// 延時(shí)1秒
yield return new WaitForSeconds(1);
}
}
// 查詢指定 ticket id 的詳細(xì)信息
private async Task GetTicketAsync( string ticketId)
{
try
{
return await MatchmakingSDK.Instance.GetTicketAsync(ticketId);
}
catch (MatchmakingClientException ex)
{
Debug.LogErrorFormat("Failed to get ticket, clientEx: {0}", ex);
throw;
}
catch (MatchmakingServerException ex)
{
Debug.LogErrorFormat("Failed to get ticket, serverEx: {0}", ex);
throw;
}
}
}
如果輪詢 Ticket 的過(guò)程中,捕獲到異常,需要做一些清除資源的操作。所以,封裝一個(gè)方法 Cleanup,來(lái)處理遇到異常時(shí)停止協(xié)同程序的執(zhí)行。在代碼中添加調(diào)用 Cleanup 方法的地方:
private void Cleanup()
{
if (matchCoroutine == null) return;
StopCoroutine(matchCoroutine);
matchCoroutine = null;
}
代碼中添加調(diào)用 Cleanup 方法的地方:
在 OnStartMatchButton 方法的 Client /Server 異常時(shí),添加調(diào)用 Cleanup 方法;
在 GetTicketAsync 方法的 Client/Server 異常時(shí),也添加調(diào)用 Cleanup 方法;
在 WaitForMatchResult 方法的輪詢匹配時(shí),如果異步任務(wù)出錯(cuò)(task.IsFaulted),則調(diào)用 Cleanup 方法停止協(xié)程函數(shù)的調(diào)用。
// 點(diǎn)擊開始匹配按鈕的響應(yīng)事件
public async void OnStartMatchButton()
{
//此處省略其它代碼行......
try
{
//此處省略其它代碼行......
}
catch (MatchmakingClientException ex)
{
Debug.LogErrorFormat("Failed to create ticket, clientEx: {0}", ex);
Cleanup();
throw;
}
catch (MatchmakingServerException ex)
{
Debug.LogErrorFormat("Failed to create ticket, serverEx: {0}", ex);
Cleanup();
throw;
}
}
// 查詢指定 ticket id 的詳細(xì)信息
private async Task GetTicketAsync( string ticketId)
{
try
{
return await MatchmakingSDK.Instance.GetTicketAsync(ticketId);
}
catch (MatchmakingClientException ex)
{
Debug.LogErrorFormat("Failed to get ticket, clientEx: {0}", ex);
Cleanup();
throw;
}
catch (MatchmakingServerException ex)
{
Debug.LogErrorFormat("Failed to get ticket, serverEx: {0}", ex);
Cleanup();
throw;
}
}
private IEnumerator WaitForMatchResult(string ticketId)
{
// 輪詢 GetTicket 等待匹配結(jié)果
Ticket ticket = null;
for (var i = 0; i < matchmakingTimeoutSeconds; i++)
{
var task = GetTicketAsync(ticketId);//查詢指定 ticket id 的詳細(xì)信息
yield return new WaitUntil(() => task.IsCompleted);
if (task.IsFaulted)
{
Cleanup();
if (task.Exception != null) throw task.Exception;
break;
}
//此處省略其它代碼行......
}
}
添加完代碼,接著找到場(chǎng)景中的開始匹配的 UI 按鈕對(duì)象 Button_StartMatch,要確保此按鈕已經(jīng)在 Inspector 面板上注冊(cè)綁定了:BootstrapManager 對(duì)象上的腳本 BootstrapManager.cs 中的 OnStartMatchButton 方法。
回到 UOS 網(wǎng)頁(yè)端,找到「Matchmaking」→「配置列表」,復(fù)制 mvdemo-websocket 下方的 Matchmaking 配置 ID 號(hào)。
回到 Unity 編輯器,在 Inspector 面板上的 BootBootstrapManager 組件這里,有幾個(gè)參數(shù)需要我們?cè)O(shè)置:
將剛才 UOS 網(wǎng)頁(yè)端復(fù)制的 Matchmaking 配置 ID ,粘貼到 matchmakingConfigId 參數(shù)這里。
matchmakingTimeoutSeconds 參數(shù),在這里也是需要和 UOS 網(wǎng)頁(yè)端配置的玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng)(60 秒)保持一致。如果在 60 秒結(jié)束之后 Ticket 仍未匹配成功,將被標(biāo)記為匹配失敗。
StartButton:指定為場(chǎng)景中的按鈕 Button_StartMatch。
繼續(xù)點(diǎn)擊運(yùn)行項(xiàng)目后,查看控制臺(tái)輸出的日志信息,可以看到當(dāng)前 Tickcet 狀態(tài)的變化:由 created → awaitingAssignment → matched。
Ticket 共有以下狀態(tài):
匹配已創(chuàng)建 (created):Ticket 已成功創(chuàng)建,并處于正在匹配玩家的狀態(tài)。
匹配成功,正在創(chuàng)建房間 (awaitingAssignment): Ticket 已匹配成功,系統(tǒng)正在為該局游戲創(chuàng)建房間。
匹配成功,且創(chuàng)建房間完成 (matched): 匹配成功并已能夠拿到 IP 和 PORT 等對(duì)局信息,可連接到戰(zhàn)斗服開始游戲。
匹配失敗 (error): 匹配超時(shí)(匹配時(shí)長(zhǎng)超出玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng))或是 Ticket 不符合規(guī)則被標(biāo)記非法 Ticket。
Ticket 狀態(tài)轉(zhuǎn)換關(guān)系如下圖:
4.2 匹配成功后,獲取匹配到的服務(wù)器的 IP 地址和端口號(hào)
接下來(lái)處理匹配成功后的邏輯!
在這里我們先使用 WebSocket 協(xié)議進(jìn)行測(cè)試,代碼中如果缺少引用會(huì)報(bào)錯(cuò)的,我們先提前「引入 WebSocket Transport for Netcode 資源包的應(yīng)用程序集」。添加引用后,點(diǎn)擊「Apply」按鈕。
在腳本中定義方法 HandleMatchedTicket 來(lái)處理匹配到的 Ticket。
當(dāng) Ticket 的狀態(tài)為匹配成功時(shí),會(huì)先解析匹配到的房間端口信息(ticket.assignment.gamePorts),得到當(dāng)前匹配到的服務(wù)器的端口號(hào) port。
然后根據(jù)所使用的網(wǎng)絡(luò)傳輸協(xié)議 NetworkTransport 類型(如 UOS 提供的 KCP 或 WebSocket 協(xié)議)進(jìn)行相應(yīng)配置,以便客戶端能連接到匹配的服務(wù)器。
當(dāng)使用 WebSocket 協(xié)議時(shí),先獲取到 WebSocketTransport 組件,然后再將匹配到的服務(wù)器的 IP 地址和端口,填充到 WebSocketTransport 組件上的參數(shù) ConnectAddress 和 Port 。
//需要新引入的 namespace-----------------------------------
using Netcode.Transports.WebSocket;
//--------------------------------------------------------
private void HandleMatchedTicket(Ticket ticket)
{
//ticket匹配到的房間端口,形如 port-name / port
//比如當(dāng)Multiverse啟動(dòng)配置只填了一個(gè)端口時(shí):websocket / 7654; 配置了多個(gè)端口時(shí):websocket / 7654,kcp / 7655
//這里的代碼示例是以只填了一個(gè)端口為例
var ports = ticket.assignment.gamePorts.Split(",");
if (ports.Length != 1)
{
throw new Exception("Unexpected number of ports");
}
var portInfo = ports[0].Split("/");
if (portInfo.Length != 2)
{
throw new Exception("Unexpected number of port info");
}
var port = ushort.Parse(portInfo[1]);
switch (NetworkManager.Singleton.NetworkConfig.NetworkTransport)
{
case WebSocketTransport:
{
//如果使用 websocket 協(xié)議,獲取 WebSocketTransport 組件
var wt = NetworkManager.Singleton.GetComponent ();
if (wt == null)
{
throw new Exception("Unexpected null WebSocketTransport");
}
//填充匹配到的服務(wù)器的ip地址和端口到 WebSocketTransport 組件
Debug.Log("Using WebSocket transport");
wt.ConnectAddress = ticket.assignment.ip;
wt.Port = port;
break;
}
default:
throw new Exception("No suitable transport found");
}
}
然后我們找到之前封裝過(guò)的 WaitForMatchResult 方法,添加判斷:如果匹配成功時(shí),調(diào)用處理匹配結(jié)果的方法 HandleMatchedTicket。如果捕獲到異常時(shí),就清除相關(guān)的資源。
private IEnumerator WaitForMatchResult(string ticketId)
{
// 輪詢 GetTicket 等待匹配結(jié)果
Ticket ticket = null;
for (var i = 0; i < matchmakingTimeoutSeconds; i++)
{
//此處省略其它代碼行......
}
if (ticket is { status: MatchmakingSDK.TicketStatusMatched })
{
Debug.Log("匹配成功");
try
{
// 處理匹配結(jié)果
HandleMatchedTicket(ticket);
}
catch (Exception)
{
Cleanup();
throw;
}
}
//此處省略其它代碼行......
}
再次運(yùn)行游戲后進(jìn)行測(cè)試,查看日志信息會(huì)輸出:"Using WebSocket transport",說(shuō)明使用了 WebSocket 協(xié)議。同時(shí)可以查看下 WebSocketTransport 組件的 Connect Address 和 Port 參數(shù),已經(jīng)發(fā)生了變化。
刷新 UOS 網(wǎng)頁(yè)端的服務(wù)器列表,可以看到自動(dòng)創(chuàng)建的服務(wù)器信息。點(diǎn)擊服務(wù)器 UUID 值查看服務(wù)器詳情,可以獲取到服務(wù)器的 IP 與端口(使用 Multiverse 映射后的端口)。
配置中的服務(wù)器端口為內(nèi)部監(jiān)聽端口,連接時(shí)會(huì)使用 Multiverse 映射后的端口。
如下圖,配置時(shí)填的服務(wù)器監(jiān)聽端口為 7777,但是連接時(shí)將使用映射后的端口 7005 進(jìn)行連接。
繼續(xù)在 HandleMatchedTicket 方法中添加代碼:
處理下當(dāng)匹配成功后,開啟客戶端連接到 Multiverse 服務(wù)端,并將開始匹配的 UI 按鈕隱藏,在場(chǎng)景中會(huì)出現(xiàn)一個(gè) Sphere 小球?qū)ο蠛鸵粋€(gè)可以控制隨機(jī)小球位置的按鈕。
在這里,我們定義一個(gè)變量 randomTeleport 表示隨機(jī)小球位置的 UI 按鈕。
public Button randomTeleport;//隨機(jī)位置的UI按鈕
private void HandleMatchedTicket(Ticket ticket)
{
//此處省略其它代碼行......
switch (NetworkManager.Singleton.NetworkConfig.NetworkTransport)
{
//此處省略其它代碼行......
}
// 開啟客戶端,連接到server
NetworkManager.Singleton.StartClient();
//隱藏匹配的按鈕
startButton.gameObject.SetActive(false);
//激活隨機(jī)位置的按鈕
if (NetworkManager.Singleton.IsClient && NetworkManager.Singleton.LocalClient != null)
{
randomTeleport.gameObject.SetActive(true);
}
}
BootstrapManager.cs 組件上的參數(shù) Random Teleport 選擇提前創(chuàng)建好的按鈕對(duì)象:Button_RandomTeleport。
此時(shí)再次運(yùn)行游戲后,看到在 Game 窗口中出現(xiàn)了一個(gè) Sphere 對(duì)象,說(shuō)明客戶端連接上了服務(wù)器。
4.4 匹配失敗后,則重新匹配
如果匹配失敗,則重新開始剛才的匹配過(guò)程。在 WaitForMatchResult 方法中,添加重新開始匹配的代碼,同時(shí)會(huì)調(diào)用 Cleanup 方法來(lái)停止當(dāng)前的匹配操作。
private IEnumerator WaitForMatchResult(string ticketId)
{
Ticket ticket = null;
for (var i = 0; i < matchmakingTimeoutSeconds; i++)
{
//此處省略其它代碼行......
}
if (ticket is { status: MatchmakingSDK.TicketStatusMatched })
{
//此處省略其它代碼行......
}
else
{
if (ticket != null) Debug.Log($"匹配失敗: {ticket.assignment.msg}");
// Restore button to matchable state
startButton.transform.GetChild(0).GetComponent ().text = "開始匹配";
startButton.interactable = true;
// Match failed, throw an exception
Cleanup();
throw new Exception("Match failed");
}
}
4.5 匹配成功后,在客戶端隨機(jī)小球?qū)ο蟮奈恢?/strong>
繼續(xù)打開腳本 BootstrapManager.cs,添加新的方法 OnRandomTeleportButton,判斷下如果本地客戶端的玩家對(duì)象包含 BootstrapPlayer 組件,則會(huì)從客戶端向服務(wù)器端發(fā)送一個(gè)遠(yuǎn)程過(guò)程調(diào)用(Server RPC),目的是將玩家傳送到服務(wù)器端的一個(gè)隨機(jī)位置。
//當(dāng)點(diǎn)擊隨機(jī)位置的按鈕響應(yīng)的事件
public void OnRandomTeleportButton()
{
if (NetworkManager.Singleton.LocalClient.PlayerObject.TryGetComponent(out BootstrapPlayer bootstrapPlayer))
{
// Invoke a `ServerRpc` from client-side to teleport player to a random position on the server-side
bootstrapPlayer.RandomTeleportServerRpc();
}
}
有需要的話,可自行查看下 Netcode 示例 Demo 提供的隨機(jī)位置的腳本 BootstrapPlayer.cs。這段代碼定義了一個(gè)服務(wù)器遠(yuǎn)程過(guò)程調(diào)用(Server RPC)方法 RandomTeleportServerRpc,用于將調(diào)用該方法的游戲?qū)ο蟆魉偷?XY 平面上的一個(gè)隨機(jī)位置。
public class BootstrapPlayer : NetworkBehaviour
{
[ServerRpc]
public void RandomTeleportServerRpc()
{
var oldPosition = transform.position;
transform.position = GetRandomPositionOnXYPlane();
var newPosition = transform.position;
print($"{nameof(RandomTeleportServerRpc)}() -> {nameof(OwnerClientId)}: {OwnerClientId} --- {nameof(oldPosition)}: {oldPosition} --- {nameof(newPosition)}: {newPosition}");
}
private static Vector3 GetRandomPositionOnXYPlane()
{
return new Vector3(Random.Range(-3f, 3f), Random.Range(-3f, 3f), 0f);
}
}
查看場(chǎng)景中隨機(jī)位置的 UI 按鈕 Button_RandomTeleport,確保它已經(jīng)提前注冊(cè)綁定了方法 OnRandomTeleportButton。
然后大家可以再次點(diǎn)擊運(yùn)行測(cè)試,匹配成功以后,就可以隨機(jī)小球的位置了。
4.6 啟用 Websocket Proxy
如果想要在微信小游戲中使用 WebSocket 協(xié)議進(jìn)行通信,需要「聯(lián)系我們」,為你的項(xiàng)目開通「啟用 Websocket Proxy」功能。
在 HandleMatchedTicket 方法中添加判斷的代碼,若開啟了 WebSocket 代理選項(xiàng),系統(tǒng)會(huì)自動(dòng)在 WebSocketTransport 組件的 “Proxy” 這一相關(guān)配置字段中填入 WebSocket 代理服務(wù)器的地址。這一過(guò)程無(wú)需開發(fā)人員手動(dòng)輸入,系統(tǒng)將自動(dòng)完成配置。
如此一來(lái),在進(jìn)行 WebSocket 通信時(shí),便能正確連接到指定的代理服務(wù)器,進(jìn)而實(shí)現(xiàn)預(yù)期的網(wǎng)絡(luò)通信和功能邏輯。
private void HandleMatchedTicket(Ticket ticket)
{
//此處省略其它代碼行......
switch (NetworkManager.Singleton.NetworkConfig.NetworkTransport)
{
case WebSocketTransport:
{
//如果使用 websocket 協(xié)議,獲取 WebSocketTransport 組件
var wt = NetworkManager.Singleton.GetComponent ();
if (wt == null)
{
throw new Exception("Unexpected null WebSocketTransport");
}
//填充匹配到的服務(wù)器的ip地址和端口到 WebSocketTransport 組件
Debug.Log("Using WebSocket transport");
wt.ConnectAddress = ticket.assignment.ip;
wt.Port = port;
if (!string.IsNullOrEmpty(ticket.assignment.wsProxy))
{
// 如果聯(lián)系我們配置了開啟websocket代理, 則填充代理地址到 WebSocketTransport 組件
Debug.Log("Using WebSocket proxy");
wt.Proxy = ticket.assignment.wsProxy;
wt.SecureConnection = true;
}
break;
}
}
}
找到場(chǎng)景中的 BootstrapManager 對(duì)象上的 WebSocket Transport 組件,勾選「Secure Connection」選項(xiàng)。
如果你的項(xiàng)目已經(jīng)開啟 Websocket 代理, 運(yùn)行項(xiàng)目后,會(huì)看到 WebSocketTransport 組件的 Proxy 參數(shù)上,已經(jīng)自動(dòng)填充了代理地址。
4.7 增加關(guān)閉的按鈕
可以在場(chǎng)景中增加一個(gè) Close 的關(guān)閉,隨時(shí)來(lái)關(guān)閉場(chǎng)景重新運(yùn)行匹配功能。大家自行根據(jù)自己的需求,選擇是否添加這個(gè)按鈕。
創(chuàng)建一個(gè)新的按鈕,重命名為 Button_Close,可自行設(shè)置按鈕上的貼圖和顏色。
將按鈕上的 Text 文字設(shè)置為 X。
腳本中添加 OnClickCloseButton 方法,主要功能是點(diǎn)擊關(guān)閉按鈕時(shí),進(jìn)行資源清理、關(guān)閉網(wǎng)絡(luò)管理器并銷毀其對(duì)應(yīng)的游戲?qū)ο?,最后異步加載索引為 0 的場(chǎng)景,以實(shí)現(xiàn)場(chǎng)景的切換和資源的釋放。
//需要新引入的 namespace------------------------------------
using UnityEngine.SceneManagement;
//---------------------------------------------------------
public void OnClickCloseButton()
{
Cleanup();
if (NetworkManager.Singleton)
{
NetworkManager.Singleton.Shutdown();
Destroy(NetworkManager.Singleton.gameObject);
}
SceneManager.LoadSceneAsync(0, LoadSceneMode.Single);
}
找到場(chǎng)景中的關(guān)閉的 UI 按鈕對(duì)象 Button_Close,要確保此按鈕已經(jīng)在 Inspector 面板上注冊(cè)綁定了:BootstrapManager 對(duì)象上的腳本 BootstrapManager.cs 中的 OnStartMatchButton 方法。
可以運(yùn)行場(chǎng)景,再次進(jìn)行測(cè)試效果。
5. 使用 KCP 協(xié)議配置并啟動(dòng)項(xiàng)目
為助力開發(fā)者在微信小游戲平臺(tái)實(shí)現(xiàn)更優(yōu)網(wǎng)絡(luò)通信體驗(yàn),我們特別提供 KCP 協(xié)議。針對(duì)微信獨(dú)特的 UDP 通信環(huán)境,我們進(jìn)行了深度適配工作,成功讓 KCP 協(xié)議無(wú)縫融入其中,保障其在微信小游戲平臺(tái)穩(wěn)定且高效地運(yùn)行。
5.1 選擇 KCP 協(xié)議
我們?cè)賮?lái)測(cè)試下使用 KCP 協(xié)議來(lái)運(yùn)行時(shí)所需要的相關(guān)配置!
再次找到場(chǎng)景中的對(duì)象 BootstrapManager,給其添加 Kcp 2k Transport 協(xié)議腳本組件 ,同時(shí)將 NetworkManager 組件上的 NetworkTransport 參數(shù)選擇的協(xié)議修改為:Kcp 2k Transport 協(xié)議。
5.2 構(gòu)建并上傳 Multiverse 鏡像
更換協(xié)議之后,我們按照之前的步驟,重新構(gòu)建新的服務(wù)器鏡像來(lái)測(cè)試。
鏡像標(biāo)簽 Image Tag 設(shè)置為:image2-kcp,然后點(diǎn)擊「Build Image」按鈕。
在 UOS 網(wǎng)頁(yè)端的「啟動(dòng)配置」→「鏡像」這里,可以看到剛才構(gòu)建的服務(wù)端程序 image2-kcp,已經(jīng)自動(dòng)上傳了。
5.3 創(chuàng)建啟動(dòng)配置
多套啟動(dòng)配置可以方便管理同一服務(wù)器程序?qū)τ诓煌?wù)所需的啟動(dòng)參數(shù),CPU 消耗等不同配置的需求。
同理參考使用 WebSocket Transport 協(xié)議時(shí)的操作步驟,再創(chuàng)建一個(gè)使用 KCP 協(xié)議的啟動(dòng)配置。在「啟動(dòng)配置」頁(yè)面,點(diǎn)擊「創(chuàng)建啟動(dòng)配置」:
填寫配置名稱,示例為 mvdemo2-kcp,然后點(diǎn)擊「創(chuàng)建」。
點(diǎn)擊「添加已有鏡像」:
接著為啟動(dòng)配置 mvdemo2-kcp 添加鏡像配置:
選擇鏡像:image2-kcp;
CPU核數(shù):Unity 程序運(yùn)行時(shí)我們還是推薦設(shè)置為 0.5 核以上;
入口程序啟動(dòng)命令:填 server.x86_64;
服務(wù)器端口:使用 KCP 協(xié)議時(shí),服務(wù)器協(xié)議選擇 UDP 協(xié)議。游戲服務(wù)器端口我們還是和 Unity 編輯器中自定義的端口號(hào)保持一致(7777)即可。
填寫完后,點(diǎn)擊「添加鏡像配置」,將該鏡像添加至啟動(dòng)配置上。
5.4 測(cè)試并應(yīng)用鏡像配置
接著點(diǎn)擊「立即測(cè)試配置」,然后點(diǎn)擊「下一步」→「立即應(yīng)用配置」即可。
5.5 配置 Matchmaking
進(jìn)入「Matchmaking」的「配置列表」頁(yè)面,點(diǎn)擊「創(chuàng)建 Match」按鈕。
這里「Match 配置名稱」就先填寫 :mvdemo2-kcp 了,「玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng)」也設(shè)置為默認(rèn) 60 秒。
Match 模板還是選擇「自由休閑類」的「快速啟動(dòng)」模板為例進(jìn)行講解。選擇了模板后,點(diǎn)擊「創(chuàng)建」。
在 Match 配置的詳情頁(yè)中,這里的匹配信息和之前一樣,不再重復(fù)解釋了。
5.6 獲取匹配到的服務(wù)器的 IP 地址和端口
此時(shí)也是需要先設(shè)置下,引入 Kcp Transport for Netcode 資源包的應(yīng)用程序集。當(dāng)添加引用后,點(diǎn)擊「Apply」。
找到腳本中的 HandleMatchedTicket 方法,繼續(xù)判斷:如果使用的是 KCP 協(xié)議的話,同理也是通過(guò) Ticket 拿到匹配到的服務(wù)器的 IP 地址和端口號(hào),然后填充到 Kcp Transport 組件上的參數(shù) Host 和 Port。
//需要新引入的 namespace------------------------------------
using Netcode.Transports.KCP;
//--------------------------------------------------------
private void HandleMatchedTicket(Ticket ticket)
{
//此處省略其它代碼行......
switch (NetworkManager.Singleton.NetworkConfig.NetworkTransport)
{
case Kcp2kTransport:
{
//如果使用 kcp 協(xié)議,獲取 Kcp2kTransport 組件
var kt = NetworkManager.Singleton.GetComponent ();
if (kt == null)
{
throw new Exception("Unexpected null Kcp2kTransport");
}
Debug.Log("Using KCP transport");
//填充匹配到的服務(wù)器的ip地址和端口到 Kcp2kTransport 組件
kt.Port = port;
kt.Host = ticket.assignment.ip;
break;
}
case WebSocketTransport:
{
//此處省略其它代碼行......
break;
}
default:
throw new Exception("No suitable transport found");
}
//此處省略其它代碼行......
}
進(jìn)入 UOS 網(wǎng)頁(yè)端的「Matchmaking」→「配置列表」頁(yè)面,找到新創(chuàng)建的 Matchmaking 配置:mvdemo2-kcp,復(fù)制 mvdemo2-kcp 下方的 Matchmaking 配置 ID 號(hào)。
將剛才 UOS 網(wǎng)頁(yè)端復(fù)制的 Matchmaking 配置 ID ,粘貼到 matchmakingConfigId 參數(shù)這里。
matchmakingTimeoutSeconds 參數(shù):還是和 Matchmaking 配置中的玩家匹配請(qǐng)求超時(shí)時(shí)長(zhǎng)保持一致寫 60 秒。
再次運(yùn)行游戲后進(jìn)行測(cè)試,查看日志信息會(huì)輸出:"Using KCP transport",說(shuō)明使用了 KCP 協(xié)議。
同時(shí)看到 Kcp 2k Transport 組件的 Host 和 Port 參數(shù),也已經(jīng)自動(dòng)填充了匹配到的服務(wù)器的 IP 地址和 Multiverse映射后的端口號(hào)。
再次刷新 UOS 網(wǎng)頁(yè)端的服務(wù)器列表,點(diǎn)擊服務(wù)器 UUID 值查看服務(wù)器詳情,可以看到 Unity 編輯器 Inspector 面板上的服務(wù)器的 IP 與端口,和網(wǎng)頁(yè)端的是一致的。
此時(shí),Game 窗口的畫面如下,可以正常隨機(jī)位置。
6. 構(gòu)建 MiniGame 項(xiàng)目,設(shè)置 WXSDK 打包參數(shù)
接下來(lái)我們將該 Demo 發(fā)布成微信小游戲,接著剛才的 KCP 協(xié)議繼續(xù)操作。使用 Unity 引擎發(fā)布成微信小游戲的話,需要安裝 WebGL 平臺(tái)支持模塊包和 WXSDK,然后再構(gòu)建小游戲項(xiàng)目工程。
6.1 安裝 WebGL 平臺(tái)支持模塊包,將項(xiàng)目切換至 WebGL 平臺(tái)
再次打開 Unity Hub ,找到左側(cè)的「安裝」,選擇自己使用的 Unity 編輯器版本,比如 2022.3.53f1c1,點(diǎn)擊「添加模塊」按鈕:
在彈出窗口中「勾選 WebGL Build Support 」模塊,然后點(diǎn)擊「安裝」。
在 UOS Launcher 面板中勾選 Weixin Minigame 選項(xiàng),點(diǎn)擊彈窗中的按鈕「Switch to WebGL」。
然后打開「File」→「Build Settings」窗口,可以看到勾選 Weixin Minigame 選項(xiàng)后,Platform 已經(jīng)自動(dòng)幫你切換至「WebGL」平臺(tái)了。
切換平臺(tái)后,需要重新檢查下你的 UOS Launcher 面板,查看 UOS App 是否綁定。貼圖的壓縮模式 Texture Compression:選擇「ASTC」。
6.2 安裝 WXSDK
在 Unity Editor 菜單欄,點(diǎn)擊「Window」→「Package Manager」,點(diǎn)擊左上角的「+」,選擇「Add package from git URL...」,輸入下方的 WXSDK 的倉(cāng)庫(kù) Git 資源地址,點(diǎn)擊「Add」即可。
WXSDK 的倉(cāng)庫(kù) Git 地址:
https://gitee.com/wechat-minigame/minigame-tuanjie-transform-sdk.git
等待 WXSDK 安裝好以后,可以在「Package Manager」窗口中看到。
6.3 設(shè)置 WXSDK 的參數(shù),構(gòu)建小游戲項(xiàng)目工程
安裝好 WXSDK 后,點(diǎn)擊菜單欄的按鈕「微信小游戲」→「轉(zhuǎn)換小游戲」按鈕。
然后設(shè)置微信小游戲轉(zhuǎn)換工具面板的參數(shù),如下面的截圖所示:
游戲AppID:可以直接從微信小游戲開發(fā)工具里點(diǎn)擊 register 去申請(qǐng)正式 AppID。注冊(cè)過(guò)賬號(hào)以后,可以在「開發(fā)管理」→「開發(fā)設(shè)置」這里看到自己的 AppID 賬號(hào),復(fù)制一下 AppID 號(hào)填入 WXSDK 的參數(shù)中。
注冊(cè)鏈接:https://mp.weixin.qq.com/wxopen/waregister?action=step1
第一次注冊(cè)好的 AppID 賬號(hào),需要進(jìn)行設(shè)置下是用于「小程序開發(fā)」還是「小游戲開發(fā)」。
設(shè)置修改:登錄小程序頁(yè)面后,選擇「首頁(yè)」→「小程序發(fā)布流程」→「小程序類目」,然后點(diǎn)擊填寫添加服務(wù)類目,選擇「游戲」類目,自行選擇一種游戲類型即可,在這里我設(shè)置的是「休閑游戲」類型。
小游戲項(xiàng)目名:自定義填寫導(dǎo)出的微信小游戲項(xiàng)目名稱,這里先寫 MultiverseAndNetcode_MiniGame。
游戲方向:選擇根據(jù)游戲畫面選擇豎屏還是橫屏,這里選擇橫屏 Landsape。
導(dǎo)出路徑:這里大家自行來(lái)設(shè)置生成微信小游戲工程的位置,后續(xù)步驟會(huì)需要打開這里的小游戲目錄。
首包資源加載方式:由于當(dāng)前項(xiàng)目只用到了 Multiverse 和 Matchmaking 服務(wù),沒(méi)有使用到 CDN 服務(wù),所以先將首包的資源加載方式設(shè)置為「小游戲包內(nèi)」加載。
壓縮首包資源:由于場(chǎng)景中 UI 用到了背景圖資源,卻未使用 CDN 上傳資源,直接打包的話會(huì)提示首包資源太大。所以我們這里可以勾選下「壓縮首包資源」選項(xiàng)。
WebGL2.0(beta):這里參數(shù)打勾。
顯示優(yōu)化建議彈窗:這里先不打勾,后面在微信開發(fā)者工具中運(yùn)行測(cè)試項(xiàng)目時(shí),就暫時(shí)不讓它自動(dòng)彈出優(yōu)化提示的窗口。如果需要時(shí),可以再勾選。
最后,點(diǎn)擊「生成并轉(zhuǎn)換」的按鈕,開始打包項(xiàng)目,等到轉(zhuǎn)換成功后,會(huì)看到小游戲?qū)С雎窂较碌奈募鐖D所示:
7. 在微信開發(fā)者工具運(yùn)行測(cè)試
7.1 下載微信開發(fā)者工具
如果你還沒(méi)有下載過(guò)微信開發(fā)者工具的話,可以前往微信官方網(wǎng)站進(jìn)行下載微信開發(fā)者工具, 并安裝到本地電腦上。
https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
7.2 在微信開發(fā)者工具中導(dǎo)入小游戲項(xiàng)目
打開安裝好的微信開發(fā)者工具,選擇「小游戲」類型,導(dǎo)入你的項(xiàng)目。「目錄」這里選擇之前在 WXSDK 的參數(shù)面板中設(shè)置的導(dǎo)出路徑:
MultiverseAndNetcode_minigame/minigame 路徑文件夾。
注意不要選錯(cuò)了目錄哦!路徑一定要選擇到 minigame 路徑文件夾?。?!
當(dāng)選擇完目錄以后,會(huì)自動(dòng)加載填入你在 Unity 編輯器內(nèi)設(shè)置的導(dǎo)出的小游戲項(xiàng)目名稱的,不需要自己填寫。然后選擇你的 AppID,最后點(diǎn)擊「確定」按鈕即可。
接著在 Unity 編輯器里和微信開發(fā)者工具中,我們都點(diǎn)擊「開始匹配」按鈕,匹配成功后,會(huì)看到都出現(xiàn)了 Sphere 游戲?qū)ο?,點(diǎn)擊隨機(jī)位置,可以看到這兩個(gè)對(duì)象在場(chǎng)景中的 Transform 組件的數(shù)據(jù)信息都是同步的。
7.3 設(shè)置域名
如果此時(shí),想要在手機(jī)上掃描二維碼進(jìn)行真機(jī)預(yù)覽小游戲效果的話,還需要進(jìn)行相關(guān)域名的設(shè)置。如果大家想知道自己缺少的域名,可以在微信開發(fā)者工具里強(qiáng)制開啟白名單調(diào)試:
打開「設(shè)置->項(xiàng)目設(shè)置」面板,選擇「本地設(shè)置」,取消勾選”不校驗(yàn)合法域名、網(wǎng)絡(luò)視圖(業(yè)務(wù)域名)、TLS 版本以及 HTTPS 證書“(默認(rèn)是勾選狀態(tài),表示默認(rèn)不進(jìn)行校驗(yàn)域名)。
可以進(jìn)入 UOS 官網(wǎng),在網(wǎng)頁(yè)上的 QA 的「4 .小程序/小游戲需要用到的域名白名單」這里,找到項(xiàng)目中使用到的 服務(wù)對(duì)應(yīng)的域名,然后復(fù)制域名。 https://uos.unity.cn/doc/others/qa#4
Matchmaking 服務(wù)對(duì)應(yīng)的域名——https://m.unity.cn
Passp...
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.