using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; namespace Mirror.Examples.AdditiveLevels { [AddComponentMenu("")] public class AdditiveLevelsNetworkManager : NetworkManager { [Header("Additive Scenes - First is start scene")] [Scene, Tooltip("Add additive scenes here.\nFirst entry will be players' start scene")] public string[] additiveScenes; [Header("Fade Control - See child FadeCanvas")] [Tooltip("Reference to FadeInOut script on child FadeCanvas")] public FadeInOut fadeInOut; // This is set true after server loads all subscene instances bool subscenesLoaded; // This is managed in LoadAdditive, UnloadAdditive, and checked in OnClientSceneChanged bool isInTransition; #region Scene Management /// /// Called on the server when a scene is completed loaded, when the scene load was initiated by the server with ServerChangeScene(). /// /// The name of the new scene. public override void OnServerSceneChanged(string sceneName) { // This fires after server fully changes scenes, e.g. offline to online // If server has just loaded the Container (online) scene, load the subscenes on server if (sceneName == onlineScene) StartCoroutine(ServerLoadSubScenes()); } IEnumerator ServerLoadSubScenes() { foreach (string additiveScene in additiveScenes) yield return SceneManager.LoadSceneAsync(additiveScene, new LoadSceneParameters { loadSceneMode = LoadSceneMode.Additive, localPhysicsMode = LocalPhysicsMode.Physics3D // change this to .Physics2D for a 2D game }); subscenesLoaded = true; } /// /// Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed /// This allows client to do work / cleanup / prep before the scene changes. /// /// Name of the scene that's about to be loaded /// Scene operation that's about to happen /// true to indicate that scene loading will be handled through overrides public override void OnClientChangeScene(string sceneName, SceneOperation sceneOperation, bool customHandling) { //Debug.Log($"{System.DateTime.Now:HH:mm:ss:fff} OnClientChangeScene {sceneName} {sceneOperation}"); if (sceneOperation == SceneOperation.UnloadAdditive) StartCoroutine(UnloadAdditive(sceneName)); if (sceneOperation == SceneOperation.LoadAdditive) StartCoroutine(LoadAdditive(sceneName)); } IEnumerator LoadAdditive(string sceneName) { isInTransition = true; // This will return immediately if already faded in // e.g. by UnloadAdditive above or by default startup state yield return fadeInOut.FadeIn(); // host client is on server...don't load the additive scene again if (mode == NetworkManagerMode.ClientOnly) { // Start loading the additive subscene loadingSceneAsync = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); while (loadingSceneAsync != null && !loadingSceneAsync.isDone) yield return null; } // Reset these to false when ready to proceed NetworkClient.isLoadingScene = false; isInTransition = false; OnClientSceneChanged(); yield return fadeInOut.FadeOut(); } IEnumerator UnloadAdditive(string sceneName) { isInTransition = true; // This will return immediately if already faded in // e.g. by LoadAdditive above or by default startup state yield return fadeInOut.FadeIn(); if (mode == NetworkManagerMode.ClientOnly) { yield return SceneManager.UnloadSceneAsync(sceneName); yield return Resources.UnloadUnusedAssets(); } // Reset these to false when ready to proceed NetworkClient.isLoadingScene = false; isInTransition = false; OnClientSceneChanged(); // There is no call to FadeOut here on purpose. // Expectation is that a LoadAdditive will follow // that will call FadeOut after that scene loads. } /// /// Called on clients when a scene has completed loaded, when the scene load was initiated by the server. /// Scene changes can cause player objects to be destroyed. The default implementation of OnClientSceneChanged in the NetworkManager is to add a player object for the connection if no player object exists. /// /// The network connection that the scene change message arrived on. public override void OnClientSceneChanged() { //Debug.Log($"{System.DateTime.Now:HH:mm:ss:fff} OnClientSceneChanged {isInTransition}"); // Only call the base method if not in a transition. // This will be called from DoTransition after setting doingTransition to false // but will also be called first by Mirror when the scene loading finishes. if (!isInTransition) base.OnClientSceneChanged(); } #endregion #region Server System Callbacks /// /// Called on the server when a client is ready. /// The default implementation of this function calls NetworkServer.SetClientReady() to continue the network setup process. /// /// Connection from client. public override void OnServerReady(NetworkConnectionToClient conn) { //Debug.Log($"OnServerReady {conn} {conn.identity}"); // This fires from a Ready message client sends to server after loading the online scene base.OnServerReady(conn); if (conn.identity == null) StartCoroutine(AddPlayerDelayed(conn)); } // This delay is mostly for the host player that loads too fast for the // server to have subscenes async loaded from OnServerSceneChanged ahead of it. IEnumerator AddPlayerDelayed(NetworkConnectionToClient conn) { // Wait for server to async load all subscenes for game instances while (!subscenesLoaded) yield return null; // Send Scene msg to client telling it to load the first additive scene conn.Send(new SceneMessage { sceneName = additiveScenes[0], sceneOperation = SceneOperation.LoadAdditive, customHandling = true }); // We have Network Start Positions in first additive scene...pick one Transform start = GetStartPosition(); // Instantiate player as child of start position - this will place it in the additive scene // This also lets player object "inherit" pos and rot from start position transform GameObject player = Instantiate(playerPrefab, start); // now set parent null to get it out from under the Start Position object player.transform.SetParent(null); // Wait for end of frame before adding the player to ensure Scene Message goes first yield return new WaitForEndOfFrame(); // Finally spawn the player object for this connection NetworkServer.AddPlayerForConnection(conn, player); } #endregion } }