using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Serialization; namespace Mirror { public enum PlayerSpawnMethod { Random, RoundRobin } public enum NetworkManagerMode { Offline, ServerOnly, ClientOnly, Host } public enum HeadlessStartOptions { DoNothing, AutoStartServer, AutoStartClient } [DisallowMultipleComponent] [AddComponentMenu("Network/Network Manager")] [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-manager")] public class NetworkManager : MonoBehaviour { /// Enable to keep NetworkManager alive when changing scenes. // 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. [Header("Configuration")] [FormerlySerializedAs("m_DontDestroyOnLoad")] [Tooltip("Should the Network Manager object be persisted through scene changes?")] public bool dontDestroyOnLoad = true; /// Multiplayer games should always run in the background so the network doesn't time out. [FormerlySerializedAs("m_RunInBackground")] [Tooltip("Multiplayer games should always run in the background so the network doesn't time out.")] public bool runInBackground = true; /// Should the server auto-start when 'Server Build' is checked in build settings [Header("Auto-Start Options")] [Tooltip("Choose whether Server or Client should auto-start in headless builds")] public HeadlessStartOptions headlessStartMode = HeadlessStartOptions.DoNothing; [Tooltip("Headless Start Mode in Editor\nwhen enabled, headless start mode will be used in editor as well.")] public bool editorAutoStart; /// 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. [Tooltip("Server / Client send rate per second.\nUse 60-100Hz for fast paced games like Counter-Strike to minimize latency.\nUse around 30Hz for games like WoW to minimize computations.\nUse around 1-10Hz for slow paced games like EVE.")] [FormerlySerializedAs("serverTickRate")] public int sendRate = 60; // Deprecated 2023-11-25 // Using SerializeField and HideInInspector to self-correct for being // replaced by headlessStartMode. This can be removed in the future. // See OnValidate() for how we handle this. [Obsolete("Deprecated - Use headlessStartMode instead.")] [FormerlySerializedAs("autoStartServerBuild"), SerializeField, HideInInspector] public bool autoStartServerBuild = true; // Deprecated 2023-11-25 // Using SerializeField and HideInInspector to self-correct for being // replaced by headlessStartMode. This can be removed in the future. // See OnValidate() for how we handle this. [Obsolete("Deprecated - Use headlessStartMode instead.")] [FormerlySerializedAs("autoConnectClientBuild"), SerializeField, HideInInspector] public bool autoConnectClientBuild; // client send rate follows server send rate to avoid errors for now /// Client 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. // [Tooltip("Client broadcasts 'sendRate' times 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.")] // public int clientSendRate = 30; // 33 ms /// Automatically switch to this scene upon going offline (on start / on disconnect / on shutdown). [Header("Scene Management")] [Scene] [FormerlySerializedAs("m_OfflineScene")] [Tooltip("Scene that Mirror will switch to when the client or server is stopped")] public string offlineScene = ""; /// Automatically switch to this scene upon going online (after connect/startserver). [Scene] [FormerlySerializedAs("m_OnlineScene")] [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.")] public string onlineScene = ""; [Range(0, 60), Tooltip("Optional delay that can be used after disconnecting to show a 'Connection lost...' message or similar before loading the offline scene, which may take a long time in big projects.")] public float offlineSceneLoadDelay = 0; // transport layer [Header("Network Info")] [Tooltip("Transport component attached to this object that server and client will use to connect")] public Transport transport; /// Server's address for clients to connect to. [FormerlySerializedAs("m_NetworkAddress")] [Tooltip("Network Address where the client should connect to the server. Server does not use this for anything.")] public string networkAddress = "localhost"; /// The maximum number of concurrent network connections to support. [FormerlySerializedAs("m_MaxConnections")] [Tooltip("Maximum number of concurrent connections.")] public int maxConnections = 100; // Mirror global disconnect inactive option, independent of Transport. // not all Transports do this properly, and it's easiest to configure this just once. // this is very useful for some projects, keep it. [Tooltip("When enabled, the server automatically disconnects inactive connections after the configured timeout.")] public bool disconnectInactiveConnections; [Tooltip("Timeout in seconds for server to automatically disconnect inactive connections if 'disconnectInactiveConnections' is enabled.")] public float disconnectInactiveTimeout = 60f; [Header("Authentication")] [Tooltip("Authentication component attached to this object")] public NetworkAuthenticator authenticator; /// The default prefab to be used to create player objects on the server. // Player objects are created in the default handler for AddPlayer() on // the server. Implementing OnServerAddPlayer overrides this behaviour. [Header("Player Object")] [FormerlySerializedAs("m_PlayerPrefab")] [Tooltip("Prefab of the player object. Prefab must have a Network Identity component. May be an empty game object or a full avatar.")] public GameObject playerPrefab; /// Enable to automatically create player objects on connect and on scene change. [FormerlySerializedAs("m_AutoCreatePlayer")] [Tooltip("Should Mirror automatically spawn the player after scene change?")] public bool autoCreatePlayer = true; /// Where to spawn players. [FormerlySerializedAs("m_PlayerSpawnMethod")] [Tooltip("Round Robin or Random order of Start Position selection")] public PlayerSpawnMethod playerSpawnMethod; /// Prefabs that can be spawned over the network need to be registered here. [FormerlySerializedAs("m_SpawnPrefabs"), HideInInspector] public List spawnPrefabs = new List(); /// List of transforms populated by NetworkStartPositions public static List startPositions = new List(); public static int startPositionIndex; [Header("Security")] [Tooltip("For security, it is recommended to disconnect a player if a networked action triggers an exception\nThis could prevent components being accessed in an undefined state, which may be an attack vector for exploits.\nHowever, some games may want to allow exceptions in order to not interrupt the player's experience.")] public bool exceptionsDisconnect = true; // security by default [Header("Snapshot Interpolation")] public SnapshotInterpolationSettings snapshotSettings = new SnapshotInterpolationSettings(); [Header("Connection Quality")] [Tooltip("Method to use for connection quality evaluation.\nSimple: based on rtt and jitter.\nPragmatic: based on snapshot interpolation adjustment.")] public ConnectionQualityMethod evaluationMethod; [Tooltip("Interval in seconds to evaluate connection quality.\nSet to 0 to disable connection quality evaluation.")] [Range(0, 60)] [FormerlySerializedAs("connectionQualityInterval")] public float evaluationInterval = 3; [Header("Interpolation UI - Requires Editor / Dev Build")] public bool timeInterpolationGui = false; /// The one and only NetworkManager public static NetworkManager singleton { get; internal set; } /// Number of active player objects across all connections on the server. public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.identity != null); /// True if the server is running or client is connected/connecting. public bool isNetworkActive => NetworkServer.active || NetworkClient.active; // TODO remove this // internal for tests internal static NetworkConnection clientReadyConnection; /// True if the client loaded a new scene when connecting to the server. // This is set before OnClientConnect is called, so it can be checked // there to perform different logic if a scene load occurred. protected bool clientLoadedScene; // helper enum to know if we started the networkmanager as server/client/host. // -> this is necessary because when StartHost changes server scene to // online scene, FinishLoadScene is called and the host client isn't // connected yet (no need to connect it before server was fully set up). // in other words, we need this to know which mode we are running in // during FinishLoadScene. public NetworkManagerMode mode { get; private set; } // virtual so that inheriting classes' OnValidate() can call base.OnValidate() too public virtual void OnValidate() { #pragma warning disable 618 // autoStartServerBuild and autoConnectClientBuild are now obsolete, but to avoid // a breaking change we'll set headlessStartMode to what the user had set before. // // headlessStartMode defaults to DoNothing, so if the user had neither of these // set, then it will remain as DoNothing, and if they set headlessStartMode to // any selection in the inspector it won't get changed back. if (autoStartServerBuild) headlessStartMode = HeadlessStartOptions.AutoStartServer; else if (autoConnectClientBuild) headlessStartMode = HeadlessStartOptions.AutoStartClient; // Setting both to false here prevents this code from fighting with user // selection in the inspector, and they're both SerialisedField's. autoStartServerBuild = false; autoConnectClientBuild = false; #pragma warning restore 618 // always >= 0 maxConnections = Mathf.Max(maxConnections, 0); if (playerPrefab != null && !playerPrefab.TryGetComponent(out NetworkIdentity _)) { Debug.LogError("NetworkManager - Player Prefab must have a NetworkIdentity."); playerPrefab = null; } // This avoids the mysterious "Replacing existing prefab with assetId ... Old prefab 'Player', New prefab 'Player'" warning. if (playerPrefab != null && spawnPrefabs.Contains(playerPrefab)) { Debug.LogWarning("NetworkManager - Player Prefab doesn't need to be in Spawnable Prefabs list too. Removing it."); spawnPrefabs.Remove(playerPrefab); } } // virtual so that inheriting classes' Reset() can call base.Reset() too // Reset only gets called when the component is added or the user resets the component // Thats why we validate these things that only need to be validated on adding the NetworkManager here // If we would do it in OnValidate() then it would run this everytime a value changes public virtual void Reset() { // make sure someone doesn't accidentally add another NetworkManager // need transform.root because when adding to a child, the parent's // Reset isn't called. foreach (NetworkManager manager in transform.root.GetComponentsInChildren()) { if (manager != this) { 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."); // return early so that transport component isn't auto-added // to the duplicate NetworkManager. return; } } } // virtual so that inheriting classes' Awake() can call base.Awake() too public virtual void Awake() { // Don't allow collision-destroyed second instance to continue. if (!InitializeSingleton()) return; // Apply configuration in Awake once already ApplyConfiguration(); // Set the networkSceneName to prevent a scene reload // if client connection to server fails. networkSceneName = offlineScene; // setup OnSceneLoaded callback SceneManager.sceneLoaded += OnSceneLoaded; } // virtual so that inheriting classes' Start() can call base.Start() too public virtual void Start() { // Auto-start headless server or client. // // We can't do this in Awake because Awake is for initialization // and some transports might not be ready until Start. // // Auto-starting in Editor is useful for debugging, so that can // be enabled with editorAutoStart. if (Utils.IsHeadless()) { if (!Application.isEditor || editorAutoStart) switch (headlessStartMode) { case HeadlessStartOptions.AutoStartServer: StartServer(); break; case HeadlessStartOptions.AutoStartClient: StartClient(); break; } } } // make sure to call base.Update() when overwriting public virtual void Update() { ApplyConfiguration(); } // virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too public virtual void LateUpdate() { UpdateScene(); } //////////////////////////////////////////////////////////////////////// // keep the online scene change check in a separate function. // only change scene if the requested online scene is not blank, and is not already loaded. bool IsServerOnlineSceneChangeNeeded() => !string.IsNullOrWhiteSpace(onlineScene) && !Utils.IsSceneActive(onlineScene) && onlineScene != offlineScene; // NetworkManager exposes some NetworkServer/Client configuration. // we apply it every Update() in order to avoid two sources of truth. // fixes issues where NetworkServer.sendRate was never set because // NetworkManager.StartServer was never called, etc. // => all exposed settings should be applied at all times if NM exists. void ApplyConfiguration() { NetworkServer.tickRate = sendRate; NetworkClient.snapshotSettings = snapshotSettings; NetworkClient.connectionQualityInterval = evaluationInterval; NetworkClient.connectionQualityMethod = evaluationMethod; } // full server setup code, without spawning objects yet void SetupServer() { // Debug.Log("NetworkManager SetupServer"); InitializeSingleton(); // apply settings before initializing anything NetworkServer.disconnectInactiveConnections = disconnectInactiveConnections; NetworkServer.disconnectInactiveTimeout = disconnectInactiveTimeout; NetworkServer.exceptionsDisconnect = exceptionsDisconnect; if (runInBackground) Application.runInBackground = true; if (authenticator != null) { authenticator.OnStartServer(); authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated); } ConfigureHeadlessFrameRate(); // start listening to network connections NetworkServer.Listen(maxConnections); // this must be after Listen(), since that registers the default message handlers RegisterServerMessages(); // do not call OnStartServer here yet. // this is up to the caller. different for server-only vs. host mode. } /// Starts the server, listening for incoming connections. public void StartServer() { if (NetworkServer.active) { Debug.LogWarning("Server already started."); return; } mode = NetworkManagerMode.ServerOnly; // StartServer is inherently ASYNCHRONOUS (=doesn't finish immediately) // // Here is what it does: // Listen // if onlineScene: // LoadSceneAsync // ... // FinishLoadSceneServerOnly // SpawnObjects // else: // SpawnObjects // // there is NO WAY to make it synchronous because both LoadSceneAsync // and LoadScene do not finish loading immediately. as long as we // have the onlineScene feature, it will be asynchronous! SetupServer(); // call OnStartServer AFTER Listen, so that NetworkServer.active is // true and we can call NetworkServer.Spawn in OnStartServer // overrides. // (useful for loading & spawning stuff from database etc.) // // note: there is no risk of someone connecting after Listen() and // before OnStartServer() because this all runs in one thread // and we don't start processing connects until Update. OnStartServer(); // scene change needed? then change scene and spawn afterwards. if (IsServerOnlineSceneChangeNeeded()) { ServerChangeScene(onlineScene); } // otherwise spawn directly else { NetworkServer.SpawnObjec