(Fix) Loading Screen

This commit is contained in:
2026-06-24 17:28:54 +02:00
parent 7291acab91
commit 144b3bfbc4
18 changed files with 1257 additions and 548 deletions
@@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using FishNet;
using FishNet.Connection;
@@ -29,6 +30,10 @@ namespace Ashwild.Network
[Tooltip("The single source of truth for the gameplay scene loaded over the network.")]
[SerializeField] private string gameSceneName = "TestScene";
[Tooltip("Max seconds to wait for the loading curtain to fully fade in before loading the game " +
"scene anyway. Safety net so a missing/disabled LoadingScreen can never soft-lock the host.")]
[SerializeField] private float curtainWaitTimeout = 3f;
[Header("Player")]
[Tooltip("The networked player prefab (NetworkObject + PlayerNetworkController).")]
[SerializeField] private NetworkObject playerPrefab;
@@ -61,6 +66,12 @@ namespace Ashwild.Network
/// </summary>
private bool pendingSceneLoad;
/// <summary>
/// Set once the loading curtain has fully faded in; gates the heavy game scene load so the
/// activation freeze happens behind an opaque curtain. Reset at the start of every host launch.
/// </summary>
private bool curtainShown;
/// <summary>
/// Round-robin cursor over the available spawn points.
/// </summary>
@@ -107,6 +118,7 @@ namespace Ashwild.Network
networkManager.ServerManager.OnRemoteConnectionState += HandleRemoteState;
networkManager.ClientManager.OnClientConnectionState += HandleClientState;
networkManager.SceneManager.OnClientPresenceChangeEnd += HandleClientPresenceEnd;
PlayerEvents.LoadingCurtainShown += HandleLoadingCurtainShown;
}
/// <summary>
@@ -120,6 +132,7 @@ namespace Ashwild.Network
networkManager.ServerManager.OnRemoteConnectionState -= HandleRemoteState;
networkManager.ClientManager.OnClientConnectionState -= HandleClientState;
networkManager.SceneManager.OnClientPresenceChangeEnd -= HandleClientPresenceEnd;
PlayerEvents.LoadingCurtainShown -= HandleLoadingCurtainShown;
}
/// <summary>
@@ -151,6 +164,7 @@ namespace Ashwild.Network
PlayerEvents.RaiseSessionStarting();
Log("Starting host (server + client) over Steam…");
curtainShown = false;
pendingSceneLoad = true;
bool serverOk = networkManager.ServerManager.StartConnection();
@@ -215,6 +229,26 @@ namespace Ashwild.Network
if (networkManager.ServerManager.Started) networkManager.ServerManager.StopConnection(true);
}
/// <summary>
/// Leaves the current session and returns to the menu scene behind the loading curtain. Mirrors
/// the host launch: it raises the curtain, waits for it to fully fade in (so the menu-load freeze
/// is hidden), tears the session down, loads the menu, then signals MenuReady to drop the curtain.
/// Orchestrated here — not on the per-scene GameUIManager — because this object persists across
/// the scene swap while the caller is destroyed by the load.
/// </summary>
public void ReturnToMenu(string menuSceneName)
{
if (string.IsNullOrWhiteSpace(menuSceneName))
{
Debug.LogError("[NetworkSession] ReturnToMenu called with no menu scene name.", this);
return;
}
PlayerEvents.RaiseReturningToMenu();
curtainShown = false;
StartCoroutine(ReturnToMenuRoutine(menuSceneName));
}
#endregion
#region Connection Handlers
@@ -235,7 +269,7 @@ namespace Ashwild.Network
if (pendingSceneLoad)
{
pendingSceneLoad = false;
LoadGameSceneNetworked();
StartCoroutine(LoadGameSceneWhenCurtainShown());
}
}
else if (args.ConnectionState == LocalConnectionState.Stopped)
@@ -268,6 +302,12 @@ namespace Ashwild.Network
}
}
/// <summary>
/// The loading curtain finished fading in — release the gate so the deferred game scene
/// load can proceed (see <see cref="LoadGameSceneWhenCurtainShown"/>).
/// </summary>
private void HandleLoadingCurtainShown() => curtainShown = true;
/// <summary>
/// A *remote* client connected to or disconnected from our server; updates the count.
/// </summary>
@@ -343,6 +383,53 @@ namespace Ashwild.Network
#region Internal Helpers
/// <summary>
/// Defers the heavy networked scene load until the loading curtain has fully faded in
/// (PlayerEvents.LoadingCurtainShown), so the unavoidable activation freeze of a big scene
/// happens behind an already-opaque curtain instead of stalling the frame the curtain was
/// meant to appear on. Falls back to loading anyway after <see cref="curtainWaitTimeout"/> so
/// a missing or disabled LoadingScreen can never soft-lock the host. Runs on unscaled time.
/// </summary>
private IEnumerator LoadGameSceneWhenCurtainShown()
{
float start = Time.unscaledTime;
while (!curtainShown && Time.unscaledTime - start < curtainWaitTimeout)
yield return null;
if (!curtainShown)
Log($"Loading curtain not reported within {curtainWaitTimeout}s — loading scene anyway.");
LoadGameSceneNetworked();
}
/// <summary>
/// Waits for the curtain to fully fade in (or the timeout), tears the session down, loads the
/// menu scene locally (Single — the persistent NetworkManager survives), then raises MenuReady
/// so the curtain drops once the menu is up. Runs on the persistent NetworkManager object so it
/// outlives the game scene being unloaded. Uses unscaled time.
/// </summary>
private IEnumerator ReturnToMenuRoutine(string menuSceneName)
{
float start = Time.unscaledTime;
while (!curtainShown && Time.unscaledTime - start < curtainWaitTimeout)
yield return null;
StopSession();
AsyncOperation op = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(menuSceneName, LoadSceneMode.Single);
if (op != null)
{
while (!op.isDone)
yield return null;
}
else
{
Debug.LogError($"[NetworkSession] Could not load menu scene '{menuSceneName}' — is it in Build Settings?", this);
}
PlayerEvents.RaiseMenuReady();
}
/// <summary>
/// Loads the gameplay scene as a global networked scene, replacing the menu.
/// </summary>