using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; /* Documentation: https://mirror-networking.gitbook.io/docs/components/network-manager API Reference: https://mirror-networking.com/docs/api/Mirror.NetworkManager.html */ namespace Mirror.Examples.MultipleAdditiveScenes { [AddComponentMenu("")] public class MultiSceneNetManager : NetworkManager { [Header("Spawner Setup")] [Tooltip("Reward Prefab for the Spawner")] public GameObject rewardPrefab; [Header("MultiScene Setup")] public int instances = 3; [Scene] public string gameScene; // This is set true after server loads all subscene instances bool subscenesLoaded; // subscenes are added to this list as they're loaded readonly List subScenes = new List(); // Sequential index used in round-robin deployment of players into instances and score positioning int clientIndex; #region Server System Callbacks /// /// Called on the server when a client adds a new player with NetworkClient.AddPlayer. /// The default implementation for this function creates a new player object from the playerPrefab. /// /// Connection from client. public override void OnServerAddPlayer(NetworkConnectionToClient conn) { StartCoroutine(OnServerAddPlayerDelayed(conn)); } // This delay is mostly for the host player that loads too fast for the // server to have subscenes async loaded from OnStartServer ahead of it. IEnumerator OnServerAddPlayerDelayed(NetworkConnectionToClient conn) { // wait for server to async load all subscenes for game instances while (!subscenesLoaded) yield return null; // Send Scene message to client to additively load the game scene conn.Send(new SceneMessage { sceneName = gameScene, sceneOperation = SceneOperation.LoadAdditive }); // Wait for end of frame before adding the player to ensure Scene Message goes first yield return new WaitForEndOfFrame(); base.OnServerAddPlayer(conn); PlayerScore playerScore = conn.identity.GetComponent(); playerScore.playerNumber = clientIndex; playerScore.scoreIndex = clientIndex / subScenes.Count; playerScore.matchIndex = clientIndex % subScenes.Count; // Do this only on server, not on clients // This is what allows the NetworkSceneChecker on player and scene objects // to isolate matches per scene instance on server. if (subScenes.Count > 0) SceneManager.MoveGameObjectToScene(conn.identity.gameObject, subScenes[clientIndex % subScenes.Count]); clientIndex++; } #endregion #region Start & Stop Callbacks /// /// This is invoked when a server is started - including when a host is started. /// StartServer has multiple signatures, but they all cause this hook to be called. /// public override void OnStartServer() { StartCoroutine(ServerLoadSubScenes()); } // We're additively loading scenes, so GetSceneAt(0) will return the main "container" scene, // therefore we start the index at one and loop through instances value inclusively. // If instances is zero, the loop is bypassed entirely. IEnumerator ServerLoadSubScenes() { for (int index = 1; index <= instances; index++) { yield return SceneManager.LoadSceneAsync(gameScene, new LoadSceneParameters { loadSceneMode = LoadSceneMode.Additive, localPhysicsMode = LocalPhysicsMode.Physics3D }); Scene newScene = SceneManager.GetSceneAt(index); subScenes.Add(newScene); Spawner.InitialSpawn(newScene); } subscenesLoaded = true; } /// /// This is called when a server is stopped - including when a host is stopped. /// public override void OnStopServer() { NetworkServer.SendToAll(new SceneMessage { sceneName = gameScene, sceneOperation = SceneOperation.UnloadAdditive }); StartCoroutine(ServerUnloadSubScenes()); clientIndex = 0; } // Unload the subScenes and unused assets and clear the subScenes list. IEnumerator ServerUnloadSubScenes() { for (int index = 0; index < subScenes.Count; index++) yield return SceneManager.UnloadSceneAsync(subScenes[index]); subScenes.Clear(); subscenesLoaded = false; yield return Resources.UnloadUnusedAssets(); } /// /// This is called when a client is stopped. /// public override void OnStopClient() { // make sure we're not in host mode if (mode == NetworkManagerMode.ClientOnly) StartCoroutine(ClientUnloadSubScenes()); } // Unload all but the active scene, which is the "container" scene IEnumerator ClientUnloadSubScenes() { for (int index = 0; index < SceneManager.sceneCount; index++) { if (SceneManager.GetSceneAt(index) != SceneManager.GetActiveScene()) yield return SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(index)); } } #endregion } }