Mirror Networking
NetworkManager.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using kcp2k;
5using UnityEngine;
6using UnityEngine.SceneManagement;
7using UnityEngine.Serialization;
8
9namespace Mirror
10{
11 public enum PlayerSpawnMethod { Random, RoundRobin }
12 public enum NetworkManagerMode { Offline, ServerOnly, ClientOnly, Host }
13
14 [DisallowMultipleComponent]
15 [AddComponentMenu("Network/Network Manager")]
16 [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-manager")]
17 public class NetworkManager : MonoBehaviour
18 {
20 // This should be set if your game has a single NetworkManager that exists for the lifetime of the process. If there is a NetworkManager in each scene, then this should not be set.</para>
21 [Header("Configuration")]
22 [FormerlySerializedAs("m_DontDestroyOnLoad")]
23 [Tooltip("Should the Network Manager object be persisted through scene changes?")]
24 public bool dontDestroyOnLoad = true;
25
27 [FormerlySerializedAs("m_RunInBackground")]
28 [Tooltip("Multiplayer games should always run in the background so the network doesn't time out.")]
29 public bool runInBackground = true;
30
32 [Tooltip("Should the server auto-start when 'Server Build' is checked in build settings")]
33 [FormerlySerializedAs("startOnHeadless")]
34 public bool autoStartServerBuild = true;
35
37 [Tooltip("Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
38 public int serverTickRate = 30;
39
41 [Header("Scene Management")]
42 [Scene]
43 [FormerlySerializedAs("m_OfflineScene")]
44 [Tooltip("Scene that Mirror will switch to when the client or server is stopped")]
45 public string offlineScene = "";
46
48 [Scene]
49 [FormerlySerializedAs("m_OnlineScene")]
50 [Tooltip("Scene that Mirror will switch to when the server is started. Clients will recieve a Scene Message to load the server's current scene when they connect.")]
51 public string onlineScene = "";
52
53 // transport layer
54 [Header("Network Info")]
55 [Tooltip("Transport component attached to this object that server and client will use to connect")]
56 [SerializeField]
57 protected Transport transport;
58
60 [FormerlySerializedAs("m_NetworkAddress")]
61 [Tooltip("Network Address where the client should connect to the server. Server does not use this for anything.")]
62 public string networkAddress = "localhost";
63
65 [FormerlySerializedAs("m_MaxConnections")]
66 [Tooltip("Maximum number of concurrent connections.")]
67 public int maxConnections = 100;
68
69 [Header("Authentication")]
70 [Tooltip("Authentication component attached to this object")]
71 public NetworkAuthenticator authenticator;
72
74 // Player objects are created in the default handler for AddPlayer() on
75 // the server. Implementing OnServerAddPlayer overrides this behaviour.
76 [Header("Player Object")]
77 [FormerlySerializedAs("m_PlayerPrefab")]
78 [Tooltip("Prefab of the player object. Prefab must have a Network Identity component. May be an empty game object or a full avatar.")]
79 public GameObject playerPrefab;
80
82 [FormerlySerializedAs("m_AutoCreatePlayer")]
83 [Tooltip("Should Mirror automatically spawn the player after scene change?")]
84 public bool autoCreatePlayer = true;
85
87 [FormerlySerializedAs("m_PlayerSpawnMethod")]
88 [Tooltip("Round Robin or Random order of Start Position selection")]
89 public PlayerSpawnMethod playerSpawnMethod;
90
92 [FormerlySerializedAs("m_SpawnPrefabs"), HideInInspector]
93 public List<GameObject> spawnPrefabs = new List<GameObject>();
94
96 public static List<Transform> startPositions = new List<Transform>();
97 public static int startPositionIndex;
98
100 public static NetworkManager singleton { get; internal set; }
101
103 public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.identity != null);
104
106 public bool isNetworkActive => NetworkServer.active || NetworkClient.active;
107
108 // TODO remove this
109 // internal for tests
110 internal static NetworkConnection clientReadyConnection;
111
113 // This is set before OnClientConnect is called, so it can be checked
114 // there to perform different logic if a scene load occurred.
115 protected bool clientLoadedScene;
116
117 // helper enum to know if we started the networkmanager as server/client/host.
118 // -> this is necessary because when StartHost changes server scene to
119 // online scene, FinishLoadScene is called and the host client isn't
120 // connected yet (no need to connect it before server was fully set up).
121 // in other words, we need this to know which mode we are running in
122 // during FinishLoadScene.
123 public NetworkManagerMode mode { get; private set; }
124
125 // virtual so that inheriting classes' OnValidate() can call base.OnValidate() too
126 public virtual void OnValidate()
127 {
128 // always >= 0
129 maxConnections = Mathf.Max(maxConnections, 0);
130
131 if (playerPrefab != null && playerPrefab.GetComponent<NetworkIdentity>() == null)
132 {
133 Debug.LogError("NetworkManager - Player Prefab must have a NetworkIdentity.");
134 playerPrefab = null;
135 }
136
137 // This avoids the mysterious "Replacing existing prefab with assetId ... Old prefab 'Player', New prefab 'Player'" warning.
138 if (playerPrefab != null && spawnPrefabs.Contains(playerPrefab))
139 {
140 Debug.LogWarning("NetworkManager - Player Prefab should not be added to Registered Spawnable Prefabs list...removed it.");
142 }
143 }
144
145 // virtual so that inheriting classes' Reset() can call base.Reset() too
146 // Reset only gets called when the component is added or the user resets the component
147 // Thats why we validate these things that only need to be validated on adding the NetworkManager here
148 // If we would do it in OnValidate() then it would run this everytime a value changes
149 public virtual void Reset()
150 {
151 // make sure someone doesn't accidentally add another NetworkManager
152 // need transform.root because when adding to a child, the parent's
153 // Reset isn't called.
154 foreach (NetworkManager manager in transform.root.GetComponentsInChildren<NetworkManager>())
155 {
156 if (manager != this)
157 {
158 Debug.LogError($"{name} detected another component of type {typeof(NetworkManager)} in its hierarchy on {manager.name}. There can only be one, please remove one of them.");
159 // return early so that transport component isn't auto-added
160 // to the duplicate NetworkManager.
161 return;
162 }
163 }
164
165 // add transport if there is none yet. makes upgrading easier.
166 if (transport == null)
167 {
168#if UNITY_EDITOR
169 // RecordObject needs to be called before we make the change
170 UnityEditor.Undo.RecordObject(gameObject, "Added default Transport");
171#endif
172
173 transport = GetComponent<Transport>();
174
175 // was a transport added yet? if not, add one
176 if (transport == null)
177 {
178 transport = gameObject.AddComponent<KcpTransport>();
179 Debug.Log("NetworkManager: added default Transport because there was none yet.");
180 }
181 }
182 }
183
184 // virtual so that inheriting classes' Awake() can call base.Awake() too
185 public virtual void Awake()
186 {
187 // Don't allow collision-destroyed second instance to continue.
188 if (!InitializeSingleton()) return;
189
190 Debug.Log("Mirror | mirror-networking.com | discord.gg/N9QVxbM");
191
192 // Set the networkSceneName to prevent a scene reload
193 // if client connection to server fails.
195
196 // setup OnSceneLoaded callback
197 SceneManager.sceneLoaded += OnSceneLoaded;
198 }
199
200 // virtual so that inheriting classes' Start() can call base.Start() too
201 public virtual void Start()
202 {
203 // headless mode? then start the server
204 // can't do this in Awake because Awake is for initialization.
205 // some transports might not be ready until Start.
206 //
207 // (tick rate is applied in StartServer!)
208#if UNITY_SERVER
210 {
211 StartServer();
212 }
213#endif
214 }
215
216 // virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too
217 public virtual void LateUpdate()
218 {
219 UpdateScene();
220 }
221
222 // keep the online scene change check in a separate function
223 bool IsServerOnlineSceneChangeNeeded()
224 {
225 // Only change scene if the requested online scene is not blank, and is not already loaded
226 return !string.IsNullOrWhiteSpace(onlineScene) && !IsSceneActive(onlineScene) && onlineScene != offlineScene;
227 }
228
229 public static bool IsSceneActive(string scene)
230 {
231 Scene activeScene = SceneManager.GetActiveScene();
232 return activeScene.path == scene || activeScene.name == scene;
233 }
234
235 // full server setup code, without spawning objects yet
236 void SetupServer()
237 {
238 // Debug.Log("NetworkManager SetupServer");
239 InitializeSingleton();
240
241 if (runInBackground)
242 Application.runInBackground = true;
243
244 if (authenticator != null)
245 {
246 authenticator.OnStartServer();
247 authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated);
248 }
249
251
252 // start listening to network connections
253 NetworkServer.Listen(maxConnections);
254
255 // call OnStartServer AFTER Listen, so that NetworkServer.active is
256 // true and we can call NetworkServer.Spawn in OnStartServer
257 // overrides.
258 // (useful for loading & spawning stuff from database etc.)
259 //
260 // note: there is no risk of someone connecting after Listen() and
261 // before OnStartServer() because this all runs in one thread
262 // and we don't start processing connects until Update.
264
265 // this must be after Listen(), since that registers the default message handlers
266 RegisterServerMessages();
267 }
268
270 public void StartServer()
271 {
273 {
274 Debug.LogWarning("Server already started.");
275 return;
276 }
277
278 mode = NetworkManagerMode.ServerOnly;
279
280 // StartServer is inherently ASYNCHRONOUS (=doesn't finish immediately)
281 //
282 // Here is what it does:
283 // Listen
284 // if onlineScene:
285 // LoadSceneAsync
286 // ...
287 // FinishLoadSceneServerOnly
288 // SpawnObjects
289 // else:
290 // SpawnObjects
291 //
292 // there is NO WAY to make it synchronous because both LoadSceneAsync
293 // and LoadScene do not finish loading immediately. as long as we
294 // have the onlineScene feature, it will be asynchronous!
295
296 SetupServer();
297
298 // scene change needed? then change scene and spawn afterwards.
299 if (IsServerOnlineSceneChangeNeeded())
300 {
302 }
303 // otherwise spawn directly
304 else
305 {
307 }
308 }
309
311 public void StartClient()
312 {
314 {
315 Debug.LogWarning("Client already started.");
316 return;
317 }
318
319 mode = NetworkManagerMode.ClientOnly;
320
321 InitializeSingleton();
322
323 if (runInBackground)
324 Application.runInBackground = true;
325
326 if (authenticator != null)
327 {
328 authenticator.OnStartClient();
329 authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
330 }
331
332 // In case this is a headless client...
334
335 RegisterClientMessages();
336
337 if (string.IsNullOrWhiteSpace(networkAddress))
338 {
339 Debug.LogError("Must set the Network Address field in the manager");
340 return;
341 }
342 // Debug.Log($"NetworkManager StartClient address:{networkAddress}");
343
345
347 }
348
350 public void StartClient(Uri uri)
351 {
353 {
354 Debug.LogWarning("Client already started.");
355 return;
356 }
357
358 mode = NetworkManagerMode.ClientOnly;
359
360 InitializeSingleton();
361
362 if (runInBackground)
363 Application.runInBackground = true;
364
365 if (authenticator != null)
366 {
367 authenticator.OnStartClient();
368 authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
369 }
370
371 RegisterClientMessages();
372
373 // Debug.Log($"NetworkManager StartClient address:{uri}");
374 networkAddress = uri.Host;
375
377
379 }
380
382 public void StartHost()
383 {
385 {
386 Debug.LogWarning("Server or Client already started.");
387 return;
388 }
389
390 mode = NetworkManagerMode.Host;
391
392 // StartHost is inherently ASYNCHRONOUS (=doesn't finish immediately)
393 //
394 // Here is what it does:
395 // Listen
396 // ConnectHost
397 // if onlineScene:
398 // LoadSceneAsync
399 // ...
400 // FinishLoadSceneHost
401 // FinishStartHost
402 // SpawnObjects
403 // StartHostClient <= not guaranteed to happen after SpawnObjects if onlineScene is set!
404 // ClientAuth
405 // success: server sends changescene msg to client
406 // else:
407 // FinishStartHost
408 //
409 // there is NO WAY to make it synchronous because both LoadSceneAsync
410 // and LoadScene do not finish loading immediately. as long as we
411 // have the onlineScene feature, it will be asynchronous!
412
413 // setup server first
414 SetupServer();
415
416 // call OnStartHost AFTER SetupServer. this way we can use
417 // NetworkServer.Spawn etc. in there too. just like OnStartServer
418 // is called after the server is actually properly started.
419 OnStartHost();
420
421 // scene change needed? then change scene and spawn afterwards.
422 // => BEFORE host client connects. if client auth succeeds then the
423 // server tells it to load 'onlineScene'. we can't do that if
424 // server is still in 'offlineScene'. so load on server first.
425 if (IsServerOnlineSceneChangeNeeded())
426 {
427 // call FinishStartHost after changing scene.
428 finishStartHostPending = true;
430 }
431 // otherwise call FinishStartHost directly
432 else
433 {
434 FinishStartHost();
435 }
436 }
437
438 // This may be set true in StartHost and is evaluated in FinishStartHost
439 bool finishStartHostPending;
440
441 // FinishStartHost is guaranteed to be called after the host server was
442 // fully started and all the asynchronous StartHost magic is finished
443 // (= scene loading), or immediately if there was no asynchronous magic.
444 //
445 // note: we don't really need FinishStartClient/FinishStartServer. the
446 // host version is enough.
447 void FinishStartHost()
448 {
449 // ConnectHost needs to be called BEFORE SpawnObjects:
450 // https://github.com/vis2k/Mirror/pull/1249/
451 // -> this sets NetworkServer.localConnection.
452 // -> localConnection needs to be set before SpawnObjects because:
453 // -> SpawnObjects calls OnStartServer in all NetworkBehaviours
454 // -> OnStartServer might spawn an object and set [SyncVar(hook="OnColorChanged")] object.color = green;
455 // -> this calls SyncVar.set (generated by Weaver), which has
456 // a custom case for host mode (because host mode doesn't
457 // get OnDeserialize calls, where SyncVar hooks are usually
458 // called):
459 //
460 // if (!SyncVarEqual(value, ref color))
461 // {
462 // if (NetworkServer.localClientActive && !getSyncVarHookGuard(1uL))
463 // {
464 // setSyncVarHookGuard(1uL, value: true);
465 // OnColorChangedHook(value);
466 // setSyncVarHookGuard(1uL, value: false);
467 // }
468 // SetSyncVar(value, ref color, 1uL);
469 // }
470 //
471 // -> localClientActive needs to be true, otherwise the hook
472 // isn't called in host mode!
473 //
474 // TODO call this after spawnobjects and worry about the syncvar hook fix later?
475 NetworkClient.ConnectHost();
476
477 // server scene was loaded. now spawn all the objects
479
480 // connect client and call OnStartClient AFTER server scene was
481 // loaded and all objects were spawned.
482 // DO NOT do this earlier. it would cause race conditions where a
483 // client will do things before the server is even fully started.
484 //Debug.Log("StartHostClient called");
485 StartHostClient();
486 }
487
488 void StartHostClient()
489 {
490 //Debug.Log("NetworkManager ConnectLocalClient");
491
492 if (authenticator != null)
493 {
494 authenticator.OnStartClient();
495 authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
496 }
497
498 networkAddress = "localhost";
499 NetworkServer.ActivateHostScene();
500 RegisterClientMessages();
501
502 // ConnectLocalServer needs to be called AFTER RegisterClientMessages
503 // (https://github.com/vis2k/Mirror/pull/1249/)
504 NetworkClient.ConnectLocalServer();
505
507 }
508
510 public void StopHost()
511 {
512 OnStopHost();
513
514 // calling OnTransportDisconnected was needed to fix
515 // https://github.com/vis2k/Mirror/issues/1515
516 // so that the host client receives a DisconnectMessage
517 // TODO reevaluate if this is still needed after all the disconnect
518 // fixes, and try to put this into LocalConnection.Disconnect!
519 NetworkServer.OnTransportDisconnected(NetworkConnection.LocalConnectionId);
520
521 StopClient();
522 StopServer();
523 }
524
526 public void StopServer()
527 {
528 // return if already stopped to avoid recursion deadlock
530 return;
531
532 if (authenticator != null)
533 {
534 authenticator.OnServerAuthenticated.RemoveListener(OnServerAuthenticated);
535 authenticator.OnStopServer();
536 }
537
538 // Get Network Manager out of DDOL before going to offline scene
539 // to avoid collision and let a fresh Network Manager be created.
540 // IMPORTANT: .gameObject can be null if StopClient is called from
541 // OnApplicationQuit or from tests!
542 if (gameObject != null
543 && gameObject.scene.name == "DontDestroyOnLoad"
544 && !string.IsNullOrWhiteSpace(offlineScene)
545 && SceneManager.GetActiveScene().path != offlineScene)
546 SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
547
548 OnStopServer();
549
550 //Debug.Log("NetworkManager StopServer");
552
553 // set offline mode BEFORE changing scene so that FinishStartScene
554 // doesn't think we need initialize anything.
555 mode = NetworkManagerMode.Offline;
556
557 if (!string.IsNullOrWhiteSpace(offlineScene))
558 {
560 }
561
562 startPositionIndex = 0;
563
564 networkSceneName = "";
565 }
566
568 public void StopClient()
569 {
570 if (mode == NetworkManagerMode.Offline)
571 return;
572
573 if (authenticator != null)
574 {
575 authenticator.OnClientAuthenticated.RemoveListener(OnClientAuthenticated);
576 authenticator.OnStopClient();
577 }
578
579 // Get Network Manager out of DDOL before going to offline scene
580 // to avoid collision and let a fresh Network Manager be created.
581 // IMPORTANT: .gameObject can be null if StopClient is called from
582 // OnApplicationQuit or from tests!
583 if (gameObject != null
584 && gameObject.scene.name == "DontDestroyOnLoad"
585 && !string.IsNullOrWhiteSpace(offlineScene)
586 && SceneManager.GetActiveScene().path != offlineScene)
587 SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
588
589 OnStopClient();
590
591 //Debug.Log("NetworkManager StopClient");
592
593 // set offline mode BEFORE changing scene so that FinishStartScene
594 // doesn't think we need initialize anything.
595 // set offline mode BEFORE NetworkClient.Disconnect so StopClient
596 // only runs once.
597 mode = NetworkManagerMode.Offline;
598
599 // shutdown client
602
603 // If this is the host player, StopServer will already be changing scenes.
604 // Check loadingSceneAsync to ensure we don't double-invoke the scene change.
605 // Check if NetworkServer.active because we can get here via Disconnect before server has started to change scenes.
606 if (!string.IsNullOrWhiteSpace(offlineScene) && !IsSceneActive(offlineScene) && loadingSceneAsync == null && !NetworkServer.active)
607 {
608 ClientChangeScene(offlineScene, SceneOperation.Normal);
609 }
610
611 networkSceneName = "";
612 }
613
614 // called when quitting the application by closing the window / pressing
615 // stop in the editor. virtual so that inheriting classes'
616 // OnApplicationQuit() can call base.OnApplicationQuit() too
617 public virtual void OnApplicationQuit()
618 {
619 // stop client first
620 // (we want to send the quit packet to the server instead of waiting
621 // for a timeout)
623 {
624 StopClient();
625 //Debug.Log("OnApplicationQuit: stopped client");
626 }
627
628 // stop server after stopping client (for proper host mode stopping)
629 if (NetworkServer.active)
630 {
631 StopServer();
632 //Debug.Log("OnApplicationQuit: stopped server");
633 }
634
635 // Call ResetStatics to reset statics and singleton
636 ResetStatics();
637 }
638
640 // useful for dedicated servers.
641 // useful for headless benchmark clients.
642 public virtual void ConfigureHeadlessFrameRate()
643 {
644#if UNITY_SERVER
645 Application.targetFrameRate = serverTickRate;
646 // Debug.Log($"Server Tick Rate set to {Application.targetFrameRate} Hz.");
647#endif
648 }
649
650 bool InitializeSingleton()
651 {
652 if (singleton != null && singleton == this)
653 return true;
654
656 {
657 if (singleton != null)
658 {
659 Debug.LogWarning("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will be destroyed.");
660 Destroy(gameObject);
661
662 // Return false to not allow collision-destroyed second instance to continue.
663 return false;
664 }
665 //Debug.Log("NetworkManager created singleton (DontDestroyOnLoad)");
666 singleton = this;
667 if (Application.isPlaying)
668 {
669 // Force the object to scene root, in case user made it a child of something
670 // in the scene since DDOL is only allowed for scene root objects
671 transform.SetParent(null);
672 DontDestroyOnLoad(gameObject);
673 }
674 }
675 else
676 {
677 //Debug.Log("NetworkManager created singleton (ForScene)");
678 singleton = this;
679 }
680
681 // set active transport AFTER setting singleton.
682 // so only if we didn't destroy ourselves.
683 Transport.activeTransport = transport;
684 return true;
685 }
686
687 void RegisterServerMessages()
688 {
689 NetworkServer.OnConnectedEvent = OnServerConnectInternal;
690 NetworkServer.OnDisconnectedEvent = OnServerDisconnect;
691 NetworkServer.OnErrorEvent = OnServerError;
692 NetworkServer.RegisterHandler<AddPlayerMessage>(OnServerAddPlayerInternal);
693
694 // Network Server initially registers its own handler for this, so we replace it here.
695 NetworkServer.ReplaceHandler<ReadyMessage>(OnServerReadyMessageInternal);
696 }
697
698 void RegisterClientMessages()
699 {
700 NetworkClient.OnConnectedEvent = OnClientConnectInternal;
701 NetworkClient.OnDisconnectedEvent = OnClientDisconnectInternal;
702 NetworkClient.OnErrorEvent = OnClientError;
703 NetworkClient.RegisterHandler<NotReadyMessage>(OnClientNotReadyMessageInternal);
704 NetworkClient.RegisterHandler<SceneMessage>(OnClientSceneInternal, false);
705
706 if (playerPrefab != null)
707 NetworkClient.RegisterPrefab(playerPrefab);
708
709 foreach (GameObject prefab in spawnPrefabs.Where(t => t != null))
710 NetworkClient.RegisterPrefab(prefab);
711 }
712
713 // This is the only way to clear the singleton, so another instance can be created.
714 // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
715 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
716 public static void ResetStatics()
717 {
718 // call StopHost if we have a singleton
719 if (singleton)
721
722 // reset all statics
723 startPositions.Clear();
724 startPositionIndex = 0;
725 clientReadyConnection = null;
726 loadingSceneAsync = null;
727 networkSceneName = string.Empty;
728
729 // and finally (in case it isn't null already)...
730 singleton = null;
731 }
732
733 // virtual so that inheriting classes' OnDestroy() can call base.OnDestroy() too
734 public virtual void OnDestroy()
735 {
736 //Debug.Log("NetworkManager destroyed");
737 }
738
740 // set by NetworkManager when changing the scene.
741 // new clients will automatically load this scene.
742 // Loading a scene manually won't set it.
743 public static string networkSceneName { get; protected set; } = "";
744
745 public static AsyncOperation loadingSceneAsync;
746
748 // Called automatically if onlineScene or offlineScene are set, but it
749 // can be called from user code to switch scenes again while the game is
750 // in progress. This automatically sets clients to be not-ready during
751 // the change and ready again to participate in the new scene.
752 public virtual void ServerChangeScene(string newSceneName)
753 {
754 if (string.IsNullOrWhiteSpace(newSceneName))
755 {
756 Debug.LogError("ServerChangeScene empty scene name");
757 return;
758 }
759
760 if (NetworkServer.isLoadingScene && newSceneName == networkSceneName)
761 {
762 Debug.LogError($"Scene change is already in progress for {newSceneName}");
763 return;
764 }
765
766 // Debug.Log($"ServerChangeScene {newSceneName}");
768 networkSceneName = newSceneName;
769
770 // Let server prepare for scene change
771 OnServerChangeScene(newSceneName);
772
773 // set server flag to stop processing messages while changing scenes
774 // it will be re-enabled in FinishLoadScene.
775 NetworkServer.isLoadingScene = true;
776
777 loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
778
779 // ServerChangeScene can be called when stopping the server
780 // when this happens the server is not active so does not need to tell clients about the change
782 {
783 // notify all clients about the new scene
784 NetworkServer.SendToAll(new SceneMessage { sceneName = newSceneName });
785 }
786
787 startPositionIndex = 0;
788 startPositions.Clear();
789 }
790
791 // This is only set in ClientChangeScene below...never on server.
792 // We need to check this in OnClientSceneChanged called from FinishLoadSceneClientOnly
793 // to prevent AddPlayer message after loading/unloading additive scenes
794 SceneOperation clientSceneOperation = SceneOperation.Normal;
795
796 internal void ClientChangeScene(string newSceneName, SceneOperation sceneOperation = SceneOperation.Normal, bool customHandling = false)
797 {
798 if (string.IsNullOrWhiteSpace(newSceneName))
799 {
800 Debug.LogError("ClientChangeScene empty scene name");
801 return;
802 }
803
804 //Debug.Log($"ClientChangeScene newSceneName: {newSceneName} networkSceneName{networkSceneName}");
805
806 // Let client prepare for scene change
807 OnClientChangeScene(newSceneName, sceneOperation, customHandling);
808
809 // After calling OnClientChangeScene, exit if server since server is already doing
810 // the actual scene change, and we don't need to do it for the host client
811 if (NetworkServer.active)
812 return;
813
814 // set client flag to stop processing messages while loading scenes.
815 // otherwise we would process messages and then lose all the state
816 // as soon as the load is finishing, causing all kinds of bugs
817 // because of missing state.
818 // (client may be null after StopClient etc.)
819 // Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded.");
820 NetworkClient.isLoadingScene = true;
821
822 // Cache sceneOperation so we know what was requested by the
823 // Scene message in OnClientChangeScene and OnClientSceneChanged
824 clientSceneOperation = sceneOperation;
825
826 // scene handling will happen in overrides of OnClientChangeScene and/or OnClientSceneChanged
827 // Do not call FinishLoadScene here. Custom handler will assign loadingSceneAsync and we need
828 // to wait for that to finish. UpdateScene already checks for that to be not null and isDone.
829 if (customHandling)
830 return;
831
832 switch (sceneOperation)
833 {
834 case SceneOperation.Normal:
835 loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
836 break;
837 case SceneOperation.LoadAdditive:
838 // Ensure additive scene is not already loaded on client by name or path
839 // since we don't know which was passed in the Scene message
840 if (!SceneManager.GetSceneByName(newSceneName).IsValid() && !SceneManager.GetSceneByPath(newSceneName).IsValid())
841 loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName, LoadSceneMode.Additive);
842 else
843 {
844 Debug.LogWarning($"Scene {newSceneName} is already loaded");
845
846 // Reset the flag that we disabled before entering this switch
847 NetworkClient.isLoadingScene = false;
848 }
849 break;
850 case SceneOperation.UnloadAdditive:
851 // Ensure additive scene is actually loaded on client by name or path
852 // since we don't know which was passed in the Scene message
853 if (SceneManager.GetSceneByName(newSceneName).IsValid() || SceneManager.GetSceneByPath(newSceneName).IsValid())
854 loadingSceneAsync = SceneManager.UnloadSceneAsync(newSceneName, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
855 else
856 {
857 Debug.LogWarning($"Cannot unload {newSceneName} with UnloadAdditive operation");
858
859 // Reset the flag that we disabled before entering this switch
860 NetworkClient.isLoadingScene = false;
861 }
862 break;
863 }
864
865 // don't change the client's current networkSceneName when loading additive scene content
866 if (sceneOperation == SceneOperation.Normal)
867 networkSceneName = newSceneName;
868 }
869
870 // support additive scene loads:
871 // NetworkScenePostProcess disables all scene objects on load, and
872 // * NetworkServer.SpawnObjects enables them again on the server when
873 // calling OnStartServer
874 // * NetworkClient.PrepareToSpawnSceneObjects enables them again on the
875 // client after the server sends ObjectSpawnStartedMessage to client
876 // in SpawnObserversForConnection. this is only called when the
877 // client joins, so we need to rebuild scene objects manually again
878 // TODO merge this with FinishLoadScene()?
879 void OnSceneLoaded(Scene scene, LoadSceneMode mode)
880 {
881 if (mode == LoadSceneMode.Additive)
882 {
883 if (NetworkServer.active)
884 {
885 // TODO only respawn the server objects from that scene later!
886 NetworkServer.SpawnObjects();
887 // Debug.Log($"Respawned Server objects after additive scene load: {scene.name}");
888 }
889 if (NetworkClient.active)
890 {
891 NetworkClient.PrepareToSpawnSceneObjects();
892 // Debug.Log($"Rebuild Client spawnableObjects after additive scene load: {scene.name}");
893 }
894 }
895 }
896
897 void UpdateScene()
898 {
899 if (loadingSceneAsync != null && loadingSceneAsync.isDone)
900 {
901 //Debug.Log($"ClientChangeScene done readyConn {clientReadyConnection}");
902
903 // try-finally to guarantee loadingSceneAsync being cleared.
904 // fixes https://github.com/vis2k/Mirror/issues/2517 where if
905 // FinishLoadScene throws an exception, loadingSceneAsync would
906 // never be cleared and this code would run every Update.
907 try
908 {
909 FinishLoadScene();
910 }
911 finally
912 {
913 loadingSceneAsync.allowSceneActivation = true;
914 loadingSceneAsync = null;
915 }
916 }
917 }
918
919 protected void FinishLoadScene()
920 {
921 // NOTE: this cannot use NetworkClient.allClients[0] - that client may be for a completely different purpose.
922
923 // process queued messages that we received while loading the scene
924 //Debug.Log("FinishLoadScene: resuming handlers after scene was loading.");
925 NetworkServer.isLoadingScene = false;
926 NetworkClient.isLoadingScene = false;
927
928 // host mode?
929 if (mode == NetworkManagerMode.Host)
930 {
931 FinishLoadSceneHost();
932 }
933 // server-only mode?
934 else if (mode == NetworkManagerMode.ServerOnly)
935 {
936 FinishLoadSceneServerOnly();
937 }
938 // client-only mode?
939 else if (mode == NetworkManagerMode.ClientOnly)
940 {
941 FinishLoadSceneClientOnly();
942 }
943 // otherwise we called it after stopping when loading offline scene.
944 // do nothing then.
945 }
946
947 // finish load scene part for host mode. makes code easier and is
948 // necessary for FinishStartHost later.
949 // (the 3 things have to happen in that exact order)
950 void FinishLoadSceneHost()
951 {
952 // debug message is very important. if we ever break anything then
953 // it's very obvious to notice.
954 //Debug.Log("Finished loading scene in host mode.");
955
956 if (clientReadyConnection != null)
957 {
959 clientLoadedScene = true;
960 clientReadyConnection = null;
961 }
962
963 // do we need to finish a StartHost() call?
964 // then call FinishStartHost and let it take care of spawning etc.
965 if (finishStartHostPending)
966 {
967 finishStartHostPending = false;
968 FinishStartHost();
969
970 // call OnServerSceneChanged
972
973 // DO NOT call OnClientSceneChanged here.
974 // the scene change happened because StartHost loaded the
975 // server's online scene. it has nothing to do with the client.
976 // this was not meant as a client scene load, so don't call it.
977 //
978 // otherwise AddPlayer would be called twice:
979 // -> once for client OnConnected
980 // -> once in OnClientSceneChanged
981 }
982 // otherwise we just changed a scene in host mode
983 else
984 {
985 // spawn server objects
986 NetworkServer.SpawnObjects();
987
988 // call OnServerSceneChanged
990
991 if (NetworkClient.isConnected)
993 }
994 }
995
996 // finish load scene part for server-only. . makes code easier and is
997 // necessary for FinishStartServer later.
998 void FinishLoadSceneServerOnly()
999 {
1000 // debug message is very important. if we ever break anything then
1001 // it's very obvious to notice.
1002 //Debug.Log("Finished loading scene in server-only mode.");
1003
1004 NetworkServer.SpawnObjects();
1006 }
1007
1008 // finish load scene part for client-only. makes code easier and is
1009 // necessary for FinishStartClient later.
1010 void FinishLoadSceneClientOnly()
1011 {
1012 // debug message is very important. if we ever break anything then
1013 // it's very obvious to notice.
1014 //Debug.Log("Finished loading scene in client-only mode.");
1015
1016 if (clientReadyConnection != null)
1017 {
1019 clientLoadedScene = true;
1020 clientReadyConnection = null;
1021 }
1022
1023 if (NetworkClient.isConnected)
1025 }
1026
1032 // Static because it's called from NetworkStartPosition::Awake
1033 // and singleton may not exist yet
1034 public static void RegisterStartPosition(Transform start)
1035 {
1036 // Debug.Log($"RegisterStartPosition: {start.gameObject.name} {start.position}");
1037 startPositions.Add(start);
1038
1039 // reorder the list so that round-robin spawning uses the start positions
1040 // in hierarchy order. This assumes all objects with NetworkStartPosition
1041 // component are siblings, either in the scene root or together as children
1042 // under a single parent in the scene.
1043 startPositions = startPositions.OrderBy(transform => transform.GetSiblingIndex()).ToList();
1044 }
1045
1047 // Static because it's called from NetworkStartPosition::OnDestroy
1048 // and singleton may not exist yet
1049 public static void UnRegisterStartPosition(Transform start)
1050 {
1051 //Debug.Log($"UnRegisterStartPosition: {start.name} {start.position}");
1052 startPositions.Remove(start);
1053 }
1054
1056 public Transform GetStartPosition()
1057 {
1058 // first remove any dead transforms
1059 startPositions.RemoveAll(t => t == null);
1060
1061 if (startPositions.Count == 0)
1062 return null;
1063
1064 if (playerSpawnMethod == PlayerSpawnMethod.Random)
1065 {
1066 return startPositions[UnityEngine.Random.Range(0, startPositions.Count)];
1067 }
1068 else
1069 {
1070 Transform startPosition = startPositions[startPositionIndex];
1071 startPositionIndex = (startPositionIndex + 1) % startPositions.Count;
1072 return startPosition;
1073 }
1074 }
1075
1076 void OnServerConnectInternal(NetworkConnectionToClient conn)
1077 {
1078 //Debug.Log("NetworkManager.OnServerConnectInternal");
1079
1080 if (authenticator != null)
1081 {
1082 // we have an authenticator - let it handle authentication
1083 authenticator.OnServerAuthenticate(conn);
1084 }
1085 else
1086 {
1087 // authenticate immediately
1088 OnServerAuthenticated(conn);
1089 }
1090 }
1091
1092 // called after successful authentication
1093 // TODO do the NetworkServer.OnAuthenticated thing from x branch
1094 void OnServerAuthenticated(NetworkConnectionToClient conn)
1095 {
1096 //Debug.Log("NetworkManager.OnServerAuthenticated");
1097
1098 // set connection to authenticated
1099 conn.isAuthenticated = true;
1100
1101 // proceed with the login handshake by calling OnServerConnect
1103 {
1104 SceneMessage msg = new SceneMessage() { sceneName = networkSceneName };
1105 conn.Send(msg);
1106 }
1107
1108 OnServerConnect(conn);
1109 }
1110
1111 void OnServerReadyMessageInternal(NetworkConnectionToClient conn, ReadyMessage msg)
1112 {
1113 //Debug.Log("NetworkManager.OnServerReadyMessageInternal");
1114 OnServerReady(conn);
1115 }
1116
1117 void OnServerAddPlayerInternal(NetworkConnectionToClient conn, AddPlayerMessage msg)
1118 {
1119 //Debug.Log("NetworkManager.OnServerAddPlayer");
1120
1121 if (autoCreatePlayer && playerPrefab == null)
1122 {
1123 Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
1124 return;
1125 }
1126
1127 if (autoCreatePlayer && playerPrefab.GetComponent<NetworkIdentity>() == null)
1128 {
1129 Debug.LogError("The PlayerPrefab does not have a NetworkIdentity. Please add a NetworkIdentity to the player prefab.");
1130 return;
1131 }
1132
1133 if (conn.identity != null)
1134 {
1135 Debug.LogError("There is already a player for this connection.");
1136 return;
1137 }
1138
1139 OnServerAddPlayer(conn);
1140 }
1141
1142 void OnClientConnectInternal()
1143 {
1144 //Debug.Log("NetworkManager.OnClientConnectInternal");
1145
1146 if (authenticator != null)
1147 {
1148 // we have an authenticator - let it handle authentication
1149 authenticator.OnClientAuthenticate();
1150 }
1151 else
1152 {
1153 // authenticate immediately
1154 OnClientAuthenticated();
1155 }
1156 }
1157
1158 // called after successful authentication
1159 void OnClientAuthenticated()
1160 {
1161 //Debug.Log("NetworkManager.OnClientAuthenticated");
1162
1163 // set connection to authenticated
1164 NetworkClient.connection.isAuthenticated = true;
1165
1166 // proceed with the login handshake by calling OnClientConnect
1167 if (string.IsNullOrWhiteSpace(onlineScene) || onlineScene == offlineScene || IsSceneActive(onlineScene))
1168 {
1169 clientLoadedScene = false;
1171 }
1172 else
1173 {
1174 // will wait for scene id to come from the server.
1175 clientLoadedScene = true;
1176 clientReadyConnection = NetworkClient.connection;
1177 }
1178 }
1179
1180 void OnClientDisconnectInternal()
1181 {
1182 //Debug.Log("NetworkManager.OnClientDisconnectInternal");
1184 }
1185
1186 void OnClientNotReadyMessageInternal(NotReadyMessage msg)
1187 {
1188 //Debug.Log("NetworkManager.OnClientNotReadyMessageInternal");
1189 NetworkClient.ready = false;
1191
1192 // NOTE: clientReadyConnection is not set here! don't want OnClientConnect to be invoked again after scene changes.
1193 }
1194
1195 void OnClientSceneInternal(SceneMessage msg)
1196 {
1197 //Debug.Log("NetworkManager.OnClientSceneInternal");
1198
1199 // This needs to run for host client too. NetworkServer.active is checked there
1200 if (NetworkClient.isConnected)
1201 {
1202 ClientChangeScene(msg.sceneName, msg.sceneOperation, msg.customHandling);
1203 }
1204 }
1205
1207 public virtual void OnServerConnect(NetworkConnectionToClient conn) {}
1208
1210 // Called by NetworkServer.OnTransportDisconnect!
1212 {
1213 // by default, this function destroys the connection's player.
1214 // can be overwritten for cases like delayed logouts in MMOs to
1215 // avoid players escaping from PvP situations by logging out.
1217 //Debug.Log("OnServerDisconnect: Client disconnected.");
1218 }
1219
1222 {
1223 if (conn.identity == null)
1224 {
1225 // this is now allowed (was not for a while)
1226 //Debug.Log("Ready with no player object");
1227 }
1229 }
1230
1232 // The default implementation for this function creates a new player object from the playerPrefab.
1234 {
1235 Transform startPos = GetStartPosition();
1236 GameObject player = startPos != null
1237 ? Instantiate(playerPrefab, startPos.position, startPos.rotation)
1238 : Instantiate(playerPrefab);
1239
1240 // instantiating a "Player" prefab gives it the name "Player(clone)"
1241 // => appending the connectionId is WAY more useful for debugging!
1242 player.name = $"{playerPrefab.name} [connId={conn.connectionId}]";
1244 }
1245
1246 // DEPRECATED 2022-05-12
1247 [Obsolete("OnServerError(conn, Exception) was changed to OnServerError(conn, TransportError, string)")]
1248 public virtual void OnServerError(NetworkConnectionToClient conn, Exception exception) {}
1250 public virtual void OnServerError(NetworkConnectionToClient conn, TransportError error, string reason)
1251 {
1252#pragma warning disable CS0618
1253 OnServerError(conn, new Exception(reason));
1254#pragma warning restore CS0618
1255 }
1256
1258 public virtual void OnServerChangeScene(string newSceneName) {}
1259
1261 public virtual void OnServerSceneChanged(string sceneName) {}
1262
1264 public virtual void OnClientConnect()
1265 {
1266 // OnClientConnect by default calls AddPlayer but it should not do
1267 // that when we have online/offline scenes. so we need the
1268 // clientLoadedScene flag to prevent it.
1269 if (!clientLoadedScene)
1270 {
1271 // Ready/AddPlayer is usually triggered by a scene load completing.
1272 // if no scene was loaded, then Ready/AddPlayer it here instead.
1273 if (!NetworkClient.ready)
1275
1276 if (autoCreatePlayer)
1278 }
1279 }
1280
1282 public virtual void OnClientDisconnect()
1283 {
1284 if (mode == NetworkManagerMode.Offline)
1285 return;
1286
1287 StopClient();
1288 }
1289
1290 // DEPRECATED 2022-05-12
1291 [Obsolete("OnClientError(Exception) was changed to OnClientError(TransportError, string)")]
1292 public virtual void OnClientError(Exception exception) {}
1294 public virtual void OnClientError(TransportError error, string reason)
1295 {
1296#pragma warning disable CS0618
1297 OnClientError(new Exception(reason));
1298#pragma warning restore CS0618
1299 }
1300
1302 public virtual void OnClientNotReady() {}
1303
1305 // customHandling: indicates if scene loading will be handled through overrides
1306 public virtual void OnClientChangeScene(string newSceneName, SceneOperation sceneOperation, bool customHandling) {}
1307
1309 // Scene changes can cause player objects to be destroyed. The default
1310 // implementation of OnClientSceneChanged in the NetworkManager is to
1311 // add a player object for the connection if no player object exists.
1312 public virtual void OnClientSceneChanged()
1313 {
1314 // always become ready.
1316
1317 // Only call AddPlayer for normal scene changes, not additive load/unload
1318 if (clientSceneOperation == SceneOperation.Normal && autoCreatePlayer && NetworkClient.localPlayer == null)
1319 {
1320 // add player if existing one is null
1322 }
1323 }
1324
1325 // Since there are multiple versions of StartServer, StartClient and
1326 // StartHost, to reliably customize their functionality, users would
1327 // need override all the versions. Instead these callbacks are invoked
1328 // from all versions, so users only need to implement this one case.
1329
1331 public virtual void OnStartHost() {}
1332
1334 public virtual void OnStartServer() {}
1335
1337 public virtual void OnStartClient() {}
1338
1340 public virtual void OnStopServer() {}
1341
1343 public virtual void OnStopClient() {}
1344
1346 public virtual void OnStopHost() {}
1347 }
1348}
Base class for implementing component-based authentication during the Connect phase
virtual void OnClientAuthenticate()
Called on client from OnClientConnectInternal when a client needs to authenticate
UnityEvent OnClientAuthenticated
Notify subscribers on the client when the client is authenticated
virtual void OnServerAuthenticate(NetworkConnectionToClient conn)
Called on server from OnServerConnectInternal when a client needs to authenticate
virtual void OnStartClient()
Called when client starts, used to register message handlers if needed.
virtual void OnStopClient()
Called when client stops, used to unregister message handlers if needed.
UnityEventNetworkConnection OnServerAuthenticated
Notify subscribers on the server when a client is authenticated
virtual void OnStopServer()
Called when server stops, used to unregister message handlers if needed.
virtual void OnStartServer()
Called when server starts, used to register message handlers if needed.
NetworkClient with connection to server.
static bool active
active is true while a client is connecting/connected
static void Connect(string address)
Connect client to a NetworkServer by address.
static bool ready
True if client is ready (= joined world).
static void Shutdown()
Shutdown the client.
static bool Ready()
Sends Ready message to server, indicating that we loaded the scene, ready to enter the game.
static void Disconnect()
Disconnect from server.
static bool AddPlayer()
Sends AddPlayer message to the server, indicating that we want to join the world.
static bool isConnected
Check if client is connected (after connecting).
static NetworkIdentity localPlayer
NetworkIdentity of the localPlayer
Base NetworkConnection class for server-to-client and client-to-server connection.
NetworkIdentity identity
This connection's main object (usually the player object).
NetworkIdentity identifies objects across the network.
virtual void OnClientConnect()
Called on the client when connected to a server. By default it sets client as ready and adds a player...
virtual void OnStartServer()
This is invoked when a server is started - including when a host is started.
virtual void OnStopHost()
This is called when a host is stopped.
List< GameObject > spawnPrefabs
Prefabs that can be spawned over the network need to be registered here.
static NetworkManager singleton
The one and only NetworkManager
virtual void OnClientChangeScene(string newSceneName, SceneOperation sceneOperation, bool customHandling)
Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed
bool isNetworkActive
True if the server is running or client is connected/connecting.
void StartClient()
Starts the client, connects it to the server with networkAddress.
virtual void OnStartHost()
This is invoked when a host is started.
virtual void OnServerChangeScene(string newSceneName)
Called from ServerChangeScene immediately before SceneManager.LoadSceneAsync is executed
void StopHost()
This stops both the client and the server that the manager is using.
void StopServer()
Stops the server from listening and simulating the game.
void StartHost()
Starts a network "host" - a server and client in the same application.
virtual void OnStopClient()
This is called when a client is stopped.
static void RegisterStartPosition(Transform start)
Registers the transform of a game object as a player spawn location.
Transform GetStartPosition()
Get the next NetworkStartPosition based on the selected PlayerSpawnMethod.
virtual void ConfigureHeadlessFrameRate()
Set the frame rate for a headless builds. Override to disable or modify.
virtual void OnServerDisconnect(NetworkConnectionToClient conn)
Called on the server when a client disconnects.
string networkAddress
Server's address for clients to connect to.
virtual void OnClientError(TransportError error, string reason)
Called on client when transport raises an exception.
bool autoCreatePlayer
Enable to automatically create player objects on connect and on scene change.
virtual void OnStopServer()
This is called when a server is stopped - including when a host is stopped.
int maxConnections
The maximum number of concurrent network connections to support.
bool autoStartServerBuild
Should the server auto-start when 'Server Build' is checked in build settings
void StopClient()
Stops and disconnects the client.
bool clientLoadedScene
True if the client loaded a new scene when connecting to the server.
virtual void OnClientSceneChanged()
Called on clients when a scene has completed loaded, when the scene load was initiated by the server.
virtual void OnServerError(NetworkConnectionToClient conn, TransportError error, string reason)
Called on server when transport raises an exception. NetworkConnection may be null.
GameObject playerPrefab
The default prefab to be used to create player objects on the server.
static string networkSceneName
The name of the current network scene.
virtual void OnServerSceneChanged(string sceneName)
Called on server after a scene load with ServerChangeScene() is completed.
string onlineScene
Automatically switch to this scene upon going online (after connect/startserver).
virtual void OnClientDisconnect()
Called on clients when disconnected from a server.
virtual void OnStartClient()
This is invoked when the client is started.
bool dontDestroyOnLoad
Enable to keep NetworkManager alive when changing scenes.
static List< Transform > startPositions
List of transforms populated by NetworkStartPositions
virtual void OnServerReady(NetworkConnectionToClient conn)
Called on the server when a client is ready (= loaded the scene)
static void UnRegisterStartPosition(Transform start)
Unregister a Transform from start positions.
bool runInBackground
Multiplayer games should always run in the background so the network doesn't time out.
void StartServer()
Starts the server, listening for incoming connections.
virtual void OnServerAddPlayer(NetworkConnectionToClient conn)
Called on server when a client requests to add the player. Adds playerPrefab by default....
virtual void OnServerConnect(NetworkConnectionToClient conn)
Called on the server when a new client connects.
virtual void OnClientNotReady()
Called on clients when a servers tells the client it is no longer ready, e.g. when switching scenes.
void StartClient(Uri uri)
Starts the client, connects it to the server via Uri
virtual void ServerChangeScene(string newSceneName)
Change the server scene and all client's scenes across the network.
int serverTickRate
Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to mini...
PlayerSpawnMethod playerSpawnMethod
Where to spawn players.
int numPlayers
Number of active player objects across all connections on the server.
string offlineScene
Automatically switch to this scene upon going offline (on start / on disconnect / on shutdown).
NetworkServer handles remote connections and has a local connection for a local client.
static bool SpawnObjects()
Spawns NetworkIdentities in the scene on the server.
static void SetClientReady(NetworkConnectionToClient conn)
Flags client connection as ready (=joined world).
static Dictionary< int, NetworkConnectionToClient > connections
Dictionary of all server connections, with connectionId as key
static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player)
Called by server after AddPlayer message to add the player for the connection.
static bool active
active checks if the server has been started
static void DestroyPlayerForConnection(NetworkConnectionToClient conn)
Destroys all of the connection's owned objects on the server.
static void Shutdown()
Shuts down the server and disconnects all clients
static void SetAllClientsNotReady()
Marks all connected clients as no longer ready.
Abstract transport layer component
Definition: Transport.cs:32