Unity Mirror 多人同步 基礎教程

Unity Mirror 多人同步 基礎教程

  • Mirror
    • NetworkManager(網絡管理器)
      • Configuration:配置
      • Auto-Start Options:自動啟動
      • Scene Management:場景管理
      • Network Info:網絡信息
      • Authentication:身份驗證
      • Player Object:玩家對象
      • Security:安全
      • Snapshot Interpolation:快照插值
      • Connection Quality:連接質量
      • Interpolation:UI 插值調試 UI
    • KcpTransport(KCP 通信協議)
      • Transport Configuration:通信配置
      • Advanced:高級設置
      • Allowed Max Message Sizes:允許的最大消息大小
      • Debug:調試
    • NetworkManagerHUD(網絡管理器 HUD)
      • Offset X / Offset Y:畫面偏移
      • 主要方法
    • NetworkStartPosition(玩家出生點位置)
    • NetworkIdentity(網絡“身份證”)
      • Server Only:服務器端
      • Visibility:可見性
    • NetworkTransformReliable(網絡同步器 穩定版)
      • Target:同步物體
      • Selective Sync:選擇性同步
      • Bandwidth Savings:帶寬優化
      • Interpolation:插值平滑
      • Coordinate Space:坐標空間
      • Timeline Offset:時間偏移修正
      • Debug:調試
      • Additional Settings:其他設置
      • Rotation:旋轉靈敏度
      • Precision:位置同步
      • Sync Settings:同步設置
  • Unity Mirror 示例
    • Mirror & ParrelSync 插件以及 ScriptTemplates代碼模板導入
      • Mirror 插件 導入
      • ParrelSync 插件導入
      • ScriptTemplates 代碼模板導入
        • ScriptTemplates 代碼 模板 作用
        • 你能用它們干什么?
    • Mirro 消息發送接收與同步
      • Mirror 的“消息發送/接收/同步
      • 自定義消息(最靈活、協議自控)
      • 遠程調用:Command / Rpc(經典、夠用)
      • 高層自動同步:SyncVar / SyncList…
      • 如何選擇
    • Mirro UGUI 網絡控制
      • 代碼里的方法映射
      • UGUI 控制代碼
      • 腳本搭載
      • 常見坑與排查
    • Mirro 場景切換功能
      • 場景編排器
      • 自定義 NetworkManager
      • 場景管理
      • 腳本搭載以及運行

Mirror是什么?
Mirror是一款免費的開源的可以用于多人網絡聯機的一個庫,其不僅適用于局域網,也可用于專用的服務器(Dedicate Server)C/S模式,適用于Unity 2019/ 2020 / 2021 /2022 /2023 / 6000。其前身是基于Unet構建的,簡化了一些Unet里的api操作,重構并添加了一些新的功能,大部分的概念和Unet是相通的。

Mirror的一些特性包括:

  1. 消息處理(Message handlers)
  2. 通用的高性能的序列化(General purpose high performance
  3. serialization)
  4. 分布式對象管理
  5. 狀態同步
  6. 網絡類,如:Server、Client、Connection等

Mirror由不同的層構建而成:

在這里插入圖片描述

Mirror

鏈接: GitHub Mirror 下載地址
鏈接: Mirror & ParrelSync & Mirror 模板 ScriptTemplates下載地址

鏈接: Mirror 官方文檔

NetworkManager(網絡管理器)

Configuration:配置

Dont Destroy On Load:是否在切換場景時保持 NetworkManager 不被銷毀。勾選:通常用于只有一個全局 NetworkManager 的項目。不勾選:如果每個場景都有獨立的 NetworkManager。Run In Background:是否允許游戲在后臺繼續運行(比如切出去窗口)。勾選:保證多人游戲網絡不會因應用暫停而斷開。

在這里插入圖片描述

Auto-Start Options:自動啟動

Headless Start Mode:無頭模式(服務器構建時)啟動行為。		DoNothing:不自動啟動。		Auto Start Server:啟動時自動作為服務器運行。		Auto Start Client:啟動時自動作為客戶端運行。Editor Auto Start:在 Unity Editor 下是否也應用 Headless Start Mode,方便調試。Send Rate:服務器/客戶端每秒發送更新的頻率。高速游戲(FPS):60100 Hz。		RPG/MMO30 Hz 左右。		慢節奏(策略/回合):110 Hz。

在這里插入圖片描述

Scene Management:場景管理

Offline Scene:當網絡斷開/停止時切換到的場景。Online Scene:當服務器啟動、客戶端連接成功后切換的場景。Offline Scene Load Delay:從斷開到加載 Offline Scene 的延遲(秒),比如顯示 “連接丟失” 提示。

在這里插入圖片描述

NetworkManager 是 Mirror 的核心網絡入口,這些參數基本涵蓋了 生命周期(配置/啟動)→ 場景切換 → 連接設置 → 玩家生成 → 安全 → 同步優化。

Network Info:網絡信息

Transport:傳輸層組件(Mirror 提供 KCP/Telepathy/其他自定義 Transport)。Network Address:客戶端連接服務器的 IP 或域名,默認 localhost。Max Connections:最大同時連接的客戶端數量。Disconnect Inactive Connections:是否自動斷開不活躍的連接。Disconnect Inactive Timeout:不活躍多久(秒)后斷開。

在這里插入圖片描述

Authentication:身份驗證

Authenticator:可選的認證組件(比如用戶名/密碼驗證)。默認為 None,所有連接直接通過。	可自定義擴展。

在這里插入圖片描述

Player Object:玩家對象

Player Prefab:客戶端連接時生成的玩家對象(必須帶 NetworkIdentity)。Auto Create Player:是否在客戶端連接時自動生成玩家。Player Spawn Method:玩家生成位置的選擇方式:	Random:隨機選擇一個 NetworkStartPosition。	RoundRobin:輪流順序分配。

在這里插入圖片描述

Security:安全

Exceptions Disconnect:如果在處理網絡消息時拋出異常,是否立即斷開該客戶端。開啟:更安全,避免漏洞。		關閉:可能允許客戶端繼續運行,但有風險。

在這里插入圖片描述

Snapshot Interpolation:快照插值

Snapshot Settings:插值參數,用于平滑同步移動(插幀/預測)。如非必要,不用調整默認就行。

在這里插入圖片描述

Connection Quality:連接質量

Evaluation Method:評估網絡連接質量的方式:Simple:基于 RTT 和抖動。		Pragmatic:基于插值的調整。Evaluation Interval:多久評估一次連接質量(秒)。

在這里插入圖片描述

Interpolation:UI 插值調試 UI

Time Interpolation Gui:是否在 Editor/Dev Build 中啟用插值調試 GUI(幫助可視化網絡延遲和插值)。Registered Spawnable Prefabs:可被網絡動態生成的 Prefab 列表。這里需要把游戲中要通過網絡 Spawn 的物體(非玩家)都注冊進來。例如子彈、怪物、掉落物。點擊 Populate Spawnable Prefabs 按鈕可自動添加。

在這里插入圖片描述

KcpTransport(KCP 通信協議)

Transport Configuration:通信配置

Port:服務器監聽的 UDP 端口號。客戶端需要連接這個端口,常用如 777725565。Dual Mode:同時支持 IPv4 和 IPv6。? 開啟:更通用,推薦。? 關閉:僅支持 IPv4(在部分設備/網絡環境下更穩定)。No Delay:是否啟用 KCP 的 Nodelay 模式,即立即發包而不是等聚合。開啟:延遲更低,適合實時游戲。關閉:節省帶寬,延遲稍高。Interval (ms)KCP 的內部刷新周期(單位:毫秒)。默認 10ms(比 KCP 原始默認 100ms 要快很多)。越小延遲越低,但 CPU 占用更高。Timeout (ms):超時時間,客戶端多久沒響應就判定掉線。默認 10000ms(10 秒)。Recv Buffer Size / Send Buffer Size (bytes):Socket 的收/發緩沖區大小。默認 7MB 左右。并發高、大流量時要足夠大。操作系統也需要支持這么大的 buffer,否則無效。

在這里插入圖片描述

Advanced:高級設置

Fast Resend:丟包重傳的激進程度。0:標準模式。2:快速模式,丟包后更快重傳(推薦實時游戲)。Receive Window Size / Send Window Size:接收/發送窗口大小(以包為單位)。默認 4096,代表可以同時緩存/飛行這么多包。窗口越大,吞吐量越高,但丟包時壓力更大。Max Retransmit:單個包的最大重傳次數,超過就判定連接異常。默認 40。Maximize Socket Buffer:是否嘗試將 socket 緩沖區設置到系統允許的最大值。	建議開啟,在高并發/大消息場景下更穩。

在這里插入圖片描述

Allowed Max Message Sizes:允許的最大消息大小

這些是只讀值,顯示在當前窗口設置下:Reliable Max Message Size:在“可靠通道”下單個消息的最大字節數。Unreliable Max Message Size:在“不可靠通道”下單個消息的最大字節數(一般接近 MTU ~1200 字節)。👉 提示:即使最大值很大,也推薦把大消息拆分為小消息傳輸,否則會導致延遲增加。

在這里插入圖片描述

Debug:調試

Debug Log:是否打印調試日志。Statistics GUI:是否在屏幕上顯示統計 GUI(僅限 Editor/Dev Build)。Statistics Log:是否定期在控制臺輸出統計信息(方便無頭服務器調試)。

在這里插入圖片描述

NetworkManagerHUD(網絡管理器 HUD)

Offset X / Offset Y:畫面偏移

Offset X / Offset Y:類型:int	作用:控制 HUD 在屏幕上的 水平偏移 / 垂直偏移(像素)。	默認值:0,表示從屏幕左邊緣開始繪制。	使用場景:	如果你的游戲左上角有其他 UI(例如血條、菜單按鈕),可以通過修改這個值讓 HUD 向右移動,避免重疊。

在這里插入圖片描述

如果腳本搭載,會在視圖左上角出現這樣的效果,按鈕可點擊執行相應的方法。

主要方法

Host (Server + Client):NetworkManager.StartHost()內部流程:啟動 服務器(StartServer())啟動 本地客戶端(StartClient())用于單機本地測試(既當服務端,又有一個客戶端連入)。
Client:NetworkManager.StartClient()內部流程:使用 manager.networkAddress(默認是 "localhost")和端口去連接服務器。連接成功后會觸發 OnClientConnect()
Server Only:NetworkManager.StartServer()內部流程:僅啟動服務器,等待遠程客戶端連接。沒有本地玩家。

在這里插入圖片描述

Client Ready:讓客戶端向服務器聲明“我已準備好”,并生成玩家對象。Stop Host / Stop Client / Stop Server:NetworkManager.StopHost();NetworkManager.StopClient();NetworkManager.StopServer();分別關閉 Host、客戶端、服務器。

在這里插入圖片描述

小結:Host → StartHost()Client → StartClient()(同時可修改 IP 和端口)	Server Only → StartServer()	Client Ready → NetworkClient.Ready() + AddPlayer()	Stop 系列 → StopHost() / StopClient() / StopServer()

NetworkStartPosition(玩家出生點位置)

如何使用:1. 放幾個點就有幾個可選出生位:給場景里多個空物體加上 NetworkStartPosition,就能形成一個出生點池。2. 朝向也會被用到:玩家會按該 Transform.rotation 生成,擺好面向。3. 換場景/銷毀會自動清理:不必手動管理列表,組件的 OnDestroy 會把自己移除,避免臟引用。3. 與 PlayerSpawnMethod 聯動:在 NetworkManager 里切換 Random / RoundRobin 可改變分配策略(適合大廳或多刷新點地圖)。4. 沒有出生點也能生成:若列表為空,Mirror 會在(0,0,0)或默認位置實例化玩家(取決于你的自定義邏輯);通常建議至少放一個 NetworkStartPosition。

在這里插入圖片描述

NetworkIdentity(網絡“身份證”)

Server Only:服務器端

說明:如果勾選,表示這個對象 只會存在于服務器,不會同步到客戶端。用途:服務器邏輯物體(如路徑點、服務端專用的管理對象)。怪物尸體復活前隱藏、只在服務器運算等。

Visibility:可見性

說明:決定對象是否廣播給客戶端(可見性覆蓋 Interest Management 系統)。Default → 使用 Interest Management(默認規則,比如 AOI 可見性)。ForceHidden → 強制對所有客戶端不可見(即使理論上在范圍內)。ForceShown → 強制廣播給所有客戶端(比如比分 UI、全局物體)。用途:怪物重生時用 ForceHidden 先隱藏。全局排行榜、房間管理器等用 ForceShown 始終可見。
如何使用:1. 所有可聯網物體必須掛 NetworkIdentity(玩家、子彈、敵人…)。2. 服務器專用邏輯對象 → 勾選 Server Only。	3. 全局廣播對象 → Visibility = ForceShown。	4. 需要臨時隱藏 → Visibility = ForceHidden(如怪物復活)。	5. Prefab 必須有 assetId,不要復制 prefab 時丟失。

在這里插入圖片描述

NetworkTransformReliable(網絡同步器 穩定版)

Target:同步物體

Target:需要同步的 Transform 對象(一般就是 Player 或附加的子物體)。

在這里插入圖片描述

Selective Sync:選擇性同步

Sync Position:是否同步位置。Sync Rotation:是否同步旋轉。Sync Scale:是否同步縮放。👉 如果某些屬性不需要頻繁同步(比如縮放固定),可以取消勾選節省帶寬。

在這里插入圖片描述

Bandwidth Savings:帶寬優化

Only Sync On Change:只有當值變化超過閾值時才同步(位置變化大于 Position Precision、旋轉變化大于 Rotation Sensitivity)。Compress Rotation:使用壓縮四元數(Smallest-3 壓縮),減少數據量。

在這里插入圖片描述

Interpolation:插值平滑

Interpolate Position / Rotation / Scale:是否在客戶端平滑插值過渡,而不是瞬間跳躍。	? 勾選 → 畫面流暢,適合角色移動。		? 關閉 → 精準、即時,適合子彈/爆炸等瞬時事件。

在這里插入圖片描述

Coordinate Space:坐標空間

Coordinate Space:	Local → 同步本地坐標(相對于父物體)。	World → 同步全局坐標。

在這里插入圖片描述

Timeline Offset:時間偏移修正

Timeline Offset:是否啟用時間偏移修正,用于弱網下抵消網絡延遲造成的“卡頓”。

在這里插入圖片描述

Debug:調試

Show Gizmos / Show Overlay / Overlay Color:調試功能:在場景視圖或屏幕上顯示插值/同步狀態。

在這里插入圖片描述

Additional Settings:其他設置

Only Sync On Change Correction Multiplier:(在 Inspector 里叫 Only Sync On Change 值)當啟用 “只在變化時同步” 時,用于修正快照時間的倍數,避免物體第一次移動時出現卡頓。Use Fixed Update:是否在 FixedUpdate 中應用快照(適合物理物體同步),默認 Update。

在這里插入圖片描述

Rotation:旋轉靈敏度

Rotation Sensitivity:旋轉靈敏度(角度差超過多少度才同步)。默認 0.01

在這里插入圖片描述

Precision:位置同步

Position Precision:位置同步的精度(小數點后保留多少)。默認 0.011cm。Scale Precision:縮放同步的精度,默認 0.01。👉 值越大,帶寬占用越少,但精度也下降。

在這里插入圖片描述

Sync Settings:同步設置

Sync Direction:	Client To Server → 客戶端控制(例如玩家移動)。	Server To Client → 服務器控制(例如怪物 AI)。Sync Interval:同步間隔(秒)。0 表示每幀都可能同步。

在這里插入圖片描述

總結:
這個組件就是 Mirror 官方的 高精度、低帶寬版 Transform 同步器:1. Selective Sync:決定同步哪些屬性。	2. Bandwidth Savings:減少帶寬消耗(只在變化時發包 + 壓縮)。	3. Interpolation:客戶端平滑移動,避免抖動。	4. Precision / Sensitivity:控制同步的粒度。	5. Sync Direction:誰來作為“權威端”同步數據。

Unity Mirror 示例

鏈接: Mirror & ParrelSync下載地址

Mirror & ParrelSync 插件以及 ScriptTemplates代碼模板導入

Mirror 插件 導入

1. 你解壓或者下載之后,直接拉到 Unity Assets 中。

在這里插入圖片描述

2. 導入之后最好點擊一下All 然后點擊Import 按鈕。

在這里插入圖片描述

3. 如果想要了解 可以在 Assets/Mirror/Examples 文件夾下選擇自己感興趣的場景進行嘗試。

在這里插入圖片描述

4. 我推薦這個場景,整體功能基本上都有大家可以自己嘗試嘗試。場景地址:Assets/Mirror/Examples/TopDownShooter/Scenes/MirrorTopDownShooter

在這里插入圖片描述

ParrelSync 插件導入

ParrelSync 是一個 Unity 編輯器擴展,允許用戶通過打開另一個 Unity 編輯器窗口并鏡像原始項目的更改來測試多人游戲,而無需構建項目。
👉 注意:克隆的項目不可編輯否則會報錯。

特征:

  1. 測試多人游戲,無需構建項目
  2. 用于管理所有項目克隆的 GUI 工具
  3. 受保護的資產不被其他克隆實例修改
  4. 方便的 API 可加快測試工作流程
1. 你解壓或者下載之后,直接拉到 Unity Assets 中。只不過選擇的是:ParrelSync 。

在這里插入圖片描述

2. 導入成功之后可以在頂部導航欄 點擊 ParrelSync->Clones Manager

在這里插入圖片描述

3. 可以更改自己想要克隆的路徑,點至Open In New Editor 就可以打開鏡像項目了。

在這里插入圖片描述

在這里插入圖片描述

4. 最后就是這樣的效果

在這里插入圖片描述

ScriptTemplates 代碼模板導入

1. 你解壓或者下載之后選擇 ScriptTemplates 文件夾,直接拉到 Unity Assets 中。

在這里插入圖片描述

在這里插入圖片描述

2. 導入之后會是這樣,導入成功之后要重啟編輯器。

在這里插入圖片描述

3. 成功之后在Assets 中鼠標右鍵 Create -> Mirror 就可以創建可種各樣的代碼模板使用了。

在這里插入圖片描述

ScriptTemplates 代碼 模板 作用
模板名稱基類主要作用
Network ManagerNetworkManager核心入口,管理服務器/客戶端的啟動、場景切換、玩家生成等。
Network Manager With ActionsNetworkManager同上,但額外提供 Action 事件回調,方便用委托而不是繼承來訂閱。
Network AuthenticatorNetworkAuthenticator自定義認證(賬號/密碼/令牌驗證),控制客戶端是否能加入。
Network BehaviourNetworkBehaviourMirror 網絡對象的基類,帶有 OnStartServerOnStartClient 等生命周期函數。
Network Behaviour With ActionsNetworkBehaviourNetworkBehaviour 基礎上加了事件委托版本,邏輯更解耦。
Custom Interest ManagementInterestManagement控制對象的可見性(只同步范圍內的對象 / 分組廣播)。
Network Room ManagerNetworkRoomManager內置房間邏輯(大廳/準備/開始游戲/切換場景)。
Network Room PlayerNetworkRoomPlayer房間里玩家的狀態(如準備/未準備、玩家編號),與 Room Manager 配套。
Network DiscoveryNetworkDiscovery局域網房間發現(客戶端廣播 → 服務器回應)。
Network TransformNetworkTransformReliable(或 NetworkTransform同步對象的 Transform(位置、旋轉、縮放),帶插值和可靠傳輸。
你能用它們干什么?
快速搭建多人聯機框架:Network Manager 負責整體網絡。Network Room Manager + Player 負責大廳、準備、進入游戲。	Network Authenticator 控制誰能加入。	Custom Interest Management 控制誰能看到哪些對象。
同步游戲對象:Network Behaviour/With Actions → 寫自定義聯網邏輯(比如血量、技能冷卻)。Network Transform → 同步位置和旋轉,保持客戶端一致。
擴展局域網/發現功能:Network Discovery 允許自動發現服務器(無需手輸 IP)。

? 總結:這些模板就像“起手式”,幫你在寫聯網代碼時不需要每次都從 MonoBehaviour 改成 NetworkBehaviour,再一個個補生命周期。直接選對應的模板,就能快速得到 Mirror 推薦的代碼結構。

Mirro 消息發送接收與同步

Mirror 的“消息發送/接收/同步

1. 高層數據同步:SyncVar / SyncList / SyncDictionary / SyncSet(自動同步,有鉤子)
2. 遠程調用:[Command](Client→Server)、[ClientRpc] / [TargetRpc](Server→Clients/某個Client)
3. 原始消息:NetworkMessage(RegisterHandler + Send,完全自定義協議)
4. Transform 同步:NetworkTransform( Reliable )(位置/旋轉/縮放 + 插值)

自定義消息(最靈活、協議自控)

適合:聊天、房間列表、業務事件等。
核心 API:RegisterHandler<T>()、Send(msg)、conn.Send(msg)、NetworkServer.SendToAll(msg)
// ─────────────────────────────────────────────────────────────────────────────
// 項目:Mirror Demo
// 文件:ChatMessages.cs
// 說明:演示 Mirror 的 NetworkMessage 收發(客戶端→服務器→廣播給所有客戶端)
// ─────────────────────────────────────────────────────────────────────────────using Mirror;
using UnityEngine;public struct ChatMsg_ZH : NetworkMessage
{// 這里放要傳的字段(必須是 public field,不是屬性)public string _Text;
}// 掛到你的 NetworkManager 的同一個對象上更方便初始化
public class ChatMessageHub : MonoBehaviour
{// ───── 服務器端注冊 ─────/// <summary>服務器啟動時注冊消息處理</summary>[ServerCallback]private void OnEnable(){// 客戶端發來的 ChatMsg// false=不要求通過 Auth 才能收此消息:contentReference[oaicite:1]{index=1}NetworkServer.RegisterHandler<ChatMsg>(OnServerChatMsg, false); }/// <summary>服務器關閉時注銷消息處理</summary>[ServerCallback]private void OnDisable(){// 模板里也有示例:contentReference[oaicite:2]{index=2}NetworkServer.UnregisterHandler<ChatMsg>(); }/// <summary>服務器收到客戶端消息 → 回發給所有人</summary>private void OnServerChatMsg(NetworkConnectionToClient _Conn, ChatMsg _Msg){Debug.Log($"[Server] 收到:{_Msg._Text}");// 回給所有客戶端NetworkServer.SendToAll(_Msg);}// ───── 客戶端注冊 ─────/// <summary>客戶端啟動時注冊接收</summary>private void Start(){NetworkClient.RegisterHandler<ChatMsg>(OnClientChatMsg, false); //:contentReference[oaicite:3]{index=3}}/// <summary>客戶端收到服務器(或其他客戶端轉發)的消息</summary>private void OnClientChatMsg(ChatMsg _Msg){Debug.Log($"[Client] 收到:{_Msg._Text}");}// ───── 客戶端發送 ─────/// <summary>客戶端發消息到服務器</summary>public void ClientSend(string _Text){if (!NetworkClient.isConnected) return;//:contentReference[oaicite:4]{index=4}NetworkClient.Send(new ChatMsg { _Text = _Text }); }
}
要點:
結構體必須是 public struct + public 字段,Mirror 自動序列化。
先 RegisterHandler<T>()Send(),否則會丟。
可搭配 KCP 的可靠/不可靠通道(KcpTransport 層),消息體盡量小且高頻時要考慮帶寬(你前面已配好 KCP 參數)。

遠程調用:Command / Rpc(經典、夠用)

適合:權威服模式下的“客戶端輸入→服務器處理→同步給所有客戶端”
using Mirror;
using UnityEngine;public class MoveAbility_ZH : NetworkBehaviour
{[SyncVar(hook = nameof(OnSpeedChanged))]     // 值改變自動同步,調用鉤子public float _Speed = 3f;// ───── 客戶端輸入 → 發到服務器 ─────/// <summary>客戶端請求移動</summary>/// <param name="_Dir">移動方向(已歸一化)</param>[Command] // Client→Serverprivate void CmdMove(Vector3 _Dir){if (!isServer) return;// 服務器權威地修改位置(示例:簡單位移)transform.position += _Dir * _Speed * Time.fixedDeltaTime;// 廣播給所有客戶端做一些即時效果RpcOnMoveFx(transform.position);}// ───── 服務器廣播 → 客戶端執行 ─────/// <summary>移動效果(僅客戶端執行)</summary>[ClientRpc] // Server→All Clientsprivate void RpcOnMoveFx(Vector3 _NewPos){// 僅做特效/音效,位置同步可交給 SyncVar 或 NetworkTransform// Debug.DrawLine(oldPos, _NewPos, Color.green, 0.1f);}// ───── SyncVar 鉤子 ─────private void OnSpeedChanged(float _Old, float _New){// 本地 UI 刷新}// ───── 本地采集輸入 ─────private void Update(){if (!hasAuthority) return; // 僅本地玩家采集輸入Vector3 _Dir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;if (_Dir != Vector3.zero){// 向服務器發命令CmdMove(_Dir);}}
}
要點:
[Command] 只能由擁有對象 authority 的客戶端調用;服務端執行方法體。
[ClientRpc] 由服務端調用、所有客戶端執行;如只給某個玩家:用 [TargetRpc](參數首個是 NetworkConnectionToClient)。
小狀態(數值/開關)優先用 SyncVar,大范圍連續狀態(位置)交給 NetworkTransform。

高層自動同步:SyncVar / SyncList…

適合:數值狀態、裝備表、隊伍列表等。
using Mirror;
using UnityEngine;/// <summary>
/// 同步生命與物品列表的示例
/// </summary>
public class StatsAndBag_ZH : NetworkBehaviour
{// 數值:改一次→自動同步給觀察者[SyncVar(hook = nameof(OnHpChanged))]public int _Hp = 100;// 列表:增刪改→逐項同步public readonly SyncList<string> _Items = new SyncList<string>();[Server]public void ServerTakeDamage(int _Value){_Hp = Mathf.Max(0, _Hp - _Value); // 賦值會觸發同步 + 鉤子}private void OnHpChanged(int _Old, int _New){// 刷 UI、播放受擊等}private void Awake(){// 監聽同步列表事件_Items.Callback += (_Op, _Index, _OldItem, _NewItem) =>{// 根據 _Op(Add/Remove/Insert/Set)刷新 UI};}
}
要點:
SyncVar 適合小而離散的數據;SyncList 適合集合數據。
只有服務器改動的值才會被同步(默認權威)。客戶端想改 → 用 Command 請求服務器。

如何選擇

1. 玩家輸入/交互:Command 上行 → 服務器改狀態 → SyncVar/ClientRpc 下發
2. 屬性數值:SyncVar + hook
3. 集合/背包:SyncList / SyncDictionary
4. Transform:NetworkTransformReliable(或自定義 NetworkTransformBase)
5. 雜項業務事件(聊天/房間/公告):NetworkMessage(Register + Send)
6. 篩可見性/降低帶寬:自定義 InterestManagement 限制 Observer

Mirro UGUI 網絡控制

代碼里的方法映射

1. Start Host → OnClickStartHost() → NetworkManager.StartHost()(禁用三鍵防連點)。
2. Start Client → OnClickStartClient():讀取 _AddressInputField 與 _PortInputField,設置 networkAddress 和 KcpTransport.Port → StartClient()3. Start Server → OnClickStartServer():設置端口 → StartServer()(可選:切換到 online 場景的示例協程已給出,默認為注釋)。
4. Stop → StopButtons():根據當前狀態調用 StopHost() / StopClient() / StopServer()

UGUI 控制代碼

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
using UnityEngine.UI;
using Mirror.BouncyCastle.Bcpg.OpenPgp;
using Newtonsoft.Json.Serialization;
using UnityEngine.SceneManagement;[AddComponentMenu("NetHUD/NetworkManagerHUD_ZH")]
public class NetworkManagerHUD_ZH : MonoBehaviour
{NetworkManager _Manager;//開啟按鈕組public GameObject _StartButonsGroup;//停止按鈕組public GameObject _StopButtonsGroup;//顯示狀態按鈕public Text _StatusText;//創建Hostpublic Button _StartHostButton;//創建 clientpublic Button _StartClientButton;//IP地址輸入框public InputField _AddressInputField;//端口輸入框public InputField _PortInputField;//創建服務器public Button _StartServerButton;//停止Hostpublic Button _StopHostButton;// ───── 單例防重 + 常駐 ─────private static NetworkManagerHUD_ZH _Instance;private void Awake(){// 防止切換場景后出現第二個 HUDif (_Instance != null && _Instance != this){Destroy(gameObject);return;}_Instance = this;// 關鍵:切場景不銷毀DontDestroyOnLoad(gameObject);}void Start(){//獲取組件_Manager = NetworkManager.singleton ?? FindObjectOfType<NetworkManager>();// 先清理,防止因重復綁定導致一個點擊觸發兩次_StartHostButton.onClick.RemoveAllListeners();_StartClientButton.onClick.RemoveAllListeners();_StartServerButton.onClick.RemoveAllListeners();_StopHostButton.onClick.RemoveAllListeners();_StartHostButton.onClick.AddListener(OnClickStartHost);_StartClientButton.onClick.AddListener(OnClickStartClient);_StartServerButton.onClick.AddListener(OnClickStartServer);_StopHostButton.onClick.AddListener(StopButtons);}void Update(){// UI 狀態刷新StatusLabels();bool _IsHost = NetworkServer.active && NetworkClient.active;bool _IsServer = NetworkServer.active && !NetworkClient.active;bool _IsClient = NetworkClient.isConnected && !NetworkServer.active;//根據狀態顯示按鈕if (!_IsHost && !_IsServer && !_IsClient){// 如果我們還沒有連接,則允許更改地址if (!NetworkClient.active){// 未連接_Manager.networkAddress = _AddressInputField.text;//只有當我們有端口傳輸時才顯示端口字段//我們不能在address字段中使用“IP:PORT”,因為只有這個字段//支持IPV4:PORT。//對于IPV6:PORT,這可能會誤導,因為IPV6包含“:”:// 2001:0db8: 0000:0000:0000: ff00: 0042:8329if (Transport.active is PortTransport portTransport){// 如果有人試圖輸入非數字字符,請使用TryParseif (ushort.TryParse(_PortInputField.text, out ushort port)){portTransport.Port = port;}// 狀態顯示為空_StatusText.text = "";}}else{// 正在連接中_StatusText.text = ($"Connecting to {_Manager.networkAddress}..");}_StartButonsGroup.SetActive(true);_StopButtonsGroup.SetActive(false);}else{_StartButonsGroup.SetActive(false);_StopButtonsGroup.SetActive(true);}}private void OnEnable(){// 有些項目把 NetworkManager 放在玩法場景里,切場景后需要重新拿引用UnityEngine.SceneManagement.SceneManager.activeSceneChanged += OnActiveSceneChanged;}private void OnDisable(){// 解綁事件UnityEngine.SceneManagement.SceneManager.activeSceneChanged -= OnActiveSceneChanged;}/// <summary>///   活動場景已更改/// </summary>/// <param name="oldS"></param>/// <param name="newS"></param>private void OnActiveSceneChanged(Scene oldS, UnityEngine.SceneManagement.Scene newS){// 場景切換后,重新拿一次 NetworkManager / Transport 等(以防丟引用)if (_Manager == null){_Manager = FindObjectOfType<NetworkManager>();}// 這里一般不需要重新綁按鈕,因為按鈕在本對象上,隨著 HUD 常駐一起在// 如果你的按鈕是在場景里的別的對象,需要在這里重新查找并綁定}/// <summary>/// 點擊創建 Host/// </summary>public void OnClickStartHost(){// 禁止連點_StartHostButton.interactable = false;_StartClientButton.interactable = false;_StartServerButton.interactable = false;// 設置地址和端口_Manager.StartHost();//// 切換場景 onlineScene 要設置為空//StartCoroutine(Co_SwitchOnlineSceneOnce("MyScene"));//if (!string.IsNullOrWhiteSpace(_Manager.onlineScene))//{//    _Manager.ServerChangeScene(_Manager.onlineScene);//}}/// <summary>/// 點擊創建 Client/// </summary>public void OnClickStartClient(){// 設置地址_Manager.networkAddress = _AddressInputField.text;// 設置地址和端口if (Transport.active is PortTransport portTransport &&ushort.TryParse(_PortInputField.text, out ushort port)){portTransport.Port = port;}// 啟動客戶端_Manager.StartClient();}/// <summary>/// 點擊創建 Server/// </summary>public void OnClickStartServer(){//if (int.TryParse(_PortInputField.text, out int port))//{//    _Manager.GetComponent<TelepathyTransport>().port = (ushort)port;//}//_Manager.StartServer();// 設置地址和端口if (Transport.active is PortTransport portTransport &&ushort.TryParse(_PortInputField.text, out ushort port)){portTransport.Port = port;}// 啟動服務器_Manager.StartServer();// 切換場景 onlineScene 要設置為空//StartCoroutine(Co_SwitchOnlineSceneOnce("MyScene"));//if (!string.IsNullOrWhiteSpace(_Manager.onlineScene))//{//    _Manager.ServerChangeScene(_Manager.onlineScene);//}}/// <summary>/// 停止按鈕方法/// </summary>public void StopButtons(){// 如果同時是服務器和客戶端(Host)if (NetworkServer.active && NetworkClient.isConnected){_Manager.StopHost();print("停止主機");}// 停止客戶端(如果處于客戶端模式)else if (NetworkClient.isConnected){_Manager.StopClient();Debug.Log("停止客戶端");}// 停止服務器(如果處于服務器模式)else if (NetworkServer.active){_Manager.StopServer();print("停止服務器");}}/// <summary>/// UI 狀態刷新方法/// </summary>private void StatusLabels(){// 主機模式if (NetworkServer.active && NetworkClient.active){// 主機模式_StatusText.text=($"<b>Host</b>: running via {Transport.active}");}else if (NetworkServer.active){// 僅服務器端_StatusText.text = ($"<b>Server</b>: running via {Transport.active}");}else if (NetworkClient.isConnected){// 僅限客戶端_StatusText.text = ($"<b>Client</b>: connected to {_Manager.networkAddress} via {Transport.active}");}}private IEnumerator Co_SwitchOnlineSceneOnce(string _SceneName){// 等到服務器真正啟動 & 不在加載中yield return new WaitUntil(() => NetworkServer.active && !NetworkServer.isLoadingScene);// 再檢查當前場景是否已經是你想去的那個if (SceneManager.GetActiveScene().name != _SceneName){_Manager.ServerChangeScene(_SceneName);//_Manager.onlineScene = _SceneName;}}
}

腳本搭載

1. 注意物體搭載附加

在這里插入圖片描述

2. Canvas 自己創建就行,可以按照自己的風格進行處理。

在這里插入圖片描述

常見坑與排查

1. 按鈕沒反應:確認 Button 的 onClick 沒被別的腳本覆蓋;此腳本里已 RemoveAllListeners() 然后重新綁定,避免重復觸發。
2. 端口不生效:確保當前激活的傳輸層是實現了 PortTransport 的(如 KcpTransport),并且輸入的是數字(腳本用 ushort.TryParse 做了校驗)。
3. 切場景后 HUD 重復:腳本已有“單例防重”邏輯;如果你又在新場景放了一個 HUD,會被自動銷毀保留第一個。
4. 連外網失敗:服務器需要開放 UDP 端口;客戶端地址要填公網 IP,或者配合 NetworkDiscovery 做局域網發現。

Mirro 場景切換功能

場景編排器

using System.Collections;
using System.Collections.Generic;
using Mirror;
using UnityEngine;
using UnityEngine.SceneManagement;/// <summary>
/// 場景編排器(Scene Orchestrator)
/// 功能:
/// 1. 服務器權威管理 Additive 子場景的加載與卸載;
/// 2. 使用 SyncList<string> 同步子場景狀態到所有客戶端;
/// 3. 客戶端(含晚加入)會根據列表自動對齊場景加載狀態。
/// </summary>
public class SceneOrchestrator_ZH : NetworkBehaviour
{// ───── 同步列表 ─────// 當前應加載的子場景列表(服務器寫入,客戶端跟隨)public readonly SyncList<string> _LoadedAdditives = new SyncList<string>();// ───── 本地狀態防抖 ─────// 記錄正在加載的子場景,避免重復調用private readonly HashSet<string> _Loading = new HashSet<string>();// 記錄正在卸載的子場景,避免重復調用private readonly HashSet<string> _Unloading = new HashSet<string>();// ───── Server 端 API ─────#region Server API/// <summary>/// 服務器端:加載一組 Additive 場景(已加載/正在加載的會自動跳過)/// </summary>[Server]public void ServerLoadAdditivesOnce(IEnumerable<string> _Names){StartCoroutine(Co_ServerLoadAdditivesOnce(_Names));}/// <summary>/// 服務器端:切換到新的 Additive 集合(方案1:清空后再加載)/// </summary>[Server]public void ServerSwitchAdditiveSet(IEnumerable<string> _NewSet){StartCoroutine(Co_ServerResetAndApply(_NewSet));}/// <summary>/// 協程:清空舊列表 → 直接加載新集合(避免卸載無效場景報錯)/// </summary>private IEnumerator Co_ServerResetAndApply(IEnumerable<string> _NewSet){// 清空同步列表(客戶端收到 CLEAR 事件,會卸掉所有 Additive)_LoadedAdditives.Clear();yield return null; // 給客戶端一幀時間處理// 直接加載目標集合yield return Co_ServerLoadAdditivesOnce(_NewSet);}/// <summary>/// 協程:僅加載缺失的 Additive 場景/// </summary>private IEnumerator Co_ServerLoadAdditivesOnce(IEnumerable<string> _SceneNames){// 遍歷請求的場景名foreach (var _Name in _SceneNames){// 跳過空名、已加載的、正在加載的if (string.IsNullOrWhiteSpace(_Name)) continue;if (_LoadedAdditives.Contains(_Name)) continue;if (_Loading.Contains(_Name)) continue;// 加載場景_Loading.Add(_Name);// 注意:這里不需要檢查場景是否存在于 Build Settings 中var _Op = SceneManager.LoadSceneAsync(_Name, LoadSceneMode.Additive);while (!_Op.isDone) yield return null;_Loading.Remove(_Name);// 更新同步列表if (!_LoadedAdditives.Contains(_Name)){_LoadedAdditives.Add(_Name); // 同步到客戶端}}}#endregion// ───── Client 端同步邏輯 ─────#region Client Sync/// <summary>/// 客戶端啟動時:做一次全集對齊,并注冊列表回調/// </summary>public override void OnStartClient(){// 保留基類調用,確保 Mirror 內部邏輯不丟失base.OnStartClient();StartCoroutine(Co_ClientApplyFullList()); // 晚加入對齊// 注冊列表變化回調_LoadedAdditives.Callback += OnLoadedAdditivesChanged;}/// <summary>/// 客戶端關閉時:移除列表回調/// </summary>public override void OnStopClient(){// 移除列表變化回調_LoadedAdditives.Callback -= OnLoadedAdditivesChanged;base.OnStopClient();}/// <summary>/// 同步列表變化時的回調/// </summary>private void OnLoadedAdditivesChanged(SyncList<string>.Operation _Op, int _Index, string _OldItem, string _NewItem){// 根據操作類型處理switch (_Op){case SyncList<string>.Operation.OP_ADD:// 新增場景if (!string.IsNullOrEmpty(_NewItem)){StartCoroutine(Co_ClientEnsureLoaded(_NewItem));}break;case SyncList<string>.Operation.OP_REMOVEAT:// 移除場景if (!string.IsNullOrEmpty(_OldItem)){StartCoroutine(Co_ClientEnsureUnloaded(_OldItem));}break;case SyncList<string>.Operation.OP_CLEAR:// 清空列表StartCoroutine(Co_ClientUnloadAll());break;}}/// <summary>/// 客戶端:全集對齊(卸掉多余的,加載缺的)/// </summary>private IEnumerator Co_ClientApplyFullList(){// 卸掉本地多余的(根場景除外)for (int _i = 0; _i < SceneManager.sceneCount; ++_i){// 跳過根場景var _Sc = SceneManager.GetSceneAt(_i);if (_Sc == SceneManager.GetActiveScene()) continue;// 如果不在同步列表里,就卸掉if (!_LoadedAdditives.Contains(_Sc.name)){yield return Co_ClientEnsureUnloaded(_Sc.name);}}// 加載缺失的foreach (var _Name in _LoadedAdditives){// 跳過空名yield return Co_ClientEnsureLoaded(_Name);}}/// <summary>/// 客戶端:確保場景已加載/// </summary>private IEnumerator Co_ClientEnsureLoaded(string _SceneName){// 跳過空名if (string.IsNullOrWhiteSpace(_SceneName)) yield break;// 跳過已加載的和正在加載的var _Sc = SceneManager.GetSceneByName(_SceneName);if (_Sc.IsValid() && _Sc.isLoaded) yield break;if (_Loading.Contains(_SceneName)) yield break;// 加載場景_Loading.Add(_SceneName);var _Op = SceneManager.LoadSceneAsync(_SceneName, LoadSceneMode.Additive);// 注意:這里不需要檢查場景是否存在于 Build Settings 中while (!_Op.isDone) yield return null;_Loading.Remove(_SceneName);}/// <summary>/// 客戶端:確保場景已卸載/// </summary>private IEnumerator Co_ClientEnsureUnloaded(string _SceneName){// 跳過空名if (string.IsNullOrWhiteSpace(_SceneName)) yield break;// 跳過未加載的和正在卸載的var _Sc = SceneManager.GetSceneByName(_SceneName);if (!_Sc.IsValid() || !_Sc.isLoaded) yield break;if (_Unloading.Contains(_SceneName)) yield break;// 卸載場景_Unloading.Add(_SceneName);var _Op = SceneManager.UnloadSceneAsync(_SceneName);// 注意:這里不需要檢查場景是否存在于 Build Settings 中while (_Op != null && !_Op.isDone) yield return null;_Unloading.Remove(_SceneName);}/// <summary>/// 客戶端:卸載所有非根場景/// </summary>private IEnumerator Co_ClientUnloadAll(){// 遍歷所有場景,卸掉非根場景for (int i = 0; i < SceneManager.sceneCount; ++i){// 跳過根場景var _Sc = SceneManager.GetSceneAt(i);if (_Sc == SceneManager.GetActiveScene()) continue;// 卸掉場景yield return Co_ClientEnsureUnloaded(_Sc.name);}}#endregion
}

自定義 NetworkManager

using Mirror;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using System.IO;/// <summary>
/// 自定義 NetworkManager:
/// 1. 擴展 Mirror 自帶的場景切換邏輯;
/// 2. 在根場景切換完成后,服務器權威地加載指定的 Additive 子場景;
/// 3. 通過 SceneOrchestrator_ZH 同步給所有客戶端,保證晚加入客戶端也能正確對齊。
/// </summary>
public class CustomNetworkManager_ZH : NetworkManager
{// ───── 預制體引用 ─────[Header("Orchestrator 預制體(已在 Spawnable Prefabs 中注冊)")]public SceneOrchestrator_ZH _OrchestratorPrefab;   // 用于管理 Additive 子場景的網絡對象預制體// ───── 根場景與其對應的 Additive 集合映射 ─────[Header("根場景 → Additive 集合映射")]public List<string> _AdditivesForMyScene = new List<string> { "Add", "GameList" };        // 當根場景是 MyScene 時要加載的子場景public List<string> _AdditivesForMyotherScene = new List<string> { "Add", "GameList" };   // 當根場景是 MyOtherScene 時要加載的子場景// 服務器側持有的 orchestrator 實例(單例)private SceneOrchestrator_ZH _ServerOrchestrator;// ───── 玩家生成防重 ─────/// <summary>/// 重寫 Mirror 的 OnServerAddPlayer,避免重復給同一個連接添加玩家。/// </summary>public override void OnServerAddPlayer(NetworkConnectionToClient _Conn){if (_Conn.identity != null){Debug.LogWarning($"[Server] 添加玩家操作被忽略(連接已存在玩家對象) connId={_Conn.connectionId}");return;}base.OnServerAddPlayer(_Conn);}// ───── 場景切換鉤子 ─────/// <summary>/// 當服務器端完成根場景切換時調用。/// Mirror 會在 ServerChangeScene → FinishLoadScene → OnServerSceneChanged 順序觸發。/// </summary>public override void OnServerSceneChanged(string _SceneName){Debug.Log($"[Server] OnServerSceneChanged -> {_SceneName}");// 保留基類調用,確保 Mirror 內部邏輯不丟失base.OnServerSceneChanged(_SceneName);if (!NetworkServer.active) return;// 開啟協程,等根場景完全切換完成后再裝配 AdditiveStartCoroutine(Co_PostSceneChanged(_SceneName));}// ───── 協程:根場景切換完成后再加載 Additive 集 ─────/// <summary>/// 根場景切換后的后處理邏輯:/// 1. 等待場景完全切換完成;/// 2. 規范化根場景名(去除路徑和后綴);/// 3. 如果 orchestrator 不存在,則生成并 Spawn;/// 4. 按映射選擇要加載的 Additive 集,并調用 orchestrator 同步加載。/// </summary>/// <param name="_ScenePathOrName">傳入的場景路徑或名字(Mirror 傳的可能是完整路徑)</param>private IEnumerator Co_PostSceneChanged(string _ScenePathOrName){// 等待 Mirror 把根場景切換完畢yield return new WaitUntil(() => !NetworkServer.isLoadingScene);yield return null; // 再等一幀更穩// 從路徑提取出純場景名string _RootName = Path.GetFileNameWithoutExtension(_ScenePathOrName);Debug.Log($"[Server] RootSceneName 規范化后 = {_RootName}");// 如果 orchestrator 還沒生成,就在服務器端實例化并 Spawnif (_ServerOrchestrator == null){_ServerOrchestrator = Instantiate(_OrchestratorPrefab);DontDestroyOnLoad(_ServerOrchestrator.gameObject);          // 保持跨場景不銷毀NetworkServer.Spawn(_ServerOrchestrator.gameObject);        // 廣播給所有客戶端}// 按根場景名選擇要加載的 Additive 集List<string> _Set = null;if (_RootName == "MyScene") _Set = _AdditivesForMyScene;else if (_RootName == "MyOtherScene" || _RootName == "MyotherScene") _Set = _AdditivesForMyotherScene;else _Set = new List<string>(); // 未配置的根場景 → 不加載任何 Additive// 調用 orchestrator 執行子場景加載(服務器權威,客戶端跟隨)_ServerOrchestrator.ServerSwitchAdditiveSet(_Set);Debug.Log($"[Server] Additive 集裝配完成:[{string.Join(", ", _Set)}]");}// ───── 工具函數:校驗子場景是否在 Build Settings 中 ─────/// <summary>/// 檢查 Additive 場景是否都已加入 Build Settings。/// 避免運行時報 “場景未找到”。/// </summary>/// <param name="_Names">要校驗的子場景列表</param>private bool CheckScenesInBuild(List<string> _Names){// 空列表直接通過if (_Names == null) return true;// 遍歷檢查每個場景名for (int i = 0; i < _Names.Count; i++){// 跳過空白項var _N = _Names[i];if (string.IsNullOrWhiteSpace(_N)) continue;// 查找是否存在bool _Exists = false;// 遍歷 Build Settings 里的場景for (int _Bi = 0; _Bi < SceneManager.sceneCountInBuildSettings; _Bi++){var _Path = SceneUtility.GetScenePathByBuildIndex(_Bi);var _NameOnly = System.IO.Path.GetFileNameWithoutExtension(_Path);if (_NameOnly == _N) { _Exists = true; break; }}// 報錯并返回if (!_Exists){Debug.LogError($"[BuildSettings] 缺少場景:{_N}");return false;}}return true;}
}

場景管理

using Mirror;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;/// <summary>
/// 場景管理腳本:
/// - 管理 UI 文本顯示(子彈數量、消息內容);
/// - 負責調用玩家腳本的消息接口;
/// - 提供按鈕觸發的場景切換邏輯。
/// </summary>
public class SceneScript_ZH : NetworkBehaviour
{[Header("UI 引用")]public Text _BulletText;       // 顯示子彈數量public Text _MessageText;      // 顯示消息文本[Header("玩家引用")]public PlayerController_ZH _PlayerController; // 玩家腳本引用[SyncVar(hook = nameof(OnStatusTextChanged))]public string _StatusText;     // 同步消息內容(帶鉤子)/// <summary>/// 當 _StatusText 發生變化時調用,刷新 UI。/// </summary>private void OnStatusTextChanged(string _Old, string _NewStr){_MessageText.text = _StatusText;}/// <summary>/// 按鈕:發送消息/// </summary>public void OnSendMessageButton(){if (_PlayerController != null){_PlayerController.CmdSendPlayerMessage();}}/// <summary>/// 按鈕:切換場景(僅服務器可操作)/// </summary>public void ChangeSceneButton(){// 檢查服務器是否已啟動,是獨立服務器還是作為主機服務器。if (!NetworkServer.active){Debug.Log("只有服務器/主機可以切換場景");return;}// 檢查是否正在切換場景中if (NetworkServer.isLoadingScene){Debug.Log("正在切換場景中,忽略重復請求");return;}// 決定下一個場景string _Cur = SceneManager.GetActiveScene().name;string _NextRoot = (_Cur == "MyScene") ? "MyOtherScene" : "MyScene";// 如果當前場景就是目標場景,則不切換if (_Cur == _NextRoot) return;// 切換場景NetworkManager.singleton.ServerChangeScene(_NextRoot);Debug.Log($"切根場景到:{_NextRoot}");}
}

腳本搭載以及運行

1. 自定義 NetworkManager 搭載:

在這里插入圖片描述

2. 注意 場景編排器預制體的創建以及切換場景和附加場景名稱添加。

在這里插入圖片描述

3. 點擊創建 Host But 創建房間

在這里插入圖片描述

4. 房間創建的時候 會在CustomNetworkManager_ZH 腳本中自動執行 OnServerSceneChanged 方法。然后會執行Co_PostSceneChanged 協程方法,按映射加載 Additive集并調用 orchestrator同步加載。

在這里插入圖片描述

5. 點擊 Change Scene 按鈕,調用 SceneScript_ZH.ChangeSceneButton() 方法,進行游戲場景切換。

在這里插入圖片描述

6. 如果切換成功之后,會根據場景名稱進行加載附加場景集。在 CustomNetworkManager_ZH代碼中,根場景 → Additive 集合映射。

在這里插入圖片描述

7. 執行順序會在 Console 窗口中進行顯示。

在這里插入圖片描述

鏈接: Unity Mirror 多人同步 基礎教程 完整示例工程

暫時先這樣吧,如果實在看不明白就留言,看到我會回復的。希望這個教程對您有幫助!
路漫漫其修遠,與君共勉。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/98237.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/98237.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/98237.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于紅尾鷹優化的LSTM深度學習網絡模型(RTH-LSTM)的一維時間序列預測算法matlab仿真

目錄 1.程序功能描述 2.測試軟件版本以及運行結果展示 3.部分程序 4.算法理論概述 5.完整程序 1.程序功能描述 紅尾鷹優化的LSTM&#xff08;RTH-LSTM&#xff09;算法&#xff0c;是將紅尾鷹優化算法&#xff08;Red-Tailed Hawk Optimization, RTHO&#xff09;與長短期…

深度學習“調參”黑話手冊:學習率、Batch Size、Epoch都是啥?

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;注冊即送-H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;從"煉丹"到科學&#xff0c;…

【網絡實驗】-MUX-VLAN

實驗拓撲實驗要求&#xff1a; 在企業網絡中&#xff0c;企業員工和企業客戶可以訪問企業的服務器&#xff0c;對于企業來說&#xff0c;希望員工之間可以互相交流&#xff0c;但是企業用戶之間相互隔離&#xff0c;不能夠訪問。為了實現所有用戶都可以訪問企業服務器&#xff…

Java泛型:類型安全的藝術與實踐指南

Java泛型&#xff1a;類型安全的藝術與實踐指南 前言&#xff1a;一個常見的編譯錯誤 最近在開發中遇到了這樣一個編譯錯誤&#xff1a; Required type: Callable<Object> Provided: SalesPitchTask這個看似簡單的錯誤背后&#xff0c;隱藏著Java泛型設計的深層哲學。今天…

UMI企業智腦 2.1.0:智能營銷新引擎,圖文矩陣引領內容創作新潮流

在數字營銷日益激烈的今天&#xff0c;企業如何在信息洪流中脫穎而出&#xff1f;UMI企業智腦 2.1.0 的發布為企業提供了全新的解決方案。這款智能營銷工具結合了先進的AI技術與數據驅動策略&#xff0c;幫助企業優化營銷流程、提升效率&#xff0c;并通過圖文矩陣實現內容創作…

Lustre Ceph GlusterFS NAS 需要掛載在k8s容器上,數據量少,選擇哪一個存儲較好

在 K8s 容器環境中&#xff0c;數據量 不大的 規模下&#xff0c;Lustre、Ceph、GlusterFS 和 NAS 的選擇需結合性能需求、運維成本、擴展性和K8s 適配性綜合判斷。以下是針對性分析及推薦&#xff1a;一、核心對比與適用場景二、關鍵決策因素1. 性能需求高并發 / 高吞吐&#…

深入解析 Apache Doris 寫入原理:一條數據的“落地之旅”

在日常的數據分析場景中&#xff0c;我們經常會向 Apache Doris 寫入大量數據&#xff0c;無論是實時導入、批量導入&#xff0c;還是通過流式寫入。但你是否想過&#xff1a;一條數據從客戶端發出&#xff0c;到最終穩定落盤&#xff0c;中間到底經歷了哪些步驟&#xff1f; …

基于MATLAB的視頻動態目標跟蹤檢測實現方案

一、系統架構設計 視頻動態目標跟蹤系統包含以下核心模塊&#xff1a; 視頻輸入模塊&#xff1a;支持攝像頭實時采集或視頻文件讀取預處理模塊&#xff1a;灰度轉換、降噪、光照補償目標檢測模塊&#xff1a;背景建模、運動區域提取跟蹤算法模塊&#xff1a;卡爾曼濾波、粒子濾…

【Python】Python文件操作

Python文件操作 文章目錄Python文件操作[toc]1.文件的編碼2.文件打開、讀取&#xff08;r模式&#xff09;、關閉3.文件的寫入&#xff08;w模式&#xff09;4.文件的追加寫入&#xff08;a模式&#xff09;5.綜合案例1.文件的編碼 意義&#xff1a;計算機只能識別0和1&#x…

CES Asia的“五年計劃”:打造與北美展比肩的科技影響力

在全球科技產業版圖中&#xff0c;展會一直是前沿技術展示、行業趨勢探討以及商業合作達成的關鍵平臺。CES Asia&#xff08;亞洲消費電子技術展&#xff09;作為亞洲科技領域的重要展會&#xff0c;近日明確提出其“五年計劃”&#xff0c;目標是打造與北美展會比肩的科技影響…

【計算機網絡 | 第16篇】DNS域名工作原理

文章目錄3.5 域名系統工作原理主機的標識方式&#xff1a;域名 vs IP 地址標識轉換機制&#xff1a;DNS系統因特網的域名系統&#xff1a;層次域名空間&#x1f426;?&#x1f525;頂級域名分類低級域名與管理域名與IP的區別因特網的域名系統&#xff1a;域名服務器&#x1f9…

YASKAWA安川機器人鋁材焊接節氣之道

在鋁材焊接領域&#xff0c;保護氣體的合理使用對焊接質量與成本控制至關重要。安川焊接機器人憑借高精度與穩定性成為行業常用設備&#xff0c;而WGFACS節氣裝置的應用&#xff0c;則為其在鋁材焊接過程中實現高效節氣提供了創新路徑。掌握二者結合的節氣之道&#xff0c;對提…

GooseDB,一款實現服務器客戶端模式的DuckDB

在網上看到韓國公司開發的一款GooseDB&#xff0c; 官方網站對它的介紹是DuckDB? 的功能擴展分支&#xff0c;具有服務器/客戶端、多會話和并發寫入支持&#xff0c;使用 PostgreSQL 有線協議&#xff08;DuckDB?是 DuckDB 基金會的商標&#xff09; 使用也很簡單&#xff…

lesson62:JavaScript對象進化:ES2025新特性深度解析與實戰指南

目錄 一、迭代器輔助方法&#xff1a;對象數據處理的優雅革命 1.1 核心方法與語法 1.2 對象屬性處理實戰 1.3 性能與兼容性考量 二、JSON模塊原生支持&#xff1a;對象加載的范式轉變 2.1 靜態與動態導入語法 2.2 與傳統方案的對比優勢 2.3 典型應用場景 三、Set集合增…

設計模式學習筆記(一)

設計模式學習筆記&#xff08;一&#xff09; 一般說設計模式都是指面向對象的設計模式&#xff0c;因為面向對象語言可以借助封裝、繼承、多態等特性更好的達到復用性、可拓展性、可維護性。 面向對象一般指以類、對象為組織代碼的基本單元&#xff0c;并將封裝、繼承、多態、…

【CSS】一個自適應大小的父元素,如何讓子元素的寬高比一直是2:1

父元素是自適應大小的容器&#xff08;比如 width:100%&#xff09;&#xff0c;我們希望子元素 始終保持 2:1 寬高比&#xff08;比如寬 200px → 高 100px&#xff0c;寬 300px → 高 150px&#xff09;。 有幾種常見解法&#xff1a;? 方法一&#xff1a;CSS aspect-ratio&…

如何搭建redis集群(docker方式非哨兵)

1、redis的配置文件這里要注意&#xff0c;主從的ip不需要我們去設置&#xff0c;只需要設置主從的密碼就可以&#xff0c;然后就是protect-mode&#xff0c;我設置的是no&#xff0c;一定注意不能設置主從。客戶端要訪問&#xff0c;一定要加# 每個節點的 redis.conf 中 clust…

如何學習VBA_3.3.9:利用“搭積木”思想,快速有效地完成你的代碼

我給VBA的定義&#xff1a;VBA是個人小型自動化處理的有效工具。利用好了&#xff0c;可以大大提高自己的勞動效率&#xff0c;而且可以提高數據處理的準確度。我推出的VBA系列教程共九套和一部VBA漢英手冊&#xff0c;現在已經全部完成&#xff0c;希望大家利用、學習。如果您…

JSP程序設計之輸入/輸出對象 — response對象

response對象1.概述2.實例&#xff1a;response對象方法運用&#xff08;1&#xff09;實例一&#xff1a;頁面自動刷新&#xff08;2&#xff09;實例二&#xff1a;實現頁面重定向&#xff0c;具體的代碼&#xff08;3&#xff09;綜合實例&#xff1a;實現登錄并記錄用戶名1…

Redis 事件驅動框架(ae.c_ae.h)深度解析

Redis 事件驅動框架&#xff08;ae.c/ae.h&#xff09;深度解析 之前咱們用 “超市收銀員” 的例子&#xff0c;簡單看懂了 ae 模塊是 Redis 的 “多任務神器”。現在咱們再往深走一層&#xff0c;不用復雜代碼&#xff0c;只拆它的 “核心運作邏輯”—— 搞懂它怎么做到 “一個…